ZhgChg.Li

CI/CD Guide|Integrate Google Apps Script Web App with GitHub Actions for Free Packaging Platform

Developers facing complex CI/CD setups can streamline workflows by connecting Google Apps Script Web App with GitHub Actions, Slack, and Firebase APIs to build a free, collaborative packaging tool platform that enhances cross-team efficiency.

CI/CD Guide|Integrate Google Apps Script Web App with GitHub Actions for Free Packaging Platform

CI/CD Practical Guide (Part 4): Using Google Apps Script Web App to Connect GitHub Actions and Build a Free, Easy-to-Use Packaging Tool Platform

Independent writing, free to read — please support these ads

 

Advertise here →

GAS Web App Integrates GitHub, Slack, Firebase, or Asana/Jira APIs to Build a Relay Station, Providing a Cross-Team Shared Packaging Tool Platform

Photo by Lee Campbell

Photo by Lee Campbell

Preface

In the previous article “CI/CD Practical Guide (Part 3): Implementing iOS App CI and CD Workflows with GitHub Actions,” we completed the foundational CI/CD setup for the iOS app project. Now, CI can automatically run tests and CD can handle packaging and deployment. However, in real product development workflows, packaging and deployment are mostly done to deliver builds to other functional teams for QA (Quality Assurance) testing. At this point, the CD process is no longer limited to the engineering side; it may involve QA, PM, design (Design QA), or even the boss wanting to try it out first.

GitHub Actions workflow_dispatch manual trigger events provide a simple form for users to start builds. However, this is unfriendly for non-engineers who may not understand: what is a branch? Should fields be filled? How to know when the build is done? How to download it once ready? …etc.

There is also a permission control issue. To let other team members use GitHub Actions for builds, you have to add their accounts to the repo. This is insecure and unreasonable from a security perspective, as they get access to the entire source code just to operate the build form.

Unlike Jenkins, which has an independent web tool platform, GitHub Actions only provides this functionality.

`workflow_dispatch form style`

workflow_dispatch form style

Therefore, we need an intermediary packaging platform to serve other functional users, integrating Asana/Jira task tickets so that users can directly package the app using the task ticket and view the progress and download the packaging results on it.

GAS Web App Relay Station

GAS Web App Relay Station

Previous article focused on the core GitHub Actions CI/CD workflow development on the right side; this article focuses on the left side—the end-user packaging tool platform and enhancing user experience.

Google Apps Script — Web App Packaging Tool Platform Result Image

  • Packaging Form: Integrate project management tools to fetch ticket numbers and GitHub to retrieve open Pull Requests.

  • Package Records: Show packaging history, current task progress, and allow clicking to get download links by fetching Firebase App Distribution links and related information.

  • Runner Status: Display the status of the Self-hosted Runner.

  • Slack build progress notifications.

  • Support Mobile Version.

  • Support restricting usage to accounts within the organization team.

Main Responsibilities

0 state, 0 database, purely a relay exchange station, integrating and displaying data from various APIs (e.g. Asana/Jira/GitHub) and forwarding form requests to GitHub Actions.

Operation Requirements: Support both mobile and desktop devices.

Permission Requirements: Access should be restricted to team organization members only.

Online Demo Web App

  • For first-time use, please refer to the authorization in the image below (Only For Demo App):

Project source code: https://script.google.com/home/projects/1CBB39OMedqP9Ro1WSlvgDnMBin4-ksyhgly2h_KrbOuFiPHTalNgwHOp/edit

Technology Choices

The first article mentioned this, here is a detailed summary again.

Integrate with Slack

We tried using Slack as the packaging platform by developing our own backend service hosted on GCP. It integrated with Slack API and Asana API, then forwarded forms submitted via Slack to GitHub API to trigger subsequent GitHub Actions. The experience was very smooth and unified within the team’s collaboration tools, making it easy to use. However, the downside was the high development and maintenance costs. Since the backend was built with Ktor, app engineers had to handle both backend development and Google service OAuth integration. Some features, like submission for review, had to be implemented here, making it complex. If new team members didn’t properly take over this part, it became nearly impossible to maintain. Additionally, there was a monthly $15 USD GCP server fee.

