Google Chat: Space, Create Message
Google Chat: Space, Create Message
Posts a message in a Google Chat Space. Bold decoration (with asterisks) and Inline-code decoration (with backticks) are also applied as they are. It is also possible to attach some Process Data in card format.
Configs
  • A1: Set INCOMING WEBHOOK Url (at Menu “Manage Webhook”) *#{EL}
  • B1: Set Body of Message#{EL}
  • B2: If to post Data, Set Field Names in each line (eg “q_corp”)#{EL}
  • B3: If to display footer link, Set ${var[applicationRoot]}#{EL}
  • C1: Select STRING DATA that stores Message Name (update)
  • C2: Select STRING DATA that stores Space Name (update)
Script (click to open)
// GraalJS Script (engine type: 2)

//////// START "main()" /////////////////////////////////////////////////////////////////

main();
function main(){ 

//// == Config Retrieving / 工程コンフィグの参照 ==
const strWebhookUrl        = configs.get      ( "StrConfA1" );    /// REQUIRED
  if( strWebhookUrl      === "" ){
    throw new Error( "\n AutomatedTask ConfigError:" +
                     " Config {A1: WebhookUrl} is empty \n" );
  }
const strBodyOfMessage     = configs.get      ( "StrConfB1" );    // NotRequired
const strFieldNames        = configs.get      ( "StrConfB2" );    // NotRequired
const strApplicationRoot   = configs.get      ( "StrConfB3" );    // NotRequired
const strPocketMessageName = configs.getObject( "SelectConfC1" ); // NotRequired
const strPocketSpaceName   = configs.getObject( "SelectConfC2" ); // NotRequired
  if( strBodyOfMessage  === "" && strFieldNames === "" ){
    throw new Error( "\n AutomatedTask ConfigError:" +
                     " Both Config {B1:Message} and {B2:FieldNames} are empty \n" );
  }

//// == Data Retrieving / ワークフローデータの参照 ==
// (Nothing. Retrieved via Expression Language in Config Retrieving)


//// == Calculating / 演算 ==
//// get property
const strProcessInstanceId    = processInstance.getProcessInstanceId() + "";
const strProcessInstanceTitle = processInstance.getProcessInstanceTitle() + "";
const strWorkflowAppName      = processInstance.getProcessModelInfoName() + "";
const strWorkflowAppId        = processInstance.getProcessModelInfoId() + "";

//// prepare for access to "Google Chat Bot"
/// Google Workspace for Developers > Google Chat for Developers
/// > Integrate any tool, service, or resource with Google Chat bots
/// > Send asychronous messages with incoming webhooks
/// https://developers.google.com/chat/how-tos/webhooks
/// 
/// NOT "Google Chat API"
/// "Get programmatic access to events, messages, spaces, and attachments"
/// Reference > REST Resources > spaces.messages > create
/// https://developers.google.com/chat/api/reference/rest/v1/spaces.messages/create

// Message
let request1Obj = {};

// Simple text message
// https://developers.google.com/chat/api/guides/message-formats/basic
    request1Obj.text = strBodyOfMessage;

// Card message
// https://developers.google.com/chat/api/guides/message-formats/cards
// https://developers.google.com/chat/api/guides/message-formats/cards#card_text_formatting

if( strFieldNames !== "" || strApplicationRoot !== "" ){
    request1Obj.cards = [];           // ___ ___ prepare Cards array ___ ___
    request1Obj.cards[0] = {};
    request1Obj.cards[0].sections = [];  // ___ prepare Sections array ___

    let objTitleSection = {};            // ### Title Section
        objTitleSection.widgets = [];        // ___ prepare Widgets array ___
        objTitleSection.widgets[0] = {};
        objTitleSection.widgets[0].textParagraph = {};
        objTitleSection.widgets[0].textParagraph.text = strApplicationRoot ?
          // clickable link to WorkflowApp Info
          `<font color="#57C400"><i>p${strProcessInstanceId}</i></font><br>` +
          `<font color="#009900"><b>` + escaper.escapeHtml(strProcessInstanceTitle) + `</b></font><br> ` +
          `<a href=${strApplicationRoot}OR/ProcessModel/view?processModelInfoId=${strWorkflowAppId}>` +
            escaper.escapeHtml(strWorkflowAppName) + `</a>` :
          // No link
          `<font color="#57C400"><i>p${strProcessInstanceId}</i></font><br>` +
          `<font color="#009900"><b>` + escaper.escapeHtml(strProcessInstanceTitle) +
          `</b></font><br> ` + escaper.escapeHtml(strWorkflowAppName);
    request1Obj.cards[0].sections.push( objTitleSection ); // push TitleSection to Cards[0].Sections ###

  if( strFieldNames !== "" ){
    const arrFieldNames = strFieldNames.split("\n");
    for( let i = 0; i < arrFieldNames.length; i++ ){
      if( arrFieldNames[i] === "" ){ continue; }
      let objDataSection = {};           // ### Data Section
          objDataSection.widgets = [];       // ___ prepare Widgets array ___
      let objKeyValueWidget = {};            // ### KeyValue Widget
          objKeyValueWidget.keyValue = {};
      let qPocket = engine.findDataDefinitionByVarName( arrFieldNames[i] );
      // refer Process Data
      if( qPocket === null ){
        engine.log( " AutomatedTask RuntimeWarning: " +
                    " {B2: FieldName} contains illegal name: " + arrFieldNames[i] );
        continue;
      }else if( qPocket.matchDataType( "STRING_TEXTFIELD" ) ){ 
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            escaper.escapeHtml(engine.findData(qPocket)) : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "false";
          // "topLabel" "bottomLabel" Subset_of_HTML_Tags not supported
      }else if( qPocket.matchDataType( "STRING_TEXTAREA" ) ){
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            escaper.escapeHtml(engine.findData(qPocket)) : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "true";
      }else if( qPocket.matchDataType( "DECIMAL" ) ){
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            (engine.findData(qPocket) + "") : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "false";
      }else if( qPocket.matchDataType( "SELECT" ) ){
          let qSelected = engine.findData(qPocket);
          let numOfSelected = qSelected.size() - 0;
          let strSelected = "";
          for( let j = 0; j < numOfSelected; j++ ){
            strSelected += escaper.escapeHtml(qSelected.get(j).getDisplay());
            if( j !== numOfSelected - 1 ){ strSelected += "<br>";}
          }
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            strSelected : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "true";
      }else if( qPocket.matchDataType( "DATE" ) ){
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            (engine.findData(qPocket).toString()) : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "false";
      }else if( qPocket.matchDataType( "DATETIME" ) ){
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            (engine.findData(qPocket).toString()) : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "false";
      }else if( qPocket.matchDataType( "FILE" ) ){
          let qFiles = engine.findData(qPocket);
          let numOfFiles = qFiles.size() - 0;
          let strFiles = "";
          for( let j = 0; j < numOfFiles; j++ ){
            strFiles += escaper.escapeHtml(qFiles.get(j).getName());
            if( j !== numOfFiles - 1 ){ strFiles += "<br>";}
          }
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            strFiles : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "true";
      }else if( qPocket.matchDataType( "QUSER" ) ){
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            escaper.escapeHtml(engine.findData(qPocket).getName()) : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "false";
      }else if( qPocket.matchDataType( "QGROUP" ) ){
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            escaper.escapeHtml(engine.findData(qPocket).getName()) : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "false";
      }else if( qPocket.matchDataType( "LIST" ) ){
          objKeyValueWidget.keyValue.topLabel = qPocket.getName();
          objKeyValueWidget.keyValue.content  = engine.findData(qPocket) ?
            (`<i><font color="#aaaaaa">` + engine.findData(qPocket).size() + ` rows</font></i>`) : `<i><font color="#aaaaaa">null</font></i>`;
          objKeyValueWidget.keyValue.contentMultiline = "false";
      }else if( qPocket.matchDataType( "DISCUSSION" ) ){
        engine.log( " AutomatedTask RuntimeWarning: " +
                    " {B2: FieldName} contains DISCUSSION type (skipped): " + arrFieldNames[i] );
        continue;
      }else{
        engine.log( " AutomatedTask RuntimeWarning: " +
                    " {B2: FieldName} contains unsupported type (skipped): " + arrFieldNames[i] );
        continue;
      }
      objDataSection.widgets.push( objKeyValueWidget );     // push Widget to DataSection ###
      request1Obj.cards[0].sections.push( objDataSection ); // push DataSection to Cards[0].Sections ###
    }
  }

  if( strApplicationRoot !== "" ){
    let objFooterSection = {};          // ### Footer Section
        objFooterSection.widgets = [];

    let objTextButtonWidget = {};         // ### Button Widget
        objTextButtonWidget.buttons = [];
        objTextButtonWidget.buttons[0] = {};
        objTextButtonWidget.buttons[0].textButton = {};
        objTextButtonWidget.buttons[0].textButton.text = "Desktop View";
        objTextButtonWidget.buttons[0].textButton.onClick = {};
        objTextButtonWidget.buttons[0].textButton.onClick.openLink = {};
        objTextButtonWidget.buttons[0].textButton.onClick.openLink.url =
          `${strApplicationRoot}PE/Workitem/list?processInstanceId=${strProcessInstanceId}`;
        objTextButtonWidget.buttons[1] = {};
        objTextButtonWidget.buttons[1].textButton = {};
        objTextButtonWidget.buttons[1].textButton.text = "Mobile View";
        objTextButtonWidget.buttons[1].textButton.onClick = {};
        objTextButtonWidget.buttons[1].textButton.onClick.openLink = {};
        objTextButtonWidget.buttons[1].textButton.onClick.openLink.url =
          `${strApplicationRoot}SP/PE/ProcessInstance/view?processInstanceId=${strProcessInstanceId}`;
    objFooterSection.widgets.push( objTextButtonWidget );   // push Widget to FooterSection ###

    request1Obj.cards[0].sections.push( objFooterSection ); // push FooterSection to Cards[0].Sections ###
  }
}
// engine.log( JSON.stringify( request1Obj ) ); // for debug

/// access to "Google Chat Bot"
let request1Uri = strWebhookUrl;
let request1    = httpClient.begin(); // HttpRequestWrapper
    request1    = request1.body( JSON.stringify( request1Obj ), "application/json" );
// request1, try
const response1     = request1.post( request1Uri ); // HttpResponseWrapper
const arrRequest1Uri = request1Uri.split('?'); // "key" will not be loged
engine.log( " AutomatedTask ApiRequest1 Start: " + arrRequest1Uri[0] );
const response1Code = response1.getStatusCode() + "";
const response1Body = response1.getResponseAsString() + "";
engine.log( " AutomatedTask ApiResponse Status: " + response1Code );
if( response1Code !== "200"){
  throw new Error( "\n AutomatedTask UnexpectedResponseError: " +
                    response1Code + "\n" + response1Body + "\n" );
}
// response1, parse
/* 
engine.log( response1Body ); // for debug
{
  "name": "spaces/AAAA4xxxxx4/messages/EdtRhyyyyy4.EdtRhyyyyy4",
  "sender": {
    "name": "users/114029999953014888889",
    "displayName": "TestWebhook",
    "avatarUrl": "",
    "email": "",
    "domainId": "",
    "type": "BOT",
    "isAnonymous": false
  },
  "text": "test\n*bold text*\n`backquotes`",
  "cards": [],
  "previewText": "",
  "annotations": [],
  "thread": {
    "name": "spaces/AAAA4xxxxx4/threads/EdtRhyyyyy4"
  },
  "space": {
    "name": "spaces/AAAA4xxxxx4",
    "type": "ROOM",
    "singleUserBotDm": false,
    "threaded": true,
    "displayName": "TestSpace",
    "legacyGroupChat": false
  },
  "fallbackText": "",
  "argumentText": "test\n*bold text*\n`backquotes`",
  "attachment": [],
  "createTime": "2022-03-08T07:33:33.109455Z",
  "lastUpdateTime": "2022-03-08T07:33:33.109455Z"
}
*/
const response1Obj   = JSON.parse( response1Body );
const strMessageName = response1Obj.name;
const strSpaceName   = response1Obj.space.name;


//// == Data Updating / ワークフローデータへの代入 ==
if( strPocketMessageName !== null ){
  engine.setData( strPocketMessageName, strMessageName );
  // Example: spaces/AAAAAAAAAAA/messages/BBBBBBBBBBB.BBBBBBBBBBB
}
if( strPocketSpaceName !== null ){
  engine.setData( strPocketSpaceName, strSpaceName );
  // Example: spaces/AAAAAAAAAAA
}


} //////// END "main()" /////////////////////////////////////////////////////////////////


