Criar um fluxo de trabalho human-in-the-loop usando callbacks


Neste tutorial, mostramos como criar um fluxo de trabalho de tradução que aguarda sua entrada (human-in-the-loop) e conecta um banco de dados do Firestore, duas funções do Cloud Functions, a API Cloud Translation e uma página da Web que usa o SDK do Firebase para atualizar em tempo real.

Com o Workflows, é possível oferecer suporte a um endpoint de callback (ou webhook) que aguarda a chegada de solicitações HTTP nesse endpoint, retomando a execução do fluxo de trabalho posteriormente. Nesse caso, o fluxo de trabalho aguarda a rejeição da entrada ou a validação da tradução de algum texto, mas também pode aguardar um processo externo. Para mais informações, consulte Aguardar usando callbacks.

Arquitetura

Neste tutorial, criamos um app da Web que permite fazer o seguinte:

  1. Na página de tradução da Web, digite o texto que você quer traduzir do inglês para o francês. Clique em Traduzir.
  2. Na página da Web, uma função do Cloud é chamada para iniciar a execução do fluxo de trabalho. O texto a ser traduzido é transmitido como um parâmetro para a função e o fluxo de trabalho.
  3. O texto é salvo em um banco de dados do Cloud Firestore. A API Cloud Translation é chamada. A tradução retornada é armazenada no banco de dados. O app da Web é implantado usando o Firebase Hosting e as atualizações em tempo real para exibir o texto traduzido.
  4. A etapa create_callback no fluxo de trabalho cria um URL de endpoint de callback que também é salvo no banco de dados do Firestore. A página da Web exibe os botões Validar e Rejeitar.
  5. O fluxo de trabalho está pausado e aguarda uma solicitação HTTP POST explícita para o URL do endpoint de callback.
  6. É possível validar ou rejeitar a tradução. Ao clicar em um botão, é chamada uma segunda função do Cloud que, por sua vez, chama o endpoint de callback criado pelo fluxo de trabalho, transmitindo o status de aprovação. O fluxo de trabalho retoma a execução e salva um status de aprovação de true ou false no banco de dados do Firestore.

Este diagrama apresenta uma visão geral do processo:

Fluxo de trabalho com callback

Objetivos

  • Implantar um app da Web.
  • Criar um banco de dados do Firestore para armazenar solicitações de tradução.
  • Implantar o Cloud Functions para executar o fluxo de trabalho.
  • Implantar e executar um fluxo de trabalho para orquestrar todo o processo.

Custos

Neste documento, você usará os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços. Novos usuários do Google Cloud podem estar qualificados para uma avaliação gratuita.

Antes de começar