Initially, we also tried using FaaS services to connect with the Slack API, such as Cloud Functions. However, Slack API requires the connected service to respond within 3 seconds; otherwise, it is considered a failure. FaaS suffers from the cold start issue, where the service goes to sleep after a period of inactivity and takes longer to respond (≥ 5 seconds) when called again. This causes the Slack packaging form to be unstable and often results in Timeout Errors.

Integration into Internal Systems

This is obviously the optimal solution. If the team has web or backend developers, directly integrating with existing systems is the best and safest approach.

The premise of this article is: None, the app is self-reliant.

Google Apps Script — Web App

Google Apps Script has been our longtime partner. We have used it in many RPA projects to schedule and trigger tasks, such as: “Crashlytics + Google Analytics Auto Query for App Crash-Free Users Rate” and “Using Google Apps Script to Automate Daily Data Report RPA”. This time, I recalled it again; GAS has a feature to deploy as a Web (App) to serve directly as a web service.

Advantages of Google Apps Script:

  • ✅ Free, almost no limits under normal use

  • ✅ Functions as a Service means no need to set up or maintain your own server

  • ✅ Permission control integrates with Google Workspace, allowing access only to Google accounts within the organization

  • ✅ Seamless integration with Google ecosystem services (e.g. Firebase, GA, etc.) and data (no need to handle OAuth yourself)

  • ✅ Programming language uses JavaScript, easy to learn (V8 Runtime supports ES6+)

  • ✅ Fast writing, fast deployment, fast usage

  • ✅ Stable and long-lasting service (launched over 16 years ago)

  • ✅ AI Can Help! Tested Using ChatGPT for Development Assistance, Achieving Up to 95% Accuracy

Disadvantages of Google Apps Script:

  • ❌ Built-in version control is problematic

  • ❌ No built-in support for file storage, data storage, or key/certificate management

  • ❌ Web App cannot achieve a 100% responsive web design (RWD) experience

  • ❌ The project can only be linked to a personal account, not an organization

  • ❌ Although Google continues development and maintenance, overall feature updates are slow

  • ❌ Network request UrlFetchApp does not support setting User-Agent

  • ❌ Web App doGet / doPost does not support retrieving Headers information

  • ❌ FaaS Cold Start Issue

  • Does not support simultaneous multi-developer collaboration
    However, this has little impact on the Web App, at most causing a few extra seconds delay when loading the page.

The above are the pros and cons of the GAS service itself, which have little impact on building a packaging tool web app. Compared to a Slack-based solution, this approach is faster, lighter, and easier to hand over and learn. The downside is that the team needs to know the tool’s URL and how to use it, and due to GAS’s limited library functions (e.g., no built-in encryption algorithms), it can basically only serve as a pure relay platform. For example, for submission requests, it can only forward the approval request to GitHub Actions.

Also only suitable for teams using Google Workspace environment; after weighing resources and needs, Google Apps Script — Web App was chosen to implement the packaging tool platform.

UI Framework

We directly use the Bootstrap CDN, otherwise managing CSS styles ourselves would be too troublesome. Asking AI how to combine and use Bootstrap is also more accurate and convenient.

Hands-on Practice

The entire platform architecture has been open-sourced here. Everyone can customize this version according to their team’s needs.

Open Source Example Project

View the project directly on GAS:

GitHub Repo Backup:

File Structure

I wrote a very simple class-based MVC architecture. If you need adjustments or don’t understand a feature, you can ask AI for accurate answers.

System

  • appsscript.json: Metadata configuration file for the GAS system
    The key point is the “oauthScopes” variable, which declares the external permissions this Script will use.

  • Entrypoint.gs: Define the doGet() entry point

Controller

  • Controller_iOS.gs: iOS packaging tool page Controller, responsible for fetching data for the View display

View

  • View_index.html: The entire packaging tool framework and homepage

  • View_iOS.html: iOS Packaging Tool Page Skeleton

  • View_iOS_Runs.html: iOS Packaging Tool — Packaging Record Details Page

  • View_iOS_Form.html: iOS Packaging Tool — Packaging Form Page

  • View_iOS_Runners.html: iOS Packaging Tool — Self-hosted Runner Status Page

Model(Lib)

  • Credentials.gs: Define key content
    (⚠️ Please note, using GCP IAM with GAS can be quite complex, so we directly define the keys here. Therefore, this GAS project contains sensitive information; do not share project view or edit permissions casually.)

  • StubData.gs: Stub methods and data for the Online Demo.

  • Settings.gs: Some common settings and lib initialization.

  • GitHub.gs: Wrapper for GitHub API operations.

  • Slack.gs: Wrapper for Slack API operations.

  • Firebase.gs: Firebase — Wrapper for App Distribution API operations.