/*
Notes:
- Integrates between the Workflow App and the Google Chat Space.
    - When the process reaches this automated task, a message is automatically posted to Google Chat.
    - Also be used as a means of sharing business data with "non-users" of the workflow platform.
    - The automated task posts an arbitrary string of text (message body).
    - It is also possible to attach a Card with the business data stored in the process.
- Can be used for various timeline chatting.
    - Automatically report "Order information" to SPACE for management team.
    - Automatically report "Business progress" to SPACE for communication with customers.
- The Incoming Webhook function of Google Chat is used.
    - The application administrator only needs to set the "Webhook URL" for the automatic process.
    - "Webhook URL" is obtained from the "Manage Webhook" menu of the relevant "Space".
    - e.g: `https://chat.googleapis.com/v1/spaces/AAAA4xxxxx4/messages?key=AIzaSxxxxxhCZtEyyyyyMm-WEzzzzzPzqKqqsHI&token=UMlLOxxxxxw2JgtyyyyyWLqMizzzzzSiPnBxxxxxENI%3D`
APPENDIX
- This automated task communicates via the "Incoming Webhook" of Bot. (Asynchronous Messages)
    - No user authentication or user authorization (OAuth2 settings, etc.) configuration is required.
    - https://developers.google.com/chat/how-tos/webhooks
    - Communication is done by a different mechanism from "Google Chat API".
        - https://developers.google.com/chat/api/reference/rest/v1/spaces.messages/create
- Google Chat "Spaces" were previously called "Rooms" (2021-09)
    - This automated task assumes a "Space" created in your Google Workspace account.
    - Unlike Group Messages, Spaces can add or remove users at any time.
    - Unlike Group Messages, Spaces also allow you to talk to people outside of your organization.
        - Invited external users can join from "Browse Spaces".
        - https://support.google.com/chat/answer/7653963
- Message body
    - Posted body messages are sent in plain text.
    - However, decorations such as Bold (with asterisks) and Code (with backquotes) are also possible.
    - https://developers.google.com/chat/api/guides/message-formats/basic
- Process Data
    - If the app manager specifies a "field name" for the automatic process, the case data will also be posted.
    - A simple Card is additionally displayed at the bottom of the "Message Body".
    - A Process Title is automatically added to the header of the Card.
    - This is useful for sharing information with people who do not have an account on the workflow platform (non-users).

Notes-ja:
- ワークフローアプリとGoogleチャット Space のデータ連携を実現します。
    - 案件がこの自動工程に到達した際、Googleチャットに対して自動的に投稿します。
    - ワークフロー基盤の「非ユーザ」と業務データを共有する手段としても利用できます。
    - 自動工程は、任意の文字列(メッセージ本文)を投稿します。
    - 案件内に格納されている案件データをCard添付することも可能です。
- 様々タイムライン・コミュニケーションに利用できます。
    - マネージメントチーム用Spaceに「受注情報」を自動報告する
    - 顧客とのコミュニケーション用のSpaceで「業務進捗」を自動報告する
- GoogleチャットのWebhook機能(着信Webhook)が利用されます。
    - アプリ管理者は、自動工程に "Webhook URL" をセットするだけで利用できます。
    - "Webhook URL" は、当該「スペース」の「Webhook を管理」メニューから取得します。
    - 例: `https://chat.googleapis.com/v1/spaces/AAAA4xxxxx4/messages?key=AIzaSxxxxxhCZtEyyyyyMm-WEzzzzzPzqKqqsHI&token=UMlLOxxxxxw2JgtyyyyyWLqMizzzzzSiPnBxxxxxENI%3D`
APPENDIX-ja
- この自動工程では、Bot の「着信Webhook」を介して通信されます。(非同期メッセージ)
    - ユーザ認証やユーザ認可(OAuth2設定等)の設定は必要ありません。
    - https://developers.google.com/chat/how-tos/webhooks
    - "Google Chat API" とは異なる仕組みで通信されます。
        - https://developers.google.com/chat/api/reference/rest/v1/spaces.messages/create
- Googleチャットの「スペース」は、以前は「チャットルーム」と呼称されていました(~2021-09)
    - この自動工程は、Google Workspace アカウントで作成された「スペース」を前提としています。
    - 「スペース」は、「グループ メッセージ」と異なり、いつでもユーザ追加削除が可能です。
    - 「スペース」は、「グループ メッセージ」と異なり、社外ユーザとの会話も可能です。
        - 招待された社外ユーザは「スペースをブラウジング」から参加します。
        - https://support.google.com/chat/answer/7653963
- メッセージ本文
    - 投稿される本文メッセージはプレーンテキストで送信されます。
    - ただし、太字(アスタリスク囲み)やCode(バッククオート囲み)といった装飾も可能です。
    - https://developers.google.com/chat/api/guides/message-formats/basic
- 案件データ
    - アプリ管理者が、自動工程に "フィールド名" を指定した場合、案件データも投稿されます。
    - シンプルな Card が「メッセージ本文」の下部に追加表示されます。
    - Card のヘッダー部には、案件タイトルが自動的に追加されます。
    - ワークフロー基盤にアカウントを持たない人(非ユーザ)との情報共有に便利です。
*/

