יצירת תהליך עבודה עם מעורבות אנושית באמצעות קריאות חוזרות

במדריך הזה נסביר איך ליצור תהליך עבודה לתרגום שממתין לקלט שלכם – האדם שבתהליך – ומקשר בין מסד נתונים של Firestore, שתי פונקציות של Cloud Run, Cloud Translation API ודף אינטרנט שמשתמש ב-Firebase SDK כדי להתעדכן בזמן אמת.

באמצעות Workflows, אפשר לתמוך בנקודת קצה של קריאה חוזרת (או webhook) שממתינה לבקשות HTTP שיגיעו לנקודת הקצה הזו, וממשיכה את הביצוע של תהליך העבודה בשלב מאוחר יותר. במקרה הזה, תהליך העבודה ממתין לקלט שלכם כדי לדחות או לאמת את התרגום של טקסט מסוים, אבל הוא יכול גם להמתין לתהליך חיצוני. מידע נוסף מופיע בקטע המתנה באמצעות קריאות חוזרות (callback).

ארכיטקטורה

במדריך הזה ניצור אפליקציית אינטרנט שתאפשר לכם:

  1. בדף האינטרנט של התרגום, מזינים את הטקסט שרוצים לתרגם מאנגלית לצרפתית. לוחצים על תרגום.
  2. מדף האינטרנט, מתבצעת קריאה לפונקציית Cloud Run שמתחילה את ההרצה של תהליך העבודה. הטקסט שצריך לתרגם מועבר כפרמטר גם לפונקציה וגם לתהליך העבודה.
  3. הטקסט נשמר במסד נתונים של Cloud Firestore. מתבצעת קריאה ל-Cloud Translation API. התרגום שמתקבל נשמר במסד הנתונים. אפליקציית האינטרנט נפרסת באמצעות אירוח ב-Firebase ומתעדכנת בזמן אמת כדי להציג את הטקסט המתורגם.
  4. בשלב create_callback בתהליך העבודה נוצרת כתובת URL של נקודת קצה של קריאה חוזרת, שנשמרת גם במסד הנתונים של Firestore. בדף האינטרנט מוצגים עכשיו גם הלחצן אימות וגם הלחצן דחייה.
  5. תהליך העבודה מושהה עכשיו וממתין לבקשת HTTP POST מפורשת לכתובת ה-URL של נקודת הקצה של הקריאה החוזרת.
  6. אתם יכולים להחליט אם לאשר את התרגום או לדחות אותו. לחיצה על לחצן מפעילה פונקציית Cloud Run שנייה, שמפעילה בתורה את נקודת הקצה של הקריאה החוזרת שנוצרה על ידי זרימת העבודה, ומעבירה את סטטוס האישור. התפעול של תהליך העבודה יתחדש, וסטטוס האישור true או false יישמר במסד הנתונים של Firestore.

בתרשים הבא מוצגת סקירה כללית של התהליך:

תהליך עבודה עם קריאה חוזרת

מטרות

  • פריסה של אפליקציית אינטרנט.
  • יוצרים מסד נתונים ב-Firestore לאחסון בקשות תרגום.
  • פורסים פונקציות Cloud Run כדי להריץ את תהליך העבודה.
  • פורסים ומריצים תהליך עבודה כדי לתזמר את כל התהליך.

עלויות

במסמך הזה משתמשים ברכיבים הבאים של Google Cloud, והשימוש בהם כרוך בתשלום:

כדי להעריך את ההוצאות בהתאם לתחזית השימוש שלכם, אתם יכולים להיעזר במחשבון העלויות.

משתמשים חדשים של Google Cloud ? יכול להיות שאתם זכאים לתקופת ניסיון בחינם.

לפני שמתחילים