Build Your Own Packaging Platform

  1. Create a Google Apps Script project and name it

Go to Project Settings → Check “Show ‘appsscript.json’ manifest file in editor” to display the “appsscript.json” metadata file.

  1. Refer to my open source project files and create all files exactly as in the example, then copy the content directly.

Very silly, but no choice.

Copy StubData.gs first; it can be used for the initial deployment test.

Another method is to use clasp (Google Apps Script CLI) to git clone the demo project and then push the code.

After copying, it will look exactly the same as the sample project.

  1. First Deployment of the “Web App” and Checking the Result

In the top right corner of the project, click “Deploy” → “New deployment” → select type “Web app”:

Execution Identity:

  • I always run the script using your account identity.

  • The user accessing the web app will run the script under their currently logged-in Google account.

Who can access:

  • Only me

  • All users in the same XXX organization Only users with the same organization and logged-in Google accounts can access.

  • All signed-in Google account users
    All signed-in Google account users can access.

  • Everyone does not need to sign in with a Google account, and everyone can access it publicly.

If it is an internal tool: you can choose “Who has access: All users in the XXX organization” + “Execute as: User accessing the web app” for secure control.

The URL of the “Web App” after deployment is your Web App packaging tool URL, which you can share with your team members. (The URL might look ugly, so you can use a URL shortening service to tidy it up. Updating the deployment content will not change the URL.)

The first time you click the Web App URL, you need to grant authorization.

  • Review Permission → Select the account identity to use this Web App

  • Unverified warning window, click “Advanced” to expand → click Proceed to “XXX” (unsafe)

  • Click “Allow”

No need to reauthorize if the script permissions remain unchanged.

After completing the authorization consent, you will enter the packaging tool homepage:

Demo packaging tool deployed successfully 🎉🎉🎉

Note: The “This app was created by a Google Apps Script user” message cannot be hidden automatically.

Update Deployment

⚠️ All code changes require redeployment to take effect.

️️⚠️All code changes require redeployment to take effect.

⚠️ All code changes require redeployment to take effect.

Note that saving code changes does not immediately update the Web App, so if refreshing has no effect, this is the reason; you need to go to “Deploy” → “Manage Deployments” → “Edit” → create a new version → click “Deploy” → “Done”.

After updating and deploying, refresh the webpage to see the changes take effect.

Add Test Deployment for Easier Development

As mentioned earlier, all changes require redeployment to take effect; this is inconvenient during development. Therefore, we can use a “test deployment” during development to quickly verify if the changes are correct.

Go to “Deploy” → “Test Deployments” → get the test “Web App” URL.

During development, we can directly use this URL to save. After saving file changes, refresh the page at this development URL to see the results!

After all development is complete, update and deploy as described earlier, then release it for users to use.

Modify the Demo Example Project to Connect Real Data

Independent writing, free to read — please support these ads

 

Advertise here →

Next is the key part: connecting real data. The GitHub Actions Workflow refers to the CI/CD process established in the previous article. You can also adjust the parameters according to your actual Actions Workflow.

⚠️ Please Note Before Making Changes

The Google Apps Script platform does not support multiple users or multiple editing windows well. A pitfall I encountered was accidentally opening two editor windows; after editing in window A, I later edited in window B, causing all changes to be overwritten by B’s older version. Therefore, it is recommended that only one person edits the Script in one window at a time.

GitHub Integration

Inject GitHub API Token:

GitHub -> Account -> Settings -> Developer Settings -> Fine-grained personal access tokens or Personal access tokens (classic).

It is recommended to use Fine-grained personal access tokens for better security (but they have an expiration).

The required permissions for Fine-grained personal access tokens are as follows:

  • Repo: Remember to select the Repo you want to operate on

  • Permissions: Actions (read/write) and Administration (read only)

If you don’t want to rely on someone’s personal account, it is recommended to create a dedicated team GitHub account and use its Token.

Go to the GAS project → Credentials.gs → Insert the token into the githubToken variable.

Replace GithubStub with GitHub:

Go to the GAS project → Settings.gs → change:

const iOSGitHub = new GitHubStub(githubToken, iOSRepoPath);

Change to