As restrições de segurança definidas pela sua organização podem impedir que você conclua as etapas a seguir. Para informações sobre solução de problemas, consulte Desenvolver aplicativos em um ambiente restrito do Google Cloud.

  1. Faça login na sua conta do Google Cloud. Se você começou a usar o Google Cloud agora, crie uma conta para avaliar o desempenho de nossos produtos em situações reais. Clientes novos também recebem US$ 300 em créditos para executar, testar e implantar cargas de trabalho.
  2. Instale a CLI do Google Cloud.
  3. Para inicializar a CLI gcloud, execute o seguinte comando:

    gcloud init
  4. Crie ou selecione um projeto do Google Cloud.

    • Crie um projeto do Google Cloud:

      gcloud projects create PROJECT_ID

      Substitua PROJECT_ID por um nome para o projeto do Google Cloud que você está criando.

    • Selecione o projeto do Google Cloud que você criou:

      gcloud config set project PROJECT_ID

      Substitua PROJECT_ID pelo nome do projeto do Google Cloud.

  5. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  6. Ative as APIs App Engine, Cloud Build, Cloud Functions, Firestore, Translation, and Workflows :

    gcloud services enable appengine.googleapis.com cloudbuild.googleapis.com cloudfunctions.googleapis.com firestore.googleapis.com translate.googleapis.com workflows.googleapis.com
  7. Instale a CLI do Google Cloud.
  8. Para inicializar a CLI gcloud, execute o seguinte comando:

    gcloud init
  9. Crie ou selecione um projeto do Google Cloud.

    • Crie um projeto do Google Cloud:

      gcloud projects create PROJECT_ID

      Substitua PROJECT_ID por um nome para o projeto do Google Cloud que você está criando.

    • Selecione o projeto do Google Cloud que você criou:

      gcloud config set project PROJECT_ID

      Substitua PROJECT_ID pelo nome do projeto do Google Cloud.

  10. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  11. Ative as APIs App Engine, Cloud Build, Cloud Functions, Firestore, Translation, and Workflows :

    gcloud services enable appengine.googleapis.com cloudbuild.googleapis.com cloudfunctions.googleapis.com firestore.googleapis.com translate.googleapis.com workflows.googleapis.com
  12. Atualize os componentes da CLI do Google Cloud:
    gcloud components update
  13. Faça login usando sua conta:
    gcloud auth login
  14. Defina o ID do projeto e o local padrão usados neste tutorial:
    export GOOGLE_CLOUD_PROJECT=PROJECT_ID
    export REGION=REGION
    gcloud config set workflows/location ${REGION}
    

    Substitua:

    • PROJECT_ID: é seu ID do projeto no Google Cloud. Encontre o ID do projeto na página de Boas-vindas do console do Google Cloud.
    • REGION: o local compatível com o Workflows de sua escolha.

Implantar a primeira função do Cloud

Esta função do Cloud inicia a execução do fluxo de trabalho. O texto a ser traduzido é transmitido como um parâmetro para a função e o fluxo de trabalho.

  1. Crie um diretório chamado callback-translation com subdiretórios chamados invokeTranslationWorkflow, translationCallbackCall e public:

    mkdir -p ~/callback-translation/{invokeTranslationWorkflow,translationCallbackCall,public}
    
  2. Altere para o diretório invokeTranslationWorkflow:

    cd ~/callback-translation/invokeTranslationWorkflow
    
  3. Crie um arquivo de texto com o nome index.js e o seguinte código 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. Crie um arquivo de texto com o nome package.json e os seguintes metadados npm:

    {
      "name": "launch-translation-workflow",
      "version": "0.0.1",
      "dependencies": {
        "@google-cloud/workflows": "^1.2.5",
        "cors": "^2.8.5"
      }
    }
    
  5. Implante a função com um gatilho HTTP e permita acesso não autenticado:

    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
    

    A função pode levar alguns minutos para ser implantada. Se preferir, use a interface do Cloud Functions no Console do Google Cloud para implantar a função.

  6. Depois que a função for implantada, confirme a propriedade httpsTrigger.url:

    gcloud functions describe invokeTranslationWorkflow
    

    Anote o URL retornado para usá-lo em uma etapa posterior.

Implantar a segunda função do Cloud

Essa função do Cloud faz uma solicitação HTTP POST para o endpoint de callback criado pelo fluxo de trabalho, passando um status de aprovação que reflete se a tradução é validada ou rejeitada.

  1. Altere para o diretório translationCallbackCall:

    cd ../translationCallbackCall
    
  2. Crie um arquivo de texto com o nome index.js e o seguinte código 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. Crie um arquivo de texto com o nome package.json e os seguintes metadados 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. Implante a função com um gatilho HTTP e permita acesso não autenticado:

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

    A função pode levar alguns minutos para ser implantada. Se preferir, use a interface do Cloud Functions no Console do Google Cloud para implantar a função.

  5. Depois que a função for implantada, confirme a propriedade httpsTrigger.url:

    gcloud functions describe translationCallbackCall
    

    Anote o URL retornado para usá-lo em uma etapa posterior.

Implantar o fluxo de trabalho