יכול להיות שהגבלות אבטחה שהוגדרו בארגון שלכם ימנעו מכם להשלים את השלבים הבאים. מידע לפתרון בעיות זמין במאמר פיתוח אפליקציות בסביבה מוגבלת. Google Cloud

  1. נכנסים לחשבון Google Cloud . אם אתם משתמשים חדשים ב- Google Cloud, צרו חשבון כדי שתוכלו להעריך את הביצועים של המוצרים שלנו בתרחישים מהעולם האמיתי. לקוחות חדשים מקבלים בחינם גם קרדיט בשווי 300$ להרצה, לבדיקה ולפריסה של עומסי העבודה.
  2. התקינו את ה-CLI של Google Cloud.

  3. אם אתם משתמשים בספק זהויות חיצוני (IdP), קודם אתם צריכים להיכנס ל-CLI של gcloud באמצעות המאגר המאוחד לניהול זהויות.

  4. כדי לאתחל את ה-CLI של gcloud, הריצו את הפקודה הבאה:

    gcloud init
  5. יוצרים או בוחרים Google Cloud פרויקט.

    תפקידים שנדרשים כדי לבחור או ליצור פרויקט

    • Select a project: כדי לבחור פרויקט לא צריך תפקיד IAM ספציפי – אפשר לבחור כל פרויקט שקיבלתם בו תפקיד.
    • יצירת פרויקט: כדי ליצור פרויקט, צריך את התפקיד Project Creator (יצירת פרויקטים) (roles/resourcemanager.projectCreator), שכולל את ההרשאה resourcemanager.projects.create. איך מקצים תפקידים
    • יוצרים Google Cloud פרויקט:

      gcloud projects create PROJECT_ID

      מחליפים את PROJECT_ID בשם של פרויקט Google Cloud שיוצרים.

    • בוחרים את הפרויקט שיצרתם: Google Cloud

      gcloud config set project PROJECT_ID

      מחליפים את PROJECT_ID בשם הפרויקט ב- Google Cloud .

  6. מוודאים שהחיוב מופעל בפרויקט Google Cloud .

  7. מפעילים את ממשקי ה-API של App Engine,‏ Cloud Build,‏ Cloud Run Functions,‏ Firestore,‏ Translation ו-Workflows:

    תפקידים שנדרשים להפעלת ממשקי API

    כדי להפעיל ממשקי API, צריך את תפקיד ה-IAM 'אדמין של Service Usage' (roles/serviceusage.serviceUsageAdmin), שכולל את ההרשאה serviceusage.services.enable. איך מקצים תפקידים

    gcloud services enable appengine.googleapis.com cloudbuild.googleapis.com cloudfunctions.googleapis.com firestore.googleapis.com translate.googleapis.com workflows.googleapis.com
  8. התקינו את ה-CLI של Google Cloud.

  9. אם אתם משתמשים בספק זהויות חיצוני (IdP), קודם אתם צריכים להיכנס ל-CLI של gcloud באמצעות המאגר המאוחד לניהול זהויות.

  10. כדי לאתחל את ה-CLI של gcloud, הריצו את הפקודה הבאה:

    gcloud init
  11. יוצרים או בוחרים Google Cloud פרויקט.

    תפקידים שנדרשים כדי לבחור או ליצור פרויקט

    • Select a project: כדי לבחור פרויקט לא צריך תפקיד IAM ספציפי – אפשר לבחור כל פרויקט שקיבלתם בו תפקיד.
    • יצירת פרויקט: כדי ליצור פרויקט, צריך את התפקיד Project Creator (יצירת פרויקטים) (roles/resourcemanager.projectCreator), שכולל את ההרשאה resourcemanager.projects.create. איך מקצים תפקידים
    • יוצרים Google Cloud פרויקט:

      gcloud projects create PROJECT_ID

      מחליפים את PROJECT_ID בשם של פרויקט Google Cloud שיוצרים.

    • בוחרים את הפרויקט שיצרתם: Google Cloud

      gcloud config set project PROJECT_ID

      מחליפים את PROJECT_ID בשם הפרויקט ב- Google Cloud .

  12. מוודאים שהחיוב מופעל בפרויקט Google Cloud .

  13. מפעילים את ממשקי ה-API של App Engine,‏ Cloud Build,‏ Cloud Run Functions,‏ Firestore,‏ Translation ו-Workflows:

    תפקידים שנדרשים להפעלת ממשקי API

    כדי להפעיל ממשקי API, צריך את תפקיד ה-IAM 'אדמין של Service Usage' (roles/serviceusage.serviceUsageAdmin), שכולל את ההרשאה serviceusage.services.enable. איך מקצים תפקידים

    gcloud services enable appengine.googleapis.com cloudbuild.googleapis.com cloudfunctions.googleapis.com firestore.googleapis.com translate.googleapis.com workflows.googleapis.com
  14. מעדכנים את הרכיבים של Google Cloud CLI:
    gcloud components update
  15. נכנסים באמצעות החשבון:
    gcloud auth login
  16. מגדירים את מזהה הפרויקט ואת מיקום ברירת המחדל שמשמשים במדריך הזה:
    export GOOGLE_CLOUD_PROJECT=PROJECT_ID
    export REGION=REGION
    gcloud config set workflows/location ${REGION}

    מחליפים את מה שכתוב בשדות הבאים:

    • PROJECT_ID: מזהה הפרויקט ב- Google Cloud . אפשר לראות את מזהה הפרויקט בדף Welcome במסוף Google Cloud .
    • REGION: המיקום הנתמך של Workflows שבחרתם.