const iOSGitHub = new GitHub(githubToken, iOSRepoPath);

Save the file.

Refresh the test “Web App” URL to check if the changes are correct:

Correct data display means: GitHub is successfully connected to real data 🎉🎉🎉

You can also easily switch to the “Runner Status” tab to check if the Self-hosted Runner status is being fetched correctly:

Note: My Runner is off… so it is offline.

Slack Integration

To integrate Slack notifications, we first need to go back to the Repo → GitHub Actions and add a notification container Action that wraps the package build Action Workflow.

CD-Deploy-Form.yml:

# Workflow(Action) name
name: CD-Deploy-Form

# Actions Log title
run-name: "[CD-Deploy-Form] ${{ github.ref }}"

# Cancel any running job in the same concurrency group if a new one starts
# For example, if the same branch's build job is triggered repeatedly, cancel the previous one
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

# Trigger events
on:
  # Manual form trigger
  workflow_dispatch:
    # Form input fields
    inputs:
      # App version number
      VERSION_NUMBER:
        description: 'Version Number of the app (e.g., 1.0.0). Auto-detect from the Xcode project if left blank.'
        required: false
        type: string
      # App build number
      BUILD_NUMBER:
        description: 'Build number of the app (e.g., 1). Will use a timestamp if left blank.'
        required: false
        type: string
      # App release note
      RELEASE_NOTE:
        description: 'Release notes of the deployment.'
        required: false
        type: string
      # Slack User ID of the triggerer
      SLACK_USER_ID:
        description: 'Slack user id.'
        required: true
        type: string
      # Email of the triggerer
      AUTHOR:
        description: 'Trigger author email.'
        required: true
        type: string
        
# Job tasks
jobs:
  # Send Slack message when starting build
  # Job ID
  start-message:
    # Run this small job on GitHub Hosted Runner, low usage
    runs-on: ubuntu-latest
    
    # Set max timeout to avoid endless waiting in abnormal cases
    # Normally should not exceed 5 minutes
    timeout-minutes: 5

    # Job steps
    steps:
      - name: Post a Start Slack Message
        id: slack
        uses: slackapi/[email protected]
        with:
          method: chat.postMessage
          token: ${{ secrets.SLACK_BOT_TOKEN }}
          payload: \\|
            channel: ${{ inputs.SLACK_USER_ID }}
            text: "Build request received.\nID: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\\|${{ github.run_id }}>\nBranch: ${{ github.ref_name }}\ncc'ed <@${{ inputs.SLACK_USER_ID }}>"
    # Job output for following jobs
    # ts = Slack message ID, so later notifications can reply in the same thread
    outputs:
      ts: ${{ steps.slack.outputs.ts }}

  deploy:
    # Jobs run concurrently by default, use needs to wait for start-message to finish before running
    # Execute build and deploy task
    needs: start-message
    uses: ./.github/workflows/CD-Deploy.yml
    secrets: inherit
    with:
      VERSION_NUMBER: ${{ inputs.VERSION_NUMBER }}
      BUILD_NUMBER: ${{ inputs.BUILD_NUMBER }}
      RELEASE_NOTE: ${{ inputs.RELEASE_NOTE }}
      AUTHOR: ${{ inputs.AUTHOR }}

  # Success message for build and deploy task
  end-message-success:
    needs: [start-message, deploy]
    if: ${{ needs.deploy.result == 'success' }}
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Post a Success Slack Message
        uses: slackapi/[email protected]
        with:
          method: chat.postMessage
          token: ${{ secrets.SLACK_BOT_TOKEN }}
          payload: \\|
            channel: ${{ inputs.SLACK_USER_ID }}
            thread_ts: "${{ needs.start-message.outputs.ts }}"
            text: " Build and deploy succeeded.\n\ncc'ed <@${{ inputs.SLACK_USER_ID }}>"

  # Failure message for build and deploy task
  end-message-failure:
    needs: [deploy, start-message]
    if: ${{ needs.deploy.result == 'failure' }}
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Post a Failure Slack Message
        uses: slackapi/[email protected]
        with:
          method: chat.postMessage
          token: ${{ secrets.SLACK_BOT_TOKEN }}
          payload: \\|
            channel: ${{ inputs.SLACK_USER_ID }}
            thread_ts: "${{ needs.start-message.outputs.ts }}"
            text: " Build and deploy failed, please check the execution status or try again later.\n\ncc'ed <@${{ inputs.SLACK_USER_ID }}>"

  # Cancelled message for build and deploy task
  end-message-cancelled:
    needs: [deploy, start-message]
    if: ${{ needs.deploy.result == 'cancelled' }}
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Post a Cancelled Slack Message
        uses: slackapi/[email protected]
        with:
          method: chat.postMessage
          token: ${{ secrets.SLACK_BOT_TOKEN }}
          payload: \\|
            channel: ${{ inputs.SLACK_USER_ID }}
            thread_ts: "${{ needs.start-message.outputs.ts }}"
            text: ":black_square_for_stop: Build and deploy cancelled.\n\ncc'ed <@${{ inputs.SLACK_USER_ID }}>"

