コールバックを使用して人間参加型ワークフローを作成する


このチュートリアルでは、入力(ループ内の人間)を待機し、Firestore データベース、2 つの Cloud Functions の関数、Cloud Translation API、Firebase SDK を使用してリアルタイムに更新するウェブページを接続する翻訳ワークフローを作成する方法について説明します。

ワークフローを使用すると、そのエンドポイントに HTTP リクエストが到達するのを待って、ワークフローの実行を後で再開するコールバック エンドポイント(Webhook)をサポートできます。このチュートリアルでは、ワークフローはテキストの翻訳を拒否または認める入力を待機しますが、外部プロセスを待機することもできます。詳しくは、コールバックを使用して待機するをご覧ください。

アーキテクチャ

このチュートリアルでは、次のことを行えるウェブアプリを作成します。

  1. 翻訳ウェブページで、英語からフランス語に翻訳するテキストを入力し、[翻訳] をクリックします。
  2. ウェブページから、ワークフローの実行を開始する Cloud Function が呼び出されます。翻訳対象のテキストは、関数とワークフローの両方にパラメータとして渡されます。
  3. テキストは Cloud Firestore データベースに保存されます。Cloud Translation API が呼び出されます。返された翻訳がデータベースに保存されます。このウェブアプリは、Firebase Hosting を使用してデプロイされ、翻訳されたテキストを表示するためにリアルタイムで更新されます。
  4. ワークフローの create_callback ステップは、コールバック エンドポイント URL を作成します。この URL も Firestore データベースに保存されます。ウェブページに、[検証] ボタンと [拒否] ボタンの両方が表示されるようになりました。
  5. ワークフローは一時停止し、コールバック エンドポイント URL に対する明示的な HTTP POST リクエストを待機します。
  6. 翻訳を認めるか拒否するかを決定できます。ボタンをクリックすると、第二の Cloud Functions の関数を呼び出して、ワークフローで作成されたコールバック エンドポイントが呼び出され、承認ステータスが渡されます。ワークフローが実行を再開し、承認ステータスの true または false を Firestore データベースに保存します。

次の図では、プロセスの概要を示します。

コールバックを使用するワークフロー

目標

  • ウェブアプリをデプロイする。
  • 翻訳リクエストを格納する Firestore データベースを作成する。
  • Cloud Functions をデプロイしてワークフローを実行する。
  • ワークフローをデプロイして実行し、プロセス全体をオーケストレートする。

費用

このドキュメントでは、Google Cloud の次の課金対象のコンポーネントを使用します。

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。 新しい Google Cloud ユーザーは無料トライアルをご利用いただける場合があります。

始める前に

組織で定義されているセキュリティの制約により、次の手順を完了できない場合があります。トラブルシューティング情報については、制約のある Google Cloud 環境でアプリケーションを開発するをご覧ください。

  1. Google Cloud アカウントにログインします。Google Cloud を初めて使用する場合は、アカウントを作成して、実際のシナリオでの Google プロダクトのパフォーマンスを評価してください。新規のお客様には、ワークロードの実行、テスト、デプロイができる無料クレジット $300 分を差し上げます。
  2. Google Cloud CLI をインストールします。
  3. gcloud CLI を初期化するには:

    gcloud init
  4. Google Cloud プロジェクトを作成または選択します

    • Google Cloud プロジェクトを作成します。

      gcloud projects create PROJECT_ID

      PROJECT_ID は、作成する Google Cloud プロジェクトの名前に置き換えます。

    • 作成した Google Cloud プロジェクトを選択します。

      gcloud config set project PROJECT_ID

      PROJECT_ID は、実際の Google Cloud プロジェクト名に置き換えます。

  5. Google Cloud プロジェクトで課金が有効になっていることを確認します

  6. App Engine, Cloud Build, Cloud Functions, Firestore, Translation, and Workflows API を有効にします。

    gcloud services enable appengine.googleapis.comcloudbuild.googleapis.comcloudfunctions.googleapis.comfirestore.googleapis.comtranslate.googleapis.comworkflows.googleapis.com
  7. Google Cloud CLI をインストールします。
  8. gcloud CLI を初期化するには:

    gcloud init
  9. Google Cloud プロジェクトを作成または選択します

    • Google Cloud プロジェクトを作成します。

      gcloud projects create PROJECT_ID

      PROJECT_ID は、作成する Google Cloud プロジェクトの名前に置き換えます。

    • 作成した Google Cloud プロジェクトを選択します。

      gcloud config set project PROJECT_ID

      PROJECT_ID は、実際の Google Cloud プロジェクト名に置き換えます。

  10. Google Cloud プロジェクトで課金が有効になっていることを確認します

  11. App Engine, Cloud Build, Cloud Functions, Firestore, Translation, and Workflows API を有効にします。

    gcloud services enable appengine.googleapis.comcloudbuild.googleapis.comcloudfunctions.googleapis.comfirestore.googleapis.comtranslate.googleapis.comworkflows.googleapis.com
  12. Google Cloud CLI のコンポーネントを更新します。
    gcloud components update
  13. アカウントを使用してログインします。
    gcloud auth login
  14. このチュートリアルで使用するプロジェクト ID とデフォルトのロケーションを設定します。
    export GOOGLE_CLOUD_PROJECT=PROJECT_ID
    export REGION=REGION
    gcloud config set workflows/location ${REGION}
    

    以下を置き換えます。

    • PROJECT_ID: Google Cloud プロジェクト ID。 プロジェクト ID は、Google Cloud コンソールの [ようこそ] ページで確認できます。
    • REGION: 選択したサポート対象の Workflows のロケーション