פריסת הפונקציה הראשונה של Cloud Run

פונקציית Cloud Run הזו מפעילה את הביצוע של תהליך העבודה. הטקסט שצריך לתרגם מועבר כפרמטר גם לפונקציה וגם לתהליך העבודה.

  1. יוצרים ספרייה בשם callback-translation עם ספריות משנה בשמות invokeTranslationWorkflow,‏ translationCallbackCall ו-public:

    mkdir -p ~/callback-translation/{invokeTranslationWorkflow,translationCallbackCall,public}
  2. עוברים לספרייה invokeTranslationWorkflow:

    cd ~/callback-translation/invokeTranslationWorkflow
  3. יוצרים קובץ טקסט בשם index.js שמכיל את קוד Node.js הבא:

    const cors = require('cors')({origin: true});
    const {ExecutionsClient} = require('@google-cloud/workflows');
    const client = new ExecutionsClient();
    
    exports.invokeTranslationWorkflow = async (req, res) => {
      cors(req, res, async () => {
        const text = req.body.text;
        console.log(`Translation request for "${text}"`);
    
        const PROJECT_ID = process.env.PROJECT_ID;
        const CLOUD_REGION = process.env.CLOUD_REGION;
        const WORKFLOW_NAME = process.env.WORKFLOW_NAME;
    
        const execResponse = await client.createExecution({
          parent: client.workflowPath(PROJECT_ID, CLOUD_REGION, WORKFLOW_NAME),
          execution: {
            argument: JSON.stringify({text})
          }
        });
        console.log(`Translation workflow execution request: ${JSON.stringify(execResponse)}`);
    
        const execName = execResponse[0].name;
        console.log(`Created translation workflow execution: ${execName}`);
    
        res.set('Access-Control-Allow-Origin', '*');
        res.status(200).json({executionId: execName});
      });
    };
  4. יוצרים קובץ טקסט בשם package.json שמכיל את המטא-נתונים הבאים של npm:

    {
      "name": "launch-translation-workflow",
      "version": "0.0.1",
      "dependencies": {
        "@google-cloud/workflows": "^1.2.5",
        "cors": "^2.8.5"
      }
    }
    
  5. פורסים את הפונקציה עם טריגר HTTP ומאפשרים גישה ללא אימות:

    gcloud functions deploy invokeTranslationWorkflow \
    --region=${REGION} \
    --runtime nodejs14 \
    --entry-point=invokeTranslationWorkflow \
    --set-env-vars PROJECT_ID=${GOOGLE_CLOUD_PROJECT},CLOUD_REGION=${REGION},WORKFLOW_NAME=translation_validation \
    --trigger-http \
    --allow-unauthenticated

    יכול להיות שיחלפו כמה דקות עד שהפונקציה תופעל. לחלופין, אפשר להשתמש בממשק של Cloud Run functions במסוף Google Cloud כדי לפרוס את הפונקציה.

  6. אחרי הפריסה של הפונקציה, אפשר לאשר את המאפיין httpsTrigger.url:

    gcloud functions describe invokeTranslationWorkflow

    חשוב לשים לב לכתובת ה-URL שמוחזרת כדי שתוכלו להשתמש בה בשלב מאוחר יותר.

פריסת הפונקציה השנייה של Cloud Run

