Membuat alur kerja yang memerlukan interaksi manusia menggunakan callback

Tutorial ini menunjukkan cara membuat alur kerja terjemahan yang menunggu input Anda—manusia dalam loop—dan yang menghubungkan database Firestore, dua fungsi Cloud Run, Cloud Translation API, dan halaman web yang menggunakan Firebase SDK untuk memperbarui secara real time.

Dengan Workflows, Anda dapat mendukung endpoint callback (atau webhook) yang menunggu permintaan HTTP tiba di endpoint tersebut, lalu melanjutkan eksekusi alur kerja di lain waktu. Dalam hal ini, alur kerja menunggu input Anda untuk menolak atau memvalidasi terjemahan beberapa teks, tetapi alur kerja juga dapat menunggu proses eksternal. Untuk mengetahui informasi selengkapnya, lihat Menunggu menggunakan callback.

Arsitektur

Tutorial ini membuat aplikasi web yang memungkinkan Anda melakukan hal berikut:

  1. Di halaman web terjemahan, masukkan teks yang ingin diterjemahkan dari bahasa Inggris ke bahasa Prancis. Klik Terjemahkan.
  2. Dari halaman web, fungsi Cloud Run dipanggil untuk meluncurkan eksekusi alur kerja. Teks yang akan diterjemahkan diteruskan sebagai parameter ke fungsi dan alur kerja.
  3. Teks disimpan di database Cloud Firestore. Cloud Translation API dipanggil. Terjemahan yang ditampilkan disimpan dalam database. Aplikasi web di-deploy menggunakan Firebase Hosting dan diperbarui secara real time untuk menampilkan teks yang diterjemahkan.
  4. Langkah create_callback dalam alur kerja membuat URL endpoint callback yang juga disimpan di database Firestore. Halaman web kini menampilkan tombol Validasi dan Tolak.
  5. Alur kerja kini dijeda dan menunggu permintaan POST HTTP eksplisit ke URL endpoint callback.
  6. Anda dapat memutuskan apakah akan memvalidasi atau menolak terjemahan. Mengklik tombol memanggil fungsi Cloud Run kedua yang pada gilirannya memanggil endpoint callback yang dibuat oleh alur kerja, dengan meneruskan status persetujuan. Alur kerja melanjutkan eksekusinya dan menyimpan status persetujuan true atau false di database Firestore.

Diagram ini memberikan ringkasan prosesnya:

Alur kerja dengan callback

Men-deploy fungsi Cloud Run pertama

Fungsi Cloud Run ini meluncurkan eksekusi alur kerja. Teks yang akan diterjemahkan diteruskan sebagai parameter ke fungsi dan alur kerja.

  1. Buat direktori bernama callback-translation dan dengan subdirektori bernama invokeTranslationWorkflow,translationCallbackCall, dan public:

    mkdir -p ~/callback-translation/{invokeTranslationWorkflow,translationCallbackCall,public}
  2. Ubah ke direktori invokeTranslationWorkflow:

    cd ~/callback-translation/invokeTranslationWorkflow
  3. Buat file teks dengan nama file index.js yang berisi kode Node.js berikut:

    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. Buat file teks dengan nama file package.json yang berisi metadata npm berikut:

    {
      "name": "launch-translation-workflow",
      "version": "0.0.1",
      "dependencies": {
        "@google-cloud/workflows": "^1.2.5",
        "cors": "^2.8.5"
      }
    }
    
  5. Deploy fungsi dengan pemicu HTTP dan izinkan akses yang tidak diautentikasi:

    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

    Fungsi mungkin memerlukan waktu beberapa menit untuk di-deploy. Atau, Anda dapat menggunakan antarmuka fungsi Cloud Run di konsol Google Cloud untuk men-deploy fungsi tersebut.

  6. Setelah fungsi di-deploy, Anda dapat mengonfirmasi properti httpsTrigger.url:

    gcloud functions describe invokeTranslationWorkflow

    Catat URL yang ditampilkan agar Anda dapat menggunakannya pada langkah berikutnya.

Men-deploy fungsi Cloud Run kedua

Fungsi Cloud Run ini membuat permintaan HTTP POST ke endpoint callback yang dibuat oleh alur kerja, dengan meneruskan status persetujuan yang mencerminkan apakah terjemahan divalidasi atau ditolak.

  1. Ubah ke direktori translationCallbackCall:

    cd ../translationCallbackCall
  2. Buat file teks dengan nama file index.js yang berisi kode Node.js berikut:

    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. Buat file teks dengan nama file package.json yang berisi metadata npm berikut:

    {
      "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. Deploy fungsi dengan pemicu HTTP dan izinkan akses yang tidak diautentikasi:

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

    Fungsi mungkin memerlukan waktu beberapa menit untuk di-deploy. Atau, Anda dapat menggunakan antarmuka fungsi Cloud Run di konsol Google Cloud untuk men-deploy fungsi tersebut.

  5. Setelah fungsi di-deploy, Anda dapat mengonfirmasi properti httpsTrigger.url:

    gcloud functions describe translationCallbackCall

    Catat URL yang ditampilkan agar Anda dapat menggunakannya pada langkah berikutnya.

Men-deploy alur kerja

Alur kerja terdiri dari serangkaian langkah yang dijelaskan menggunakan sintaksis Workflows, yang dapat ditulis dalam format YAML atau JSON. Ini adalah definisi alur kerja. Setelah membuat alur kerja, Anda men-deploy alur kerja tersebut agar tersedia untuk dieksekusi.

  1. Ubah ke direktori callback-translation:

    cd ..
  2. Buat file teks dengan nama file translation-validation.yaml dan dengan konten berikut:

    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. Setelah membuat alur kerja, Anda dapat men-deploy-nya, tetapi jangan jalankan alur kerja:

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

Membuat aplikasi web

Buat aplikasi web yang memanggil Cloud Function yang meluncurkan eksekusi alur kerja. Halaman web diperbarui secara real time untuk menampilkan hasil permintaan terjemahan.

  1. Ubah ke direktori public:

    cd public
  2. Buat file teks dengan nama file index.html yang berisi markup HTML berikut. (Pada langkah selanjutnya, Anda akan mengubah file index.html dan menambahkan skrip 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. Buat file teks dengan nama file script.js yang berisi kode JavaScript berikut:

    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. Edit file script.js, ganti placeholder UPDATE_ME dengan URL fungsi Cloud Run yang Anda catat sebelumnya.

    1. Dalam metode translateBtn.addEventListener, ganti const fnUrl = UPDATE_ME; dengan:

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

    2. Di fungsi callCallbackUrl, ganti const fnUrl = UPDATE_ME; dengan:

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

  5. Buat file teks dengan nama file style.css yang berisi gaya bertingkat berikut:

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

Tambahkan Firebase ke aplikasi web Anda

Dalam tutorial ini, halaman HTML, skrip JavaScript, dan lembar gaya CSS di-deploy sebagai aset statis menggunakan Firebase Hosting, tetapi dapat dihosting di mana saja dan ditayangkan secara lokal di komputer Anda sendiri untuk tujuan pengujian.

Membuat project Firebase

Agar dapat menambahkan Firebase ke aplikasi, Anda perlu membuat project Firebase untuk terhubung ke aplikasi.

  1. Di Firebase console, klik Buat project, lalu pilih project Google Cloud yang sudah ada dari menu drop-down untuk menambahkan resource Firebase ke project tersebut.

  2. Klik Lanjutkan hingga Anda melihat opsi untuk menambahkan Firebase.

  3. Lewati penyiapan Google Analytics untuk project Anda.

  4. Klik Add Firebase.

Firebase otomatis menyediakan resource untuk project Firebase Anda. Setelah selesai, Anda akan diarahkan ke halaman ringkasan untuk project Anda di Firebase console.

Mendaftarkan aplikasi ke Firebase

Setelah memiliki project Firebase, Anda dapat menambahkan aplikasi web ke project tersebut.

  1. Di bagian tengah halaman ringkasan project, klik ikon Web (</>) untuk meluncurkan alur kerja penyiapan.

  2. Masukkan nama panggilan untuk aplikasi Anda.

    Bagian ini hanya dapat dilihat oleh Anda di Firebase console.

  3. Lewati penyiapan Firebase Hosting untuk saat ini.

  4. Klik Register app dan lanjutkan ke konsol.

Aktifkan Cloud Firestore

Aplikasi web menggunakan Cloud Firestore untuk menerima dan menyimpan data. Anda harus mengaktifkan Cloud Firestore.

  1. Di bagian Build Firebase console, klik Firestore Database.

    (Anda mungkin harus meluaskan panel navigasi kiri terlebih dahulu untuk melihat bagian Build.)

  2. Di panel Cloud Firestore, klik Buat database.

  3. Pilih Mulai dalam mode uji, menggunakan aturan keamanan seperti berikut:

    rules_version = '2';
    service cloud.firestore {
    match /databases/{database}/documents {
      match /{document=**} {
        allow read, write;
      }
    }
    }
  4. Klik Berikutnya setelah membaca pernyataan penyangkalan tentang aturan keamanan.

  5. Tetapkan lokasi tempat data Cloud Firestore Anda disimpan. Anda dapat menerima setelan default atau memilih wilayah yang dekat dengan Anda.

  6. Klik Enable untuk menyediakan Firestore.

Tambahkan Firebase SDK dan lakukan inisialisasi Firebase

Firebase menyediakan library JavaScript untuk sebagian besar produk Firebase. Sebelum menggunakan Firebase Hosting, Anda harus menambahkan Firebase SDK ke aplikasi Web Anda.

  1. Untuk menginisialisasi Firebase di aplikasi, Anda perlu memberikan konfigurasi project Firebase untuk aplikasi Anda.
    1. Di Firebase console, buka Project settings .
    2. Di panel Aplikasi Anda, pilih aplikasi Anda.
    3. Di panel Penyiapan dan konfigurasi SDK, untuk memuat library Firebase SDK dari CDN, pilih CDN.
    4. Salin cuplikan ke file index.html di bagian bawah tag <body>, dengan mengganti nilai placeholder XXXX.
  2. Instal Firebase JavaScript SDK.

    1. Jika Anda belum memiliki file package.json, buat dengan menjalankan perintah berikut dari direktori callback-translation:

      npm init
    2. Instal paket npm firebase dan simpan ke file package.json Anda dengan menjalankan:

      npm install firebase

Melakukan inisialisasi dan men-deploy project Anda

Untuk menghubungkan file project lokal ke project Firebase, Anda harus menginisialisasi project. Kemudian, Anda dapat men-deploy aplikasi web.

  1. Dari direktori callback-translation, jalankan perintah berikut:

    firebase init
  2. Pilih opsi Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys.

  3. Pilih untuk menggunakan project yang ada dan masukkan project ID Anda.

  4. Terima public sebagai direktori root publik default.

  5. Pilih untuk mengonfigurasi aplikasi halaman tunggal.

  6. Lewati penyiapan build dan deployment otomatis dengan GitHub.

  7. Pada perintah File public/index.html already exists. Overwrite?, ketik Tidak.

  8. Ubah ke direktori public:

    cd public
  9. Dari direktori public, jalankan perintah berikut untuk men-deploy project Anda ke situs Anda:

    firebase deploy --only hosting

Menguji aplikasi web secara lokal

Dengan Firebase Hosting, Anda dapat melihat dan menguji perubahan secara lokal serta berinteraksi dengan resource project backend yang diemulasikan. Saat menggunakan firebase serve, aplikasi Anda berinteraksi dengan backend yang diemulasi untuk konten dan konfigurasi hosting tetapi backend sebenarnya untuk semua resource project lainnya. Untuk tutorial ini, Anda dapat menggunakan firebase serve, tetapi tidak direkomendasikan saat melakukan pengujian yang lebih ekstensif.

  1. Dari direktori public, jalankan perintah berikut:

    firebase serve
  2. Buka aplikasi web di URL lokal yang ditampilkan (biasanya http://localhost:5000).

  3. Masukkan beberapa teks dalam bahasa Inggris, lalu klik Terjemahkan.

    Terjemahan teks dalam bahasa Prancis akan ditampilkan.

  4. Sekarang Anda dapat mengklik Validasi atau Tolak.

    Di database Firestore, Anda dapat memverifikasi isinya. Repositori tersebut akan terlihat seperti berikut:

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

    Status approved adalah true atau false, bergantung pada apakah Anda memvalidasi atau menolak terjemahan.

Selamat! Anda telah membuat alur kerja terjemahan yang memerlukan interaksi manusia yang menggunakan callback Workflows.