// 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 のヘッダー部には、案件タイトルが自動的に追加されます。
- ワークフロー基盤にアカウントを持たない人(非ユーザ)との情報共有に便利です。
*/