הפונקציה הזו של Cloud Run שולחת בקשת HTTP POST לנקודת הקצה של הקריאה החוזרת שנוצרה על ידי תהליך העבודה, ומעבירה סטטוס אישור שמשקף אם התרגום אומת או נדחה.

  1. עוברים לספרייה translationCallbackCall:

    cd ../translationCallbackCall
  2. יוצרים קובץ טקסט בשם index.js שמכיל את קוד Node.js הבא:

    const cors = require('cors')({origin: true});
    const fetch = require('node-fetch');
    
    exports.translationCallbackCall = async (req, res) => {
      cors(req, res, async () => {
        res.set('Access-Control-Allow-Origin', '*');
    
        const {url, approved} = req.body;
        console.log("Approved? ", approved);
        console.log("URL = ", url);
        const {GoogleAuth} = require('google-auth-library');
        const auth = new GoogleAuth();
        const token = await auth.getAccessToken();
        console.log("Token", token);
    
        try {
          const resp = await fetch(url, {
              method: 'POST',
              headers: {
                  'accept': 'application/json',
                  'content-type': 'application/json',
                  'authorization': `Bearer ${token}`
              },
              body: JSON.stringify({ approved })
          });
          console.log("Response = ", JSON.stringify(resp));
    
          const result = await resp.json();
          console.log("Outcome = ", JSON.stringify(result));
    
          res.status(200).json({status: 'OK'});
        } catch(e) {
          console.error(e);
    
          res.status(200).json({status: 'error'});
        }
      });
    };
  3. יוצרים קובץ טקסט בשם package.json שמכיל את המטא-נתונים הבאים של npm:

    {
      "name": "approve-translation-workflow",
      "version": "0.0.1",
      "dependencies": {
        "cors": "^2.8.5",
        "node-fetch": "^2.6.1",
        "google-auth-library": "^7.1.1"
      }
    }
    
  4. פורסים את הפונקציה עם טריגר HTTP ומאפשרים גישה ללא אימות:

    gcloud functions deploy translationCallbackCall \
    --region=${REGION} \
    --runtime nodejs14 \
    --entry-point=translationCallbackCall \
    --trigger-http \
    --allow-unauthenticated

    יכול להיות שיחלפו כמה דקות עד שהפונקציה תופעל. לחלופין, אפשר להשתמש בממשק של Cloud Run functions במסוף Google Cloud כדי לפרוס את הפונקציה.

  5. אחרי הפריסה של הפונקציה, אפשר לאשר את המאפיין httpsTrigger.url:

    gcloud functions describe translationCallbackCall

    חשוב לשים לב לכתובת ה-URL שמוחזרת כדי שתוכלו להשתמש בה בשלב מאוחר יותר.

פריסת תהליך העבודה

תהליך עבודה מורכב מסדרה של שלבים שמתוארים באמצעות תחביר Workflows, שאפשר לכתוב בפורמט YAML או JSON. זו ההגדרה של תהליך העבודה. אחרי שיוצרים תהליך עבודה, פורסים אותו כדי שיהיה זמין להרצה.

  1. עוברים לספרייה callback-translation:

    cd ..
  2. יוצרים קובץ טקסט בשם translation-validation.yaml עם התוכן הבא:

    main:
        params: [translation_request]
        steps:
            - log_request:
                call: sys.log
                args:
                    text: ${translation_request}
            - vars:
                assign:
                    - exec_id: ${sys.get_env("GOOGLE_CLOUD_WORKFLOW_EXECUTION_ID")}
                    - text_to_translate: ${translation_request.text}
                    - database_root: ${"projects/" + sys.get_env("GOOGLE_CLOUD_PROJECT_ID") + "/databases/(default)/documents/translations/"}
            - log_translation_request:
                call: sys.log
                args:
                    text: ${text_to_translate}
    
            - store_translation_request:
                call: googleapis.firestore.v1.projects.databases.documents.patch
                args:
                    name: ${database_root + exec_id}
                    updateMask:
                        fieldPaths: ['text']
                    body:
                        fields:
                            text:
                                stringValue: ${text_to_translate}
                result: store_translation_request_result
    
            - translate:
                call: googleapis.translate.v2.translations.translate
                args:
                    query:
                        q: ${text_to_translate}
                        target: "FR"
                        format: "text"
                        source: "EN"
                result: translation_result
            - assign_translation:
                assign:
                    - translation: ${translation_result.data.translations[0].translatedText} 
            - log_translation_result:
                call: sys.log
                args:
                    text: ${translation}
    
            - store_translated_text:
                call: googleapis.firestore.v1.projects.databases.documents.patch
                args:
                    name: ${database_root + exec_id}
                    updateMask:
                        fieldPaths: ['translation']
                    body:
                        fields:
                            translation:
                                stringValue: ${translation}
                result: store_translation_request_result   
    
            - create_callback:
                call: events.create_callback_endpoint
                args:
                    http_callback_method: "POST"
                result: callback_details
            - log_callback_details:
                call: sys.log
                args:
                    text: ${callback_details}
    
            - store_callback_details:
                call: googleapis.firestore.v1.projects.databases.documents.patch
                args:
                    name: ${database_root + exec_id}
                    updateMask:
                        fieldPaths: ['callback']
                    body:
                        fields:
                            callback:
                                stringValue: ${callback_details.url}
                result: store_callback_details_result
    
            - await_callback:
                call: events.await_callback
                args:
                    callback: ${callback_details}
                    timeout: 3600
                result: callback_request
            - assign_approval:
                assign:
                    - approved: ${callback_request.http_request.body.approved}
    
            - store_approval:
                call: googleapis.firestore.v1.projects.databases.documents.patch
                args:
                    name: ${database_root + exec_id}
                    updateMask:
                        fieldPaths: ['approved']
                    body:
                        fields:
                            approved:
                                booleanValue: ${approved}
                result: store_approval_result
    
            - return_outcome:
                return:
                    text: ${text_to_translate}
                    translation: ${translation}
                    approved: ${approved}
  3. אחרי שיוצרים את תהליך העבודה, אפשר לפרוס אותו, אבל לא להריץ אותו:

    gcloud workflows deploy translation_validation --source=translation-validation.yaml