Um fluxo de trabalho é composto por uma série de etapas descritas usando a sintaxe dos fluxos de trabalho, que pode ser escrita em formato YAML ou JSON. Essa é a definição do fluxo de trabalho. Depois de criar um fluxo de trabalho, implante-o para que ele fique disponível para execução.

  1. Altere para o diretório callback-translation:

    cd ..
    
  2. Crie um arquivo de texto com o nome de arquivo translation-validation.yaml com o seguinte conteúdo:

    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. Depois de criar o fluxo de trabalho, implante-o, mas não execute-o:

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

Criar seu app da Web

Crie um app da Web que chame uma função do Cloud que inicie a execução do fluxo de trabalho. A página da Web é atualizada em tempo real para exibir o resultado da solicitação de tradução.

  1. Altere para o diretório public:

    cd public
    
  2. Crie um arquivo de texto com o nome index.html e a seguinte marcação HTML. Em uma etapa posterior, você modificará o arquivo index.html e adicionará os scripts do SDK do Firebase.

    <!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. Crie um arquivo de texto com o nome de arquivo script.js que contém o seguinte código 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. Edite o arquivo script.js, substituindo os marcadores UPDATE_ME pelos URLs do Cloud Function anteriores.

    1. No método translateBtn.addEventListener, substitua const fnUrl = UPDATE_ME; por:

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

    2. Na função callCallbackUrl, substitua const fnUrl = UPDATE_ME; por:

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

  5. Crie um arquivo de texto com o nome style.css e os seguintes estilos em cascata:

    * {
        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;
    }
    

Adicionar o Firebase ao seu app da Web

Neste tutorial, a página HTML, o script JavaScript e a folha de estilo CSS são implantados como recursos estáticos usando o Firebase Hosting, mas podem ser hospedados em qualquer lugar e veiculados localmente em sua própria máquina para fins de teste.

criar um projeto do Firebase

Antes de adicionar o Firebase ao seu projeto de app, é preciso criar um projeto do Firebase para ser conectado ao app.

  1. No Console do Firebase, clique em Criar um projeto e selecione o projeto atual do Google Cloud no menu suspenso para adicionar recursos do Firebase a ele.

  2. Clique em Continuar até ver a opção de adicionar o Firebase.

  3. Pule a configuração do Google Analytics para o projeto.

  4. Clique em Adicionar Firebase:

O Firebase provisiona recursos automaticamente para seu projeto. Quando o processo for concluído, você será direcionado para a página de visão geral do seu projeto no Console do Firebase.

Registrar o app no Firebase

Depois de criar um projeto do Firebase, é possível adicionar seu app da Web a ele.

  1. No centro da página de visão geral do projeto, clique no ícone Web (</>) para iniciar o fluxo de trabalho de configuração.

  2. Dê um apelido para o app.

    Ele só é visível para você no Console do Firebase.

  3. Ignore a configuração do Firebase Hosting por enquanto.

  4. Clique em Registrar app e passe para o Console.

Ativar o Cloud Firestore

O app da Web usa o Cloud Firestore para receber e salvar dados. Será necessário ativar o Cloud Firestore.

  1. Na seção Build do Console do Firebase, clique em Firestore Database.

    Talvez seja necessário expandir o painel de navegação à esquerda para ver a seção Build.

  2. No painel do Cloud Firestore, clique em Criar banco de dados.

  3. Selecione Iniciar no modo de teste usando uma regra de segurança, como a seguinte:

    rules_version = '2';
    service cloud.firestore {
    match /databases/{database}/documents {
      match /{document=**} {
        allow read, write;
      }
    }
    }
    
  4. Clique em Próxima depois de ler o aviso sobre as regras de segurança.

  5. Defina o local em que os dados do Cloud Firestore são armazenados. É possível aceitar o padrão ou escolher uma região próxima a você.

  6. Clique em Ativar para provisionar o Firestore.

Adicionar o SDK do Firebase e inicializar o Firebase