Download

2021-03-11 (C) Questetra, Inc. (MIT License)
https://support.questetra.com/addons/google-chat-space-create-message-2022/
The Add-on import feature is available with Professional edition.
Freely modifiable JavaScript (ECMAScript) code. No warranty of any kind.

Notes

  • Integrates between the Workflow App and the Google Chat Space.
    • When the process reaches this automated task, a message is automatically posted to Google Chat.
    • Also can be used as a means of sharing business data with non-users of the workflow platform.
    • The automated task posts an arbitrary string of text (message body).
    • It is also possible to attach a Card with the business data stored in the process.
  • Can be used for a variety of timeline communications.
    • Automatical reporting of order information to a Space for the management team.
    • Automatical reporting of business progress to a Space for communication with customers.
  • The Incoming Webhook function of Google Chat is used.
    • The application administrator only needs to set the Webhook URL for the automatic process.
    • The Webhook URL is obtained from the “Manage Webhook” menu of the relevant Space.
    • e.g: https://chat.googleapis.com/v1/spaces/AAAA4xxxxx4/messages?key=AIzaSxxxxxhCZtEyyyyyMm-WEzzzzzPzqKqqsHI&token=UMlLOxxxxxw2JgtyyyyyWLqMizzzzzSiPnBxxxxxENI%3D
  • If the phone number has been posted, you can click to call from your smartphone Gmail app.

Capture

Posts a message in Google Chat "Space". Bold decoration (by asterisks) and Inline-code decoration (by backticks) are also applied as they are. Also possible to attach some Process Data in "card format".

Appendix

  • This automated task communicates via the bot’s Incoming Webhook (asynchronous messages).
  • Google Chat “Spaces” were previously called “Rooms” (2021-09)
    • This automated task assumes a Space created in your Google Workspace account.
    • Unlike Group Messages, Spaces can add or remove users at any time.
    • Unlike Group Messages, Spaces also allow you to talk to people outside of your organization.
  • Message body
  • Process Data
    • If the app manager specifies a field name for the automatic process, the case data will also be posted.
    • A simple Card is additionally displayed at the bottom of the message body.
    • A Process Title is automatically added to the header of the Card.
    • This is useful for sharing information with people who do not have an account on the workflow platform (non-users).

See also

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: