Workflows that pause and wait for human approvals from Google Sheets


I’ve been writing a series of posts to showcase Google Workspace and Google Cloud Workflows integration.

In my first post, I showed an IT automation use case in which a Google Sheets spreadsheet triggers a workflow to create virtual machines in Google Cloud. In the second post, I showed how to feed a Google Sheets spreadsheet with data from BigQuery using a workflow.

In this third and final post of the series, I show how to design a workflow that pauses and waits for human approvals from Google Sheets.

Use case

With Workflows callbacks, a workflow can create an HTTP endpoint and pause execution until it receives an HTTP callback to that endpoint. This is very useful for creating human-in-the-middle type workflows. However, there can be quite a bit of setup needed before a human is able to send that approval. The workflow has to create the callback URL, then the workflow must signal in some way what that URL is to a frontend, and finally the frontend has to enable the user to approve and send the callback.

Is there an easier way to enable users to send these approval callbacks? Sure, there is! You can use Google Sheets as the approval frontend.

Here’s the idea:

  1. A workflow runs some initial steps.
  2. The workflow creates a callback, saves the callback info to a Google Sheets spreadsheet, and starts to wait for an approval from a human for its remaining steps.
  3. A human sends his/her approval via the Google Sheets spreadsheet.
  4. The workflow receives the approval and runs the rest of the steps.

Architecture

Let’s take a look at each step in more detail.

Create a Google Sheet spreadsheet

First, create a Google Sheet spreadsheet to capture callback approval requests. The spreadsheet can contain any info you deem necessary for the approval request.

Here’s an example spreadsheet:

Approval spreadsheet

The Approved column will be used to initiate a callback to the workflow.

Once the sheet is created, note the spreadsheet id, which you will need in the workflow later. You can find the sheet id in the URL of the spreadsheet:

Spreadsheet id

Later, you will deploy the workflow with the default compute service account for simplicity. Find this service account email address by visiting the IAM & Admin -> Service Accounts section of Google Cloud Console:

Default compute service account

Make sure the service account has write permissions to the spreadsheet:

Sharing page

Create an Apps Script

Create an Apps Script to watch for changes in the Approved column.

Go to Extensions and Apps Script in the spreadsheet. This opens the Apps Script editor. Replace the default code in Code.gs with the code in Code.gs and click Save.

This code watches for changes in the Approved column. When a cell in this column is set to TRUE, it calls the workflow’s callback URL with the approver information.

In the Apps Script editor, go to Settings and select Show appsscript.json manifest file in editor. Replace the contents of appsscript.json with appsscript.json. This makes sure that the Apps Script has the required permissions.

Go to the Triggers section and create a trigger from the sheet to the Apps Script when the sheet is edited:

Create trigger

Create a workflow

Create a workflow.yaml to run some initial steps, wait for the callback, and (once the call back is received) run some more steps. Make sure you replace the sheet id with your own:

main:
  steps:
    - init:
        assign:
        # Replace with your sheetId and make sure the service account
        # for the workflow has write permissions to the sheet
        - sheetId: "10hieAH6b-oMeIVT_AerSLNxQck14IGhgi8ign-x2x8g"
    - before_sheets_callback:
        call: sys.log
        args:
          severity: INFO
          data: ${"Execute steps here before waiting for callback from sheets"}
    - wait_for_sheets_callback:
        call: await_callback_sheets
        args:
          sheetId: ${sheetId}
        result: await_callback_result
    - after_sheets_callback:
        call: sys.log
        args:
          severity: INFO
          data: ${"Execute steps here after receiving callback from sheets"}
    - returnResult:
        return: ${await_callback_result}

The await_callback_sheets sub-workflow receives a sheet id, creates a callback, saves the callback to Google Sheets, and waits for the callback:

await_callback_sheets:
    params: [sheetId]
    steps:
        - init:
            assign:
              - project_id: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
              - location: ${sys.get_env("GOOGLE_CLOUD_LOCATION")}
              - workflow_id: ${sys.get_env("GOOGLE_CLOUD_WORKFLOW_ID")}
              - execution_id: ${sys.get_env("GOOGLE_CLOUD_WORKFLOW_EXECUTION_ID")}
        - create_callback:
            call: events.create_callback_endpoint
            args:
              http_callback_method: POST
            result: callback_details
        - save_callback_to_sheets:
            call: googleapis.sheets.v4.spreadsheets.values.append
            args:
                range: ${"Sheet1!A1:G1"}
                spreadsheetId: ${sheetId}
                valueInputOption: RAW
                body:
                    majorDimension: "ROWS"
                    values:
                      - ["${project_id}", "${location}", "${workflow_id}", "${execution_id}", "${callback_details.url}", "", "FALSE"]
        - log_and_await_callback:
            try:
              steps:
                - log_await_start:
                    call: sys.log
                    args:
                      severity: INFO
                      data: ${"Started waiting for callback from sheet " + sheetId}
                - await_callback:
                    call: events.await_callback
                    args:
                      callback: ${callback_details}
                      timeout: 3600
                    result: callback_request
                - log_await_stop:
                    call: sys.log
                    args:
                      severity: INFO
                      data: ${"Stopped waiting for callback from sheet " + sheetId}
            except:
                as: e
                steps:
                    - log_error:
                        call: sys.log
                        args:
                            severity: "ERROR"
                            text: ${"Received error " + e.message}
        - check_null_await_result:
            switch:
              - condition: ${callback_request == null}
                return: null
        - log_await_result:
            call: sys.log
            args:
              severity: INFO
              data: ${"Approved by " + callback_request.http_request.body.approver}
        - return_await_result:
            return: ${callback_request.http_request.body}

Deploy the workflow

Make sure you have a Google Cloud project and the project id is set in gcloud:

PROJECT_ID=your-project-id
gcloud config set project $PROJECT_ID

Run setup.sh to enable required services and deploy the workflow defined in workflow.yaml.

Run the workflow

You’re now ready to test the end-to-end flow.

Run the workflow from Google Cloud Console or gcloud:

gcloud workflows run workflows-awaits-callback-sheets

You should see the workflow is running and waiting for the callback:

Workflow in waiting state

The logs also show that the workflow is waiting:

Info
2022-09-27 09:58:00.892 BST Execute steps here before waiting for callback from sheets
Info
2022-09-27 09:58:01.887 BST Started waiting for callback from sheet 10hieAH6b-oMeIVT_AerSLNxQck14IGhgi8ign-x2x8g

Go back to the sheet; you should see the callback info appended by Workflows with the Approved column set to FALSE:

Approval spreadsheet with approved false

Now, add an approver name/email and change the Approved column to TRUE:

Approval spreadsheet with approved true

You should see the workflow execution state is now Succeeded:

Workflow in succeeded state

The logs also show that the workflow is approved and completed:

Info
2022-09-27 10:04:11.101 BST Approved by Mete Atamel
Info
2022-09-27 10:04:11.442 BST Execute steps here after receiving callback from sheets

Callbacks are especially useful for creating human-in-the-loop type workflows. Google Sheets provides an ready-to-use frontend for users to provide their approvals, and Apps Script provides an easy way for developers to signal a workflow that is waiting for a callback to resume.

For questions or feedback, feel free to reach out to me on Twitter @meteatamel.


See also