Full Code: CD-Deploy-Form.yml

This Action is just a container that connects Slack notifications, actually reusing the CD-Deploy.yml Action written in the previous article.

  • For creating the Slack Bot App and setting message permissions, please refer to my previous article.

  • Remember to add the corresponding SLACK_BOT_TOKEN in Repo → Secrets and enter the Slack Bot App Token value.

Back to the GAS project → Credentials.gs → Insert the Token into the slackBotToken variable.

Go to the GAS project → Settings.gs → change:

const slack = new SlackStub(slackBotToken);

Change to

const slack = new Slack(slackBotToken);

Save the file.

If you don’t have a ready Slack Bot App for notifications and don’t want to create one, you can skip all the steps here and remove any Slack-related usage from the GAS project.

GitHub Integration — Build Form

Go to the GAS project → Controller_iOS.gs → modify the View_iOS_Form.html content:
Remove the fake Asana Tasks integration method:

      <? tasks.forEach(function(task) { ?>
      <option value="<?=task.githubBranch?>">[<?=task.id?>] <?=task.title?></option>
      <? }) ?>

You can also adjust the default branch here (it’s main in this case).

Go to the GAS project → Controller_iOS.gs → modify the content of iOSLoadForm():

  • Remove the line template.tasks = Stubable.fetchStubAsanaTasks(); which mocks the Asana connection.
    If you want to connect to Asana/Jira, you can directly ask ChatGPT to help generate the integration method.

  • template.prs = iOSGitHub.fetchOpenPRs(); actually calls the GitHub API to fetch the list of opened PRs and can be kept as needed.

Post-Submission Handling Inside iOSSubmitForm():

You can adjust according to the actual GitHub Actions Workflow file name and the workflow_dispatch inputs parameters:

  iOSGitHub.dispatchWorkflow("CD-Deploy-Form.yml", branch, {
    "BUILD_NUMBER": buildNumber,
    "VERSION_NUMBER": versionNumber,
    "VERSION_NUMBER": versionNumber,
    "RELEASE_NOTE": releaseNote,
    "AUTHOR": email,
    "SLACK_USER_ID": slack.fetchUserID(email)
  });

You can also add your own required field validations. Here, it only checks that the branch must be filled in; otherwise, an error message will appear.

If you think this is not secure enough, you can add password verification or restrict access to specific accounts.

The last line Slack notification requires proper Slack setup. If you don’t have a Slack Bot App or don’t want to integrate Slack, you can directly use iOSGitHub.dispatchWorkflow("CD-Deploy.yml") in the Demo Actions Repo and remove the SLACK_USER_ID parameter.

Refresh the test “Web App” URL to check if the changes are correct:

You can see that the packaging form now only has the Opened PR List left.

Fill in the information and click “Submit Request” to test the packaging form:

A successful submission means everything is fine, and you can see the task started running in the build records 🎉:

Repeated packaging records can update progress.

Common Submission Errors:

Required input ‘SLACK_USER_ID’ not provided : The SLACK_USER_ID field is required in GitHub Actions but was not provided. This may be due to Slack configuration failure or the current User Email not matching any Slack UID.

Workflow does not have ‘workflow_dispatch’ trigger and Branch is outdated, please update the xxx branch: The selected branch cannot find the corresponding Action Workflow file (the file specified by iOSGitHub.dispatchWorkflow).

No ref found for, Branch not found : The branch could not be found.

The last small feature is integrating Firebase App Distribution to directly get download info and links, making it easy to open the packaging platform tool on a mobile device and click to download and install.

Previously introduced “Google Apps Script x Google APIs Quick Integration Method“—GAS can quickly and easily integrate with Firebase.

