Workflows that pause and wait for human approvals from Google Sheets

Mete Atamel
Google Cloud - Community
6 min readOct 10, 2022

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.

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:

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:

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:

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

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 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:

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:

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

You should see the workflow execution state is now Succeeded:

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.

Originally published at https://atamel.dev.

--

--

Mete Atamel
Google Cloud - Community

I'm a Developer Advocate at Google Cloud, helping developers with serverless and orchestration technologies