יצירת אפליקציית אינטרנט

יוצרים אפליקציית אינטרנט שקוראת לפונקציית Cloud Functions שמפעילה את זרימת העבודה. דף האינטרנט מתעדכן בזמן אמת כדי להציג את התוצאה של בקשת התרגום.

  1. עוברים לספרייה public:

    cd public
  2. יוצרים קובץ טקסט עם שם הקובץ index.html שמכיל את תגי ה-HTML הבאים. (בשלב מאוחר יותר, תשנו את הקובץ index.html ותוסיפו את הסקריפטים של Firebase SDK).

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
    
        <title>Frenglish translation — Feature Workflows callbacks</title>
    
        <link rel="stylesheet"
            href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.42/dist/themes/base.css">
        <script type="module"
            src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.42/dist/shoelace.js"></script>
        <link rel="stylesheet" href="./style.css">
    </head>
    
    <body>
        <h1>Translate from English to French</h1>
    
        <sl-form class="form-overview">
            <sl-textarea id="text" placeholder="The quick brown fox jumps over the lazy dog."
                label="English text to translate"></sl-textarea>
            <p></p>
            <sl-button id="translateBtn" type="primary">Translate</sl-button>
            <p></p>
            <sl-alert id="translation" type="primary">
                Le rapide renard brun saute au dessus du chien paresseux.
            </sl-alert>
            <p></p>
            <div id="buttonRow" style="display: none;">
                <sl-button id="validateBtn" type="success">Validate</sl-button>
                <sl-button id="rejectBtn" type="danger">Reject</sl-button>
            </div>
            <p></p>
            <sl-alert id="validationAlert" type="success">
                <sl-icon slot="icon" name="check2-circle"></sl-icon>
                <strong>The translation has been validated</strong><br>
                Glad that you liked our translation! We'll save it in our database.
            </sl-alert>
            <sl-alert id="rejectionAlert" type="danger">
                <sl-icon slot="icon" name="exclamation-octagon"></sl-icon>
                <strong>The translation has been rejected</strong><br>
                A pity the translation isn't good! We'll do better next time!
            </sl-alert>
            <p></p>
            <sl-button id="newBtn" style="display: none;" type="primary">New translation</sl-button>
        </sl-form>
    
        <script src="https://www.gstatic.com/firebasejs/8.6.3/firebase-app.js"></script>
        <script src="https://www.gstatic.com/firebasejs/8.6.3/firebase-firestore.js"></script>
    
        <script>
            var firebaseConfig = {
                apiKey: "XXXX",
                authDomain: "XXXX",
                projectId: "XXXX",
                storageBucket: "XXXX",
                messagingSenderId: "XXXX",
                appId: "XXXX",
                measurementId: "XXXX"
            };
            // Initialize Firebase
            firebase.initializeApp(firebaseConfig);
        </script>
        <script src="./script.js" type="module"></script>
    </body>
    
    </html>
    
  3. יוצרים קובץ טקסט בשם script.js שמכיל את קוד ה-JavaScript הבא:

    document.addEventListener("DOMContentLoaded", async function (event) {
        const textArea = document.getElementById("text");
        textArea.focus();
    
        const newBtn = document.getElementById("newBtn");
        newBtn.addEventListener("sl-focus", event => {
            event.target.blur();
            window.location.reload();
        });
    
        const translationAlert = document.getElementById("translation");
        const buttonRow = document.getElementById("buttonRow");
    
        var callbackUrl = "";
    
        const validationAlert = document.getElementById("validationAlert");
        const rejectionAlert = document.getElementById("rejectionAlert");
        const validateBtn = document.getElementById("validateBtn");
        const rejectBtn = document.getElementById("rejectBtn");
    
        const translateBtn = document.getElementById("translateBtn");
        translateBtn.addEventListener("sl-focus", async event => {
            event.target.disabled = true;
            event.target.loading = true;
            textArea.disabled = true;
    
            console.log("Text to translate = ", textArea.value);
    
            const fnUrl = UPDATE_ME;
    
            try {
                console.log("Calling workflow executor function...");
                const resp = await fetch(fnUrl, {
                    method: "POST",
                    headers: {
                        "accept": "application/json",
                        "content-type": "application/json"
                    },
                    body: JSON.stringify({ text: textArea.value })
                });
                const executionResp = await resp.json();
                const executionId = executionResp.executionId.slice(-36);
                console.log("Execution ID = ", executionId);
    
                const db = firebase.firestore();
                const translationDoc = db.collection("translations").doc(executionId);
    
                var translationReceived = false;
                var callbackReceived =  false;
                var approvalReceived = false;
                translationDoc.onSnapshot((doc) => {
                    console.log("Firestore update", doc.data());
                    if (doc.data()) {
                        if ("translation" in doc.data()) {
                            if (!translationReceived) {
                                console.log("Translation = ", doc.data().translation);
                                translationReceived = true;
                                translationAlert.innerText = doc.data().translation;
                                translationAlert.open = true;
                            }
                        }
                        if ("callback" in doc.data()) {
                            if (!callbackReceived) {
                                console.log("Callback URL = ", doc.data().callback);
                                callbackReceived = true;
                                callbackUrl = doc.data().callback;
                                buttonRow.style.display = "block";
                            }
                        }
                        if ("approved" in doc.data()) {
                            if (!approvalReceived) {
                                const approved = doc.data().approved;
                                console.log("Approval received = ", approved);
                                if (approved) {
                                    validationAlert.open = true;
                                    buttonRow.style.display = "none";
                                    newBtn.style.display = "inline-block";   
                                } else {
                                    rejectionAlert.open = true;
                                    buttonRow.style.display = "none";
                                    newBtn.style.display = "inline-block";
                                }
                                approvalReceived = true;
                            }
                        }
                    }
                });
            } catch (e) {
                console.log(e);
            }
            event.target.loading = false;
        });
    
        validateBtn.addEventListener("sl-focus", async event => {
            validateBtn.disabled = true;
            rejectBtn.disabled = true;
            validateBtn.loading = true;
            validateBtn.blur();
    
            // call callback
            await callCallbackUrl(callbackUrl, true);
        });
    
        rejectBtn.addEventListener("sl-focus", async event => {
            rejectBtn.disabled = true;
            validateBtn.disabled = true;
            rejectBtn.loading = true;
            rejectBtn.blur();
    
            // call callback
            await callCallbackUrl(callbackUrl, false);
        });
    
    });
    
    async function callCallbackUrl(url, approved) {
        console.log("Calling callback URL with status = ", approved);
    
        const fnUrl = UPDATE_ME;
        try {
            const resp = await fetch(fnUrl, {
                method: "POST",
                headers: {
                    "accept": "application/json",
                    "content-type": "application/json"
                },
                body: JSON.stringify({ url, approved })
            });
            const result = await resp.json();
            console.log("Callback answer = ", result);
        } catch(e) {
            console.log(e);
        }
    }
  4. עורכים את הקובץ script.js ומחליפים את ה-placeholders‏ UPDATE_ME בכתובות ה-URL של פונקציות Cloud Run שרשמתם קודם.

    1. בשיטה translateBtn.addEventListener, מחליפים את const fnUrl = UPDATE_ME; בערך הבא:

      const fnUrl = "https://REGION-PROJECT_ID.cloudfunctions.net/invokeTranslationWorkflow";

    2. בפונקציה callCallbackUrl, מחליפים את const fnUrl = UPDATE_ME; ב:

      const fnUrl = "https://REGION-PROJECT_ID.cloudfunctions.net/translationCallbackCall";

  5. יוצרים קובץ טקסט עם שם הקובץ style.css שמכיל את הסגנונות הבאים:

    * {
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
    }
    
    body {
        margin: 20px;
    }
    
    h1, h2, h3, h4 {
        color: #0ea5e9;
    }
    