Integration Principle

Before integration, let’s first explain the “Tricky” integration principle.

Our packaging platform has no database and serves purely as an API relay station; so in practice, when we run the packaging job in GitHub Actions CD-Deploy.yml, we include the Job Run ID in the Release Note (of course, it can also be included in the Build Number):

ID="${{ github.run_id }}" // Job Run ID
COMMIT_SHA="${{ github.sha }}"
BRANCH_NAME="${{ github.ref_name }}"
AUTHOR="${{ env.AUTHOR }}"

# Compose Release Note
RELEASE_NOTE="${{ env.RELEASE_NOTE }}
ID: ${ID}
Commit SHA: ${COMMIT_SHA}
Branch: ${BRANCH_NAME}
Author: ${AUTHOR}
"

# Run Fastlane build & deploy lane
bundle exec fastlane beta release_notes:"${RELEASE_NOTE}" version_number:"${VERSION_NUMBER}" build_number:"${BUILD_NUMBER}"

This way, the Firebase App Distribution Release Notes will include the Job Run ID.

The GAS Web App packaging tool platform connects to the GitHub API to get GitHub Actions run records. We directly use the Job Run ID provided by the API to query the Firebase App Distribution API. If the Release Notes contain a version with *ID: XXX*, the corresponding packaging record can be found.

Map two tool platforms without using any database.

Project Integration Settings

Go to GAS → Project Settings → Google Cloud Platform (GCP) Project → Change Project:

Enter the Firebase project ID you want to connect.

Initial setup may show errors “To change the project, please configure the OAuth consent screen. Set up the OAuth consent screen details.” If it does not appear, you can skip the following steps.

Click the “OAuth consent screen details” link → Click “Configure consent screen”:

Click “Start”:

Application Information:

  • Application Name: Enter Your Tool Name

  • User Support Email: Select Email

Target Audience:

  • Internal: For use by organization members only

  • External: Available to all Google account users after consent to authorization

Contact Information:

  • Enter the email address to receive notifications

Check to agree to the 《 Google API Services: User Data Policy 》。

Finally, click “Create”.

Back to GAS → Project Settings → Google Cloud Platform (GCP) Project → Change Project:

Re-enter the Firebase project ID and click “Change Project”.

If no errors appear, the binding is complete.

If you choose “External,” you may also need to complete the following settings:

Click “Project Number” → expand the left sidebar → “APIs & Services” → “OAuth consent screen”

Select “Target Audience” → Test, click “Deploy as web app” → Done.

Users can follow the previous step “User must authorize on first use” to complete authorization and start using it!

If the above steps are not configured, users will encounter the following error:

Access to "XXX" blocked due to incomplete Google verification process

Access to “XXX” blocked due to incomplete Google verification process


Integrate Project

Back to the integration, Firebase directly uses ScriptApp.getOAuthToken() to dynamically obtain the token based on the execution identity, so no token setup is needed.

Only up to the GAS project → Settings.gs → change:

const iOSFirebase = new FirebaseStub(iOSFirebaseProject);

Change to

const iOSFirebase = new Firebase(iOSFirebaseProject);

Done.

Refresh the test “Web App” URL to the build records → Find a record and click “Get Download Link”:

If the corresponding Job Run Id is found in the Firebase App Distribution Release Notes, the download information will be displayed directly, and clicking the download button will take you straight to the download page.

Done! 🎉🎉🎉

Results

Demo Web App

Demo Web App

By now, you have adapted the examples into your actual usable packaging tool. Remaining custom features, additional third-party API integrations, and more forms can be extended on your own (discuss with ChatGPT).

Finally, don’t forget that after development and testing are complete, you need to follow the previous steps — updating the deployment is required for changes to take effect!

Integration Extensions

Continuing the spirit of our “relay station” role, here are some quick integration CheatSheets:

Asana API — Get Tasks:


function asanaAPI(endPoint, method = "GET", data = null) {
    var options = {
      "method" : method,
      "headers": {
          "Authorization":  "Bearer "+asanaToken
      },
      "payload" : data
    };

    var url = "https://app.asana.com/api/1.0"+endPoint;
    var res = UrlFetchApp.fetch(url, options);
    var data = JSON.parse(res.getContentText());
    return data;
}

asanaAPI("/projects/{project_gid}/tasks")

Jira API — Get Tickets (JQL):