第一の Cloud Function をデプロイする

この Cloud Functions の関数は、ワークフローの実行を開始します。翻訳対象のテキストは、関数とワークフローの両方にパラメータとして渡されます。

  1. callback-translation という名前のディレクトリを作成し、さらに invokeTranslationWorkflowtranslationCallbackCallpublic という名前のサブディレクトリを作成します。

    mkdir -p ~/callback-translation/{invokeTranslationWorkflow,translationCallbackCall,public}
    
  2. invokeTranslationWorkflow ディレクトリに移動します。

    cd ~/callback-translation/invokeTranslationWorkflow
    
  3. 次の Node.js コードを含むテキスト ファイルを index.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. 次の npm メタデータを含むテキスト ファイルを package.json という名前で作成します。

    {
      "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
    

    関数のデプロイには数分かかる場合があります。また、Google Cloud コンソールで Cloud Functions インターフェースを使用して関数をデプロイできます。

  6. 関数がデプロイされたら、httpsTrigger.url プロパティを確認できます。

    gcloud functions describe invokeTranslationWorkflow
    

    後のステップで使用できるように、この URL をメモします。

第二の Cloud Functions の関数をデプロイする

この Cloud Functions の関数は、ワークフローによって作成されたコールバック エンドポイントに HTTP POST リクエストを行い、翻訳が検証されたか拒否されたかを示す承認ステータスを渡します。

  1. translationCallbackCall ディレクトリに移動します。

    cd ../translationCallbackCall
    
  2. 次の Node.js コードを含むテキスト ファイルを index.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. 次の npm メタデータを含むテキスト ファイルを package.json という名前で作成します。

    {
      "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
    

    関数のデプロイには数分かかる場合があります。また、Google Cloud コンソールで Cloud Functions インターフェースを使用して関数をデプロイできます。

  5. 関数がデプロイされたら、httpsTrigger.url プロパティを確認できます。

    gcloud functions describe translationCallbackCall
    

    後のステップで使用できるように、この URL をメモします。

ワークフローをデプロイする

ワークフローは、ワークフロー構文で記述された一連のステップで構成され、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. 次の HTML マークアップを含むテキスト ファイルを index.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. 次の JavaScript コードを含むテキスト ファイルを script.js という名前で作成します。

    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 ファイルを編集して、UPDATE_ME プレースホルダをメモした Cloud Functions の関数の URL に置き換えます。

    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 を追加する

このチュートリアルでは、Firebase Hosting を使用して HTML ページ、JavaScript スクリプト、CSS スタイルシートを静的アセットとしてデプロイしますが、テスト用には任意のマシンでローカルにホストできます。

Firebase プロジェクトを作成する

アプリに Firebase を追加する前に、アプリに接続する Firebase プロジェクトを作成します。

  1. Firebase コンソールで [プロジェクトを作成] をクリックし、プルダウン メニューから既存の Google Cloud プロジェクトを選択して、Firebase リソースをそのプロジェクトに追加します。

  2. Firebase を追加するオプションが表示されるまで、[続行] をクリックします。

  3. プロジェクトに対する Google アナリティクスの設定はスキップします。

  4. [Firebase を追加] をクリックします。

Firebase プロジェクトのリソースが自動的にプロビジョニングされます。処理が完了すると、Firebase コンソールにプロジェクトの概要ページが表示されます。

アプリを Firebase に登録する

Firebase プロジェクトを作成したら、プロジェクトにウェブアプリを追加できます。

  1. プロジェクトの概要ページの中央で、[ウェブ] アイコン(</>)をクリックして設定ワークフローを起動します。

  2. アプリのニックネームを入力します。

    これは Firebase コンソールにのみ表示されます。

  3. ひとまず、Firebase Hosting の設定はスキップします。

  4. [アプリを登録] をクリックしてコンソールに進みます。

Cloud Firestore の有効化

このウェブアプリは Cloud Firestore を使用してデータを受信して保存します。Cloud Firestore を有効にする必要があります。

  1. Firebase コンソールの [構築] セクションで、[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 は、ほとんどの Firebase プロダクトに対応する JavaScript ライブラリを提供しています。Firebase Hosting を使用する前に、Firebase SDK をウェブアプリに追加する必要があります。

  1. アプリで Firebase を初期化するには、アプリの Firebase プロジェクト構成を提供する必要があります。
    1. Firebase コンソールで、[プロジェクトの設定]()に移動します。
    2. [マイアプリ] ペインでアプリを選択します。
    3. [SDK の設定と構成] ペインで、CDN から Firebase SDK ライブラリを読み込むために、[CDN] を選択します。
    4. index.html ファイルの <body> タグの末尾にこのスニペットをコピーし、XXXX プレースホルダ値を置き換えます。
  2. Firebase JavaScript SDK をインストールします。

    1. package.json ファイルがない場合は、callback-translation ディレクトリから次のコマンドを実行して作成します。

      npm init
    2. 次のコマンドを実行して firebase npm パッケージをインストールし、package.json ファイルに保存します。

      npm install --save firebase

プロジェクトを初期化してデプロイする

ローカル プロジェクト ファイルを Firebase プロジェクトに接続するには、プロジェクトを初期化する必要があります。その後、ウェブアプリをデプロイできます。

  1. 任意の callback-translation ディレクトリから次のコマンドを実行します。

    firebase init
  2. [Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys] の設定を選択します。

  3. 既存のプロジェクトを使用することを選択し、プロジェクト ID を入力します。

  4. デフォルトの公開ルート ディレクトリとして public を受け入れます。

  5. 単一ページのアプリの構成を行います。

  6. GitHub を使用して自動ビルドとデプロイの設定をスキップする。

  7. File public/index.html already exists. Overwrite?」のメッセージには、「No」と入力します。

  8. public ディレクトリに移動します。

    cd public
  9. public ディレクトリから、次のコマンドを実行してプロジェクトをサイトにデプロイします。

    firebase deploy --only hosting

ウェブアプリをローカルでテストする

Firebase Hosting を使用すると、変更をローカルで確認およびテストし、エミュレートされたバックエンド プロジェクトを操作できるようになります。firebase serve を使用する場合、アプリはホストするコンテンツと構成をエミュレートしたバックエンドとやり取りしますが、他のすべてのプロジェクト リソースについては実際のバックエンドを使用します。このチュートリアルでは、firebase serve を使用できますが、より広範囲のテストを行う場合はおすすめしません。

  1. 任意の public ディレクトリから次のコマンドを実行します。

    firebase serve
  2. 返されたローカル URL(通常は http://localhost:5000)でウェブアプリを開きます。

  3. 英語のテキストを入力し、[Translate] をクリックします。

    フランス語の翻訳テキストが表示されます。

  4. ここで、[Validate] または [Reject] をクリックします。

    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 になります。

これで完了です。Workflows コールバックを使用する人間参加型翻訳ワークフローが作成されました。

クリーンアップ

このチュートリアル用に新規プロジェクトを作成した場合は、そのプロジェクトを削除します。既存のプロジェクトを使用し、このチュートリアルで変更を加えずに残す場合は、チュートリアル用に作成したリソースを削除します。

プロジェクトの削除

課金をなくす最も簡単な方法は、チュートリアル用に作成したプロジェクトを削除することです。

プロジェクトを削除するには:

  1. Google Cloud コンソールで、[リソースの管理] ページに移動します。

    [リソースの管理] に移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

チュートリアル リソースの削除

  1. チュートリアルの設定時に追加した gcloud CLI のデフォルト構成を削除します。

    gcloud config unset workflows/location
    
  2. このチュートリアルで作成したワークフローを削除します。

    gcloud workflows delete WORKFLOW_NAME
    
  3. このチュートリアルで作成した Cloud Functions の関数を削除します。

    gcloud functions delete FUNCTION_NAME
    

    Google Cloud Console から Cloud Functions を削除することもできます。

次のステップ