הוספת Firebase לאפליקציית האינטרנט

במדריך הזה, דף ה-HTML, סקריפט ה-JavaScript וגיליון הסגנונות של ה-CSS נפרסים כנכסים סטטיים באמצעות אירוח ב-Firebase, אבל אפשר לארח אותם בכל מקום ולהפעיל אותם באופן מקומי במחשב שלכם למטרות בדיקה.

יצירת פרויקט Firebase

לפני שמוסיפים את Firebase לאפליקציה, צריך ליצור פרויקט Firebase כדי לקשר אותו לאפליקציה.

  1. במסוף Firebase, לוחצים על יצירת פרויקט, ואז בוחרים את הפרויקט הקיים בתפריט הנפתח כדי להוסיף לו משאבי Firebase. Google Cloud

  2. לוחצים על המשך עד שמופיעה האפשרות להוסיף את Firebase.

  3. מדלגים על ההגדרה של Google Analytics בפרויקט.

  4. לוחצים על הוספת Firebase.

מערכת Firebase מקצה באופן אוטומטי משאבים לפרויקט Firebase. בסיום התהליך, תועברו לדף הסקירה הכללית של הפרויקט במסוף Firebase.

רישום האפליקציה ב-Firebase

אחרי שיש לכם פרויקט Firebase, אתם יכולים להוסיף אליו את אפליקציית האינטרנט.

  1. במרכז דף סקירת הפרויקט, לוחצים על סמל האינטרנט (</>) כדי להפעיל את תהליך ההגדרה.

  2. מזינים כינוי לאפליקציה.

    הנתונים האלה גלויים רק לכם במסוף Firebase.

  3. מדלגים על הגדרת אירוח ב-Firebase בשלב הזה.

  4. לוחצים על Register app (רישום האפליקציה) וממשיכים אל המסוף.