O Firebase fornece bibliotecas JavaScript para a maioria dos produtos do Firebase. Antes de usar o Firebase Hosting, você precisa adicionar os SDKs do Firebase ao seu app da Web.

  1. Para inicializar o Firebase no seu aplicativo, é preciso fornecer a configuração do projeto do Firebase do seu aplicativo.
    1. No Console do Firebase, acesse as Configurações do projeto .
    2. Selecione o app no painel Seus apps.
    3. No painel Configuração do SDK, selecione CDN para carregar as bibliotecas do SDK do Firebase no CDN.
    4. Copie o snippet para o arquivo index.html na parte inferior da tag <body>, substituindo os valores do marcador XXXX.
  2. Instale o SDK do Firebase para JavaScript.

    1. Se você ainda não tiver um arquivo package.json, crie um executando o seguinte comando no diretório callback-translation:

      npm init
    2. Instale o pacote npm firebase e salve-o no arquivo package.json executando:

      npm install --save firebase

Inicializar e implantar o projeto

Para conectar os arquivos do projeto local ao seu projeto do Firebase, inicialize o projeto. Em seguida, implante o app da Web.

  1. No diretório callback-translation, execute o seguinte comando:

    firebase init
  2. Selecione a opção Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys.

  3. Escolha usar um projeto atual e insira o ID do projeto.

  4. Aceite public como o diretório raiz público padrão.

  5. Escolha configurar um app de página única.

  6. Pule a configuração de implantações automáticas com o GitHub.

  7. No prompt File public/index.html already exists. Overwrite?, digite Não.

  8. Altere para o diretório public:

    cd public
  9. No diretório public, execute o seguinte comando para implantar seu projeto no seu local:

    firebase deploy --only hosting

Testar o app da Web localmente

O Firebase Hosting permite visualizar e testar alterações localmente e interagir com recursos de projetos de back-end emulados. Ao usar firebase serve, seu app interage com um back-end emulado para seu conteúdo de hospedagem e sua configuração, mas interage com seu back-end real para todos os outros recursos do projeto. Neste tutorial, é possível usar firebase serve, mas isso não é recomendado para testes mais abrangentes.

  1. No diretório public, execute o seguinte comando:

    firebase serve
  2. Abra seu app da Web no URL local retornado (geralmente http://localhost:5000).

  3. Digite um texto em inglês e clique em Traduzir.

    Será exibida uma tradução do texto em francês.

  4. Agora clique em Validar ou Rejeitar.

    No banco de dados do Firestore, é possível verificar o conteúdo. A visualização será parecida com esta:

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

    O status approved é true ou false, dependendo de você ter validado ou rejeitado a tradução.

Parabéns! Você criou um fluxo de trabalho de tradução human-in-the-loop que usa callbacks do Workflows.

Limpar

Se você criou um novo projeto para este tutorial, exclua o projeto. Se você usou um projeto atual e quer mantê-lo sem as alterações incluídas neste tutorial, exclua os recursos criados para o tutorial.

Exclua o projeto

O jeito mais fácil de evitar cobranças é excluindo o projeto que você criou para o tutorial.

Para excluir o projeto:

  1. No Console do Google Cloud, acesse a página Gerenciar recursos.

    Acessar "Gerenciar recursos"

  2. Na lista de projetos, selecione o projeto que você quer excluir e clique em Excluir .
  3. Na caixa de diálogo, digite o ID do projeto e clique em Encerrar para excluí-lo.

Excluir recursos do tutorial

  1. Remova a configuração padrão da CLI gcloud que você adicionou durante a configuração do tutorial:

    gcloud config unset workflows/location
    
  2. Exclua o fluxo de trabalho criado neste tutorial:

    gcloud workflows delete WORKFLOW_NAME
    
  3. Excluir as funções do Cloud criadas neste tutorial:

    gcloud functions delete FUNCTION_NAME
    

    Também é possível excluir o Cloud Functions do Console do Google Cloud.

A seguir