// jql = filter criteria
function jiraTickets(jql) {
  const url = `https://xxx.atlassian.net/rest/api/3/search`;
  const maxResults = 100;

  let allIssues = [];
  let startAt = 0;
  let total = 0;

  do {
    const queryParams = {
      jql: jql,
      startAt: startAt,
      maxResults: maxResults,
      fields: "assignee,summary,status"
    };

    const queryString = Object.keys(queryParams)
      .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`)
      .join("&");

    const options = {
      method: "get",
      headers: {
        Authorization: "Basic " + jiraToken,
        "Content-Type": "application/json",
      },
      muteHttpExceptions: true,
    };

    const response = UrlFetchApp.fetch(`${url}?${queryString}`, options);
    const json = JSON.parse(response.getContentText());
    if (response.getResponseCode() != 200) {
      throw new Error("Failed to fetch Jira issues."); 
    }

    if (json.issues && json.issues.length > 0) {
      allIssues = allIssues.concat(json.issues);
      total = json.total;
      startAt += json.issues.length;
    } else {
      break;
    }
  } while (startAt < total);

  var groupIssues = {};
  for(var i = 0; i < allIssues.length; i++) {
    const issue = allIssues[i];
    if (groupIssues[issue.fields.status.name] == null) {
      groupIssues[issue.fields.status.name] = [];
    }
    groupIssues[issue.fields.status.name].push(issue);
  }

  return groupIssues;
}

jiraTickets(`project IN(App)`);

If a database is really needed, Google Sheets can be used as a substitute:

class Saveable {
  constructor(type) {
    // https://docs.google.com/spreadsheets/d/Sheet-ID/edit
    const spreadsheet = SpreadsheetApp.openById("Sheet-ID");
    this.sheet = spreadsheet.getSheetByName("Data"); // Sheet Name
    this.type = type;
  }

  write(key, value) {
    this.sheet.appendRow([
      this.type,
      key,
      JSON.stringify(value)
    ]);
  }

  read(key) {
    const data = this.sheet.getDataRange().getValues();
    const row = data.find(r => r[0] === this.type && r[1] === key);
    if (row) {
      return JSON.parse(row[2]);
    }
    return null;
  }
}

let saveable = Saveable("user");
// Write
saveable.write("birthday_zhgchgli", "0718");
// Read
saveable.read("birthday_zhgchgli"); // -> 0718

Slack API & Sending Messages:

function slackSendMessage(channel, text = "", blocks = null) {
  const content = {
    channel: channel,
    unfurl_links: false,
    unfurl_media: false,
    text: text,
    blocks: blocks
  };

  try {
    const response = slackRequest("chat.postMessage", content);
    return response;
  } catch (error) {
    throw new Error(`Failed to send Slack message: ${error}`);
  }
}

function slackRequest(path, content) {
  const options = {
    method: "post",
    contentType: "application/json",
    headers: {
      Authorization: `Bearer ${slackBotToken}`,
      'X-Slack-No-Retry': 1
    },
    payload: JSON.stringify(content)
  };

  try {
    const response = UrlFetchApp.fetch("https://slack.com/api/"+path, options);
    const responseData = JSON.parse(response.getContentText());
    if (responseData.ok) {
      return responseData
    } else {
      throw new Error(`Slack: ${responseData.error}`);
    }
  } catch (error) {
    throw error;
  }
}

More Google Apps Script Examples:

Summary

Independent writing, free to read — please support these ads

 

Advertise here →

Thank you for your patience and participation. The CI/CD from 0 to 1 series ends here; I hope it helps you and your team build a solid CI/CD workflow to improve efficiency and product stability. If you have any implementation questions, feel free to leave a comment. These four articles took about 14+ days to write. If you found them helpful, please follow me on Medium and share with your friends and colleagues.

Thank you.

🍺 Buy me a beer on PayPal

This series took a lot of time and effort to write. If the content helps you or improves your team’s work efficiency and product quality, please consider buying me a coffee. Thank you for your support!

Buy me a coffee

🍺 Buy me a beer on PayPal

Series Articles:

Improve this page
Edit on GitHub
Also published on Medium
Read the original
Share this essay
Copy link · share to socials
ZhgChgLi
Author

ZhgChgLi

An iOS, web, and automation developer from Taiwan 🇹🇼 who also loves sharing, traveling, and writing.

Comments