הפעלת Cloud Firestore

אפליקציית האינטרנט משתמשת ב-Cloud Firestore כדי לקבל ולשמור נתונים. צריך להפעיל את Cloud Firestore.

  1. בקטע Build (פיתוח) במסוף Firebase, לוחצים על Firestore Database (מסד נתונים של Firestore).

    (יכול להיות שתצטרכו להרחיב קודם את חלונית הניווט הימנית כדי לראות את הקטע יצירה).

  2. בחלונית Cloud Firestore, לוחצים על יצירת מסד נתונים.

  3. בוחרים באפשרות הפעלה במצב בדיקה, באמצעות כלל אבטחה כמו זה שמופיע בהמשך:

    rules_version = '2';
    service cloud.firestore {
    match /databases/{database}/documents {
      match /{document=**} {
        allow read, write;
      }
    }
    }
  4. קוראים את כתב הוויתור לגבי כללי האבטחה ולוחצים על הבא.

  5. הגדרת המיקום שבו הנתונים של Cloud Firestore מאוחסנים. אפשר לאשר את ברירת המחדל או לבחור אזור שקרוב אליכם.

  6. לוחצים על הפעלה כדי להקצות את Firestore.

הוספת Firebase SDK והפעלת Firebase

‫Firebase מספקת ספריות JavaScript לרוב מוצרי Firebase. לפני שמשתמשים באירוח ב-Firebase, צריך להוסיף את Firebase SDKs לאפליקציית האינטרנט.

  1. כדי להפעיל את Firebase באפליקציה, צריך לספק את הגדרות הפרויקט של Firebase באפליקציה.
    1. במסוף Firebase, עוברים אל Project settings (הגדרות הפרויקט) .
    2. בחלונית האפליקציות שלך, בוחרים את האפליקציה.
    3. בחלונית SDK setup and configuration (הגדרה והגדרה של SDK), כדי לטעון ספריות של Firebase SDK מ-CDN, בוחרים באפשרות CDN.
    4. מעתיקים את קטע הקוד לקובץ index.html בתחתית התג <body>, ומחליפים את ערכי ה-placeholder XXXX.
  2. מתקינים את Firebase JavaScript SDK.

    1. אם עדיין אין לכם קובץ package.json, יוצרים אותו על ידי הרצת הפקודה הבאה מהספרייה callback-translation:

      npm init
    2. מריצים את הפקודה הבאה כדי להתקין את חבילת firebase npm ולשמור אותה בקובץ package.json:

      npm install firebase

אתחול ופריסה של הפרויקט

כדי לקשר את קובצי הפרויקט המקומיים לפרויקט Firebase, צריך לאתחל את הפרויקט. לאחר מכן תוכלו לפרוס את אפליקציית האינטרנט.

  1. מהספרייה callback-translation, מריצים את הפקודה הבאה:

    firebase init
  2. בוחרים באפשרות Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys.

  3. בוחרים להשתמש בפרויקט קיים ומזינים את מזהה הפרויקט.

  4. אישור public כספריית הבסיס הציבורית שמוגדרת כברירת מחדל.

  5. בוחרים באפשרות להגדיר אפליקציה של דף יחיד.

  6. לא צריך להגדיר גרסאות build ופריסות אוטומטיות באמצעות GitHub.

  7. בהודעה File public/index.html already exists. Overwrite?, מקלידים לא.

  8. עוברים לספרייה public:

    cd public
  9. מהספרייה public, מריצים את הפקודה הבאה כדי לפרוס את הפרויקט באתר:

    firebase deploy --only hosting

בדיקת אפליקציית האינטרנט באופן מקומי

‫אירוח ב-Firebase מאפשר לכם לראות ולבדוק שינויים באופן מקומי ולקיים אינטראקציה עם משאבי פרויקט של בק-אנד שנוצרו באמצעות אמולטור. כשמשתמשים ב-firebase serve, האפליקציה מקיימת אינטראקציה עם קצה עורפי מדומה כדי לארח את התוכן וההגדרות, אבל עם הקצה העורפי האמיתי לכל שאר משאבי הפרויקט. במדריך הזה אפשר להשתמש ב-firebase serve, אבל לא מומלץ להשתמש בו כשמבצעים בדיקות מקיפות יותר.

  1. מהספרייה public, מריצים את הפקודה הבאה:

    firebase serve
  2. פותחים את אפליקציית האינטרנט בכתובת ה-URL המקומית שמוחזרת (בדרך כלל http://localhost:5000).

  3. מזינים טקסט באנגלית ולוחצים על תרגום.

    יוצג תרגום של הטקסט בצרפתית.

  4. עכשיו אפשר ללחוץ על אימות או על דחייה.

    במסד הנתונים של Firestore, אפשר לאמת את התוכן. הוא אמור להיראות כך:

    approved: true
    callback: "https://workflowexecutions.googleapis.com/v1/projects/26811016474/locations/us-central1/workflows/translation_validation/executions/68bfce75-5f62-445f-9cd5-eda23e6fa693/callbacks/72851c97-6bb2-45e3-9816-1e3dcc610662_1a16697f-6d90-478d-9736-33190bbe222b"
    text: "The quick brown fox jumps over the lazy dog."
    translation: "Le renard brun rapide saute par-dessus le chien paresseux."
    

    הסטטוס approved הוא true או false, בהתאם לאישור או לדחייה של התרגום.

כל הכבוד! יצרתם תהליך עבודה לתרגום עם מעורבות אנושית שמשתמש בקריאות חוזרות (callback) של Workflows.

הסרת המשאבים

אם יצרתם פרויקט חדש בשביל המדריך הזה, תמחקו את הפרויקט. אם השתמשתם בפרויקט קיים ואתם רוצים לשמור אותו בלי השינויים שנוספו במדריך הזה, תצטרכו למחוק את המשאבים שנוצרו לצורך המדריך.

מחיקת הפרויקט

הדרך הקלה ביותר לבטל את החיוב היא למחוק את הפרויקט שיצרתם בשביל המדריך הזה.

כדי למחוק את הפרויקט:

  1. במסוף Google Cloud , נכנסים לדף Manage resources.

    כניסה לדף Manage resources

  2. ברשימת הפרויקטים, בוחרים את הפרויקט שרוצים למחוק ולוחצים על Delete.
  3. כדי למחוק את הפרויקט, כותבים את מזהה הפרויקט בתיבת הדו-שיח ולוחצים על Shut down.

מחיקת משאבי הדרכה

  1. מסירים את הגדרת ברירת המחדל של ה-CLI של gcloud שהוספתם במהלך ההגדרה של המדריך:

    gcloud config unset workflows/location
  2. מחיקת תהליך העבודה שנוצר במדריך הזה:

    gcloud workflows delete WORKFLOW_NAME
  3. מוחקים את הפונקציות של Cloud Run שיצרתם במדריך הזה:

    gcloud functions delete FUNCTION_NAME

    אפשר גם למחוק פונקציות Cloud Run ממסוףGoogle Cloud .

המאמרים הבאים