فهرست منبع

GW-6712 Show checkbox message & Refactored block-creator

hakumizuki 4 سال پیش
والد
کامیت
4beba5df89

+ 0 - 1
packages/slack/src/index.ts

@@ -14,7 +14,6 @@ export * from './interfaces/request-from-slack';
 export * from './models/errors';
 export * from './middlewares/verify-growi-to-slack-request';
 export * from './middlewares/verify-slack-request';
-export * from './utils/block-creater';
 export * from './utils/block-kit-builder';
 export * from './utils/check-communicable';
 export * from './utils/post-ephemeral-errors';

+ 0 - 31
packages/slack/src/utils/block-creater.ts

@@ -1,31 +0,0 @@
-import { SectionBlock, InputBlock } from '@slack/types';
-
-export const generateMarkdownSectionBlock = (blocks:string):SectionBlock => {
-  return {
-    type: 'section',
-    text: {
-      type: 'mrkdwn',
-      text: blocks,
-    },
-  };
-};
-
-export const generateInputSectionBlock = (blockId:string, labelText:string, actionId:string, isMultiline:boolean, placeholder:string):InputBlock => {
-  return {
-    type: 'input',
-    block_id: blockId,
-    label: {
-      type: 'plain_text',
-      text: labelText,
-    },
-    element: {
-      type: 'plain_text_input',
-      action_id: actionId,
-      multiline: isMultiline,
-      placeholder: {
-        type: 'plain_text',
-        text: placeholder,
-      },
-    },
-  };
-};

+ 70 - 9
packages/slack/src/utils/block-kit-builder.ts

@@ -1,8 +1,17 @@
-import { SectionBlock, InputBlock, DividerBlock } from '@slack/types';
+import {
+  SectionBlock, InputBlock, DividerBlock, ActionsBlock,
+  Button, Overflow, Datepicker, Select, RadioButtons, Checkboxes, Action, MultiSelect, PlainTextInput, Option,
+} from '@slack/types';
 
 export class BlockKitBuilder {
 
-  static generateMarkdownSectionBlock(text: string): SectionBlock {
+  static divider(): DividerBlock {
+    return {
+      type: 'divider',
+    };
+  }
+
+  static markdownSectionBlock(text: string): SectionBlock {
     return {
       type: 'section',
       text: {
@@ -12,13 +21,7 @@ export class BlockKitBuilder {
     };
   }
 
-  static divider(): DividerBlock {
-    return {
-      type: 'divider',
-    };
-  }
-
-  static generateInputSectionBlock(blockId: string, labelText: string, actionId: string, isMultiline: boolean, placeholder: string): InputBlock {
+  static inputSectionBlock(blockId: string, labelText: string, actionId: string, isMultiline: boolean, placeholder: string): InputBlock {
     return {
       type: 'input',
       block_id: blockId,
@@ -38,4 +41,62 @@ export class BlockKitBuilder {
     };
   }
 
+  static actionsBlock(...elements: (Button | Overflow | Datepicker | Select | RadioButtons | Checkboxes | Action)[]): ActionsBlock {
+    return {
+      type: 'actions',
+      elements: [
+        ...elements,
+      ],
+    };
+  }
+
+  static inputBlock(element: Select | MultiSelect | Datepicker | PlainTextInput | RadioButtons | Checkboxes, blockId: string, labelText: string): InputBlock {
+    return {
+      type: 'input',
+      block_id: blockId,
+      element,
+      label: {
+        type: 'plain_text',
+        text: labelText,
+      },
+    };
+  }
+
+  /**
+   * Button element
+   * https://api.slack.com/reference/block-kit/block-elements#button
+   */
+  static buttonElement(text: string, actionId: string, style?: string): Button {
+    const button: Button = {
+      type: 'button',
+      text: {
+        type: 'plain_text',
+        text,
+      },
+      action_id: actionId,
+    };
+    if (style === 'primary' || style === 'danger') {
+      button.style = style;
+    }
+    return button;
+  }
+
+  /**
+   * Option object
+   * https://api.slack.com/reference/block-kit/composition-objects#option
+   */
+  static checkboxesElementOption(text: string, description: string, value: string): Option {
+    return {
+      text: {
+        type: 'mrkdwn',
+        text,
+      },
+      description: {
+        type: 'plain_text',
+        text: description,
+      },
+      value,
+    };
+  }
+
 }

+ 3 - 3
packages/slack/src/utils/post-ephemeral-errors.ts

@@ -1,6 +1,6 @@
 import { WebAPICallResult } from '@slack/web-api';
 
-import { generateMarkdownSectionBlock } from './block-creater';
+import { BlockKitBuilder as B } from './block-kit-builder';
 import { generateWebClient } from './webclient-factory';
 
 export const postEphemeralErrors = async(
@@ -19,7 +19,7 @@ export const postEphemeralErrors = async(
       channel: channelId,
       user: userId,
       blocks: [
-        generateMarkdownSectionBlock('*Error occured:*'),
+        B.markdownSectionBlock('*Error occured:*'),
         ...rejectedResults.map((rejectedResult) => {
           const reason = rejectedResult.reason.toString();
           const resData = rejectedResult.reason.response?.data;
@@ -30,7 +30,7 @@ export const postEphemeralErrors = async(
             errorMessage += `\n  Cause: ${resDataMessage}`;
           }
 
-          return generateMarkdownSectionBlock(errorMessage);
+          return B.markdownSectionBlock(errorMessage);
         }),
       ],
     });

+ 5 - 5
packages/slackbot-proxy/src/controllers/slack.ts

@@ -7,7 +7,7 @@ import axios from 'axios';
 import { WebAPICallResult } from '@slack/web-api';
 
 import {
-  generateMarkdownSectionBlock, GrowiCommand, parseSlashCommand, postEphemeralErrors, verifySlackRequest,
+  BlockKitBuilder as B, GrowiCommand, parseSlashCommand, postEphemeralErrors, verifySlackRequest,
 } from '@growi/slack';
 
 import { Relation } from '~/entities/relation';
@@ -139,8 +139,8 @@ export class SlackCtrl {
     if (relations.length === 0) {
       return res.json({
         blocks: [
-          generateMarkdownSectionBlock('*No relation found.*'),
-          generateMarkdownSectionBlock('Run `/growi register` first.'),
+          B.markdownSectionBlock('*No relation found.*'),
+          B.markdownSectionBlock('Run `/growi register` first.'),
         ],
       });
     }
@@ -149,8 +149,8 @@ export class SlackCtrl {
     if (growiCommand.growiCommandType === 'status') {
       return res.json({
         blocks: [
-          generateMarkdownSectionBlock('*Found Relations to GROWI.*'),
-          ...relations.map(relation => generateMarkdownSectionBlock(`GROWI url: ${relation.growiUri}`)),
+          B.markdownSectionBlock('*Found Relations to GROWI.*'),
+          ...relations.map(relation => B.markdownSectionBlock(`GROWI url: ${relation.growiUri}`)),
         ],
       });
     }

+ 8 - 8
packages/slackbot-proxy/src/services/RegisterService.ts

@@ -1,6 +1,6 @@
 import { Inject, Service } from '@tsed/di';
 import { WebClient, LogLevel, Block } from '@slack/web-api';
-import { generateInputSectionBlock, GrowiCommand, generateMarkdownSectionBlock } from '@growi/slack';
+import { BlockKitBuilder as B, GrowiCommand } from '@growi/slack';
 import { AuthorizeResult } from '@slack/oauth';
 import { GrowiCommandProcessor } from '~/interfaces/slack-to-growi/growi-command-processor';
 import { OrderRepository } from '~/repositories/order';
@@ -40,9 +40,9 @@ export class RegisterService implements GrowiCommandProcessor {
         private_metadata: JSON.stringify({ channel: body.channel_name }),
 
         blocks: [
-          generateInputSectionBlock('growiUrl', 'GROWI domain', 'contents_input', false, 'https://example.com'),
-          generateInputSectionBlock('tokenPtoG', 'Access Token Proxy to GROWI', 'contents_input', false, 'jBMZvpk.....'),
-          generateInputSectionBlock('tokenGtoP', 'Access Token GROWI to Proxy', 'contents_input', false, 'sdg15av.....'),
+          B.inputSectionBlock('growiUrl', 'GROWI domain', 'contents_input', false, 'https://example.com'),
+          B.inputSectionBlock('tokenPtoG', 'Access Token Proxy to GROWI', 'contents_input', false, 'jBMZvpk.....'),
+          B.inputSectionBlock('tokenGtoP', 'Access Token GROWI to Proxy', 'contents_input', false, 'sdg15av.....'),
         ],
       },
     });
@@ -81,7 +81,7 @@ export class RegisterService implements GrowiCommandProcessor {
     catch (error) {
       const invalidErrorMsg = 'Please enter a valid URL';
       const blocks = [
-        generateMarkdownSectionBlock(invalidErrorMsg),
+        B.markdownSectionBlock(invalidErrorMsg),
       ];
       await this.replyToSlack(client, channel, payload.user.id, 'Invalid URL', blocks);
       throw new InvalidUrlError(growiUrl);
@@ -105,7 +105,7 @@ export class RegisterService implements GrowiCommandProcessor {
 
     if (isOfficialMode) {
       const blocks = [
-        generateMarkdownSectionBlock('Successfully registered with the proxy! Please check test connection in your GROWI'),
+        B.markdownSectionBlock('Successfully registered with the proxy! Please check test connection in your GROWI'),
       ];
       await this.replyToSlack(client, channel, payload.user.id, 'Proxy URL', blocks);
       return;
@@ -113,8 +113,8 @@ export class RegisterService implements GrowiCommandProcessor {
     }
 
     const blocks = [
-      generateMarkdownSectionBlock('Please enter and update the following Proxy URL to slack bot setting form in your GROWI'),
-      generateMarkdownSectionBlock(`Proxy URL: ${serverUri}`),
+      B.markdownSectionBlock('Please enter and update the following Proxy URL to slack bot setting form in your GROWI'),
+      B.markdownSectionBlock(`Proxy URL: ${serverUri}`),
     ];
     await this.replyToSlack(client, channel, payload.user.id, 'Proxy URL', blocks);
     return;

+ 3 - 3
packages/slackbot-proxy/src/services/UnregisterService.ts

@@ -1,6 +1,6 @@
 import { Inject, Service } from '@tsed/di';
 import { WebClient, LogLevel } from '@slack/web-api';
-import { GrowiCommand, generateMarkdownSectionBlock } from '@growi/slack';
+import { GrowiCommand, BlockKitBuilder as B } from '@growi/slack';
 import { AuthorizeResult } from '@slack/oauth';
 import { GrowiCommandProcessor } from '~/interfaces/slack-to-growi/growi-command-processor';
 import { RelationRepository } from '~/repositories/relation';
@@ -38,7 +38,7 @@ export class UnregisterService implements GrowiCommandProcessor {
         private_metadata: JSON.stringify({ channel: body.channel_name, growiUrls }),
 
         blocks: [
-          ...growiUrls.map(growiCommandArg => generateMarkdownSectionBlock(`GROWI url: ${growiCommandArg}.`)),
+          ...growiUrls.map(growiCommandArg => B.markdownSectionBlock(`GROWI url: ${growiCommandArg}.`)),
         ],
       },
     });
@@ -63,7 +63,7 @@ export class UnregisterService implements GrowiCommandProcessor {
       // refer to https://api.slack.com/methods/chat.postEphemeral#text_usage
       text: 'Delete Relations',
       blocks: [
-        generateMarkdownSectionBlock(`Deleted ${deleteResult.affected} Relations.`),
+        B.markdownSectionBlock(`Deleted ${deleteResult.affected} Relations.`),
       ],
     });
 

+ 4 - 4
src/server/service/slack-command-handler/create.js

@@ -26,9 +26,9 @@ module.exports = () => {
             text: 'Cancel',
           },
           blocks: [
-            B.generateMarkdownSectionBlock('Create new page.'),
-            B.generateInputSectionBlock('path', 'Path', 'path_input', false, '/path'),
-            B.generateInputSectionBlock('contents', 'Contents', 'contents_input', true, 'Input with Markdown...'),
+            B.markdownSectionBlock('Create new page.'),
+            B.inputSectionBlock('path', 'Path', 'path_input', false, '/path'),
+            B.inputSectionBlock('contents', 'Contents', 'contents_input', true, 'Input with Markdown...'),
           ],
           private_metadata: JSON.stringify({ channelId: body.channel_id }),
         },
@@ -41,7 +41,7 @@ module.exports = () => {
         user: body.user_id,
         text: 'Failed To Create',
         blocks: [
-          B.generateMarkdownSectionBlock(`*Failed to create new page.*\n ${err}`),
+          B.markdownSectionBlock(`*Failed to create new page.*\n ${err}`),
         ],
       });
       throw err;

+ 1 - 1
src/server/service/slack-command-handler/help.js

@@ -11,7 +11,7 @@ module.exports = () => {
       user: body.user_id,
       text: 'Help',
       blocks: [
-        B.generateMarkdownSectionBlock(message),
+        B.markdownSectionBlock(message),
       ],
     });
   };

+ 13 - 13
src/server/service/slack-command-handler/search.js

@@ -21,7 +21,7 @@ module.exports = (crowi) => {
         user: body.user_id,
         text: 'Failed To Search',
         blocks: [
-          B.generateMarkdownSectionBlock('*Failed to search.*\n Hint\n `/growi search [keyword]`'),
+          B.markdownSectionBlock('*Failed to search.*\n Hint\n `/growi search [keyword]`'),
         ],
       });
       throw new Error('/growi command:search: Failed to search');
@@ -62,7 +62,7 @@ module.exports = (crowi) => {
 
     const now = new Date();
     const blocks = [
-      B.generateMarkdownSectionBlock(`:mag: <${decodeURI(appUrl)}|*${appTitle}*>\n${searchResultsDesc}`),
+      B.markdownSectionBlock(`:mag: <${decodeURI(appUrl)}|*${appTitle}*>\n${searchResultsDesc}`),
       contextBlock,
       { type: 'divider' },
       // create an array by map and extract
@@ -154,7 +154,7 @@ module.exports = (crowi) => {
         user: body.user_id,
         text: 'Failed to post ephemeral message.',
         blocks: [
-          B.generateMarkdownSectionBlock(err.toString()),
+          B.markdownSectionBlock(err.toString()),
         ],
       });
       throw new Error(err);
@@ -169,7 +169,7 @@ module.exports = (crowi) => {
         user: body.user_id,
         text: 'Input keywords',
         blocks: [
-          B.generateMarkdownSectionBlock('*Input keywords.*\n Hint\n `/growi search [keyword]`'),
+          B.markdownSectionBlock('*Input keywords.*\n Hint\n `/growi search [keyword]`'),
         ],
       });
       return;
@@ -190,22 +190,22 @@ module.exports = (crowi) => {
         user: body.user_id,
         text: `No page found with "${keywords}"`,
         blocks: [
-          B.generateMarkdownSectionBlock(`*No page that matches your keyword(s) "${keywords}".*`),
-          B.generateMarkdownSectionBlock(':mag: *Help: Searching*'),
+          B.markdownSectionBlock(`*No page that matches your keyword(s) "${keywords}".*`),
+          B.markdownSectionBlock(':mag: *Help: Searching*'),
           B.divider(),
-          B.generateMarkdownSectionBlock('`word1` `word2` (divide with space) \n Search pages that include both word1, word2 in the title or body'),
+          B.markdownSectionBlock('`word1` `word2` (divide with space) \n Search pages that include both word1, word2 in the title or body'),
           B.divider(),
-          B.generateMarkdownSectionBlock('`"This is GROWI"` (surround with double quotes) \n Search pages that include the phrase "This is GROWI"'),
+          B.markdownSectionBlock('`"This is GROWI"` (surround with double quotes) \n Search pages that include the phrase "This is GROWI"'),
           B.divider(),
-          B.generateMarkdownSectionBlock('`-keyword` \n Exclude pages that include keyword in the title or body'),
+          B.markdownSectionBlock('`-keyword` \n Exclude pages that include keyword in the title or body'),
           B.divider(),
-          B.generateMarkdownSectionBlock('`prefix:/user/` \n Search only the pages that the title start with /user/'),
+          B.markdownSectionBlock('`prefix:/user/` \n Search only the pages that the title start with /user/'),
           B.divider(),
-          B.generateMarkdownSectionBlock('`-prefix:/user/` \n Exclude the pages that the title start with /user/'),
+          B.markdownSectionBlock('`-prefix:/user/` \n Exclude the pages that the title start with /user/'),
           B.divider(),
-          B.generateMarkdownSectionBlock('`tag:wiki` \n Search for pages with wiki tag'),
+          B.markdownSectionBlock('`tag:wiki` \n Search for pages with wiki tag'),
           B.divider(),
-          B.generateMarkdownSectionBlock('`-tag:wiki` \n Exclude pages with wiki tag'),
+          B.markdownSectionBlock('`-tag:wiki` \n Exclude pages with wiki tag'),
         ],
       });
       return { pages: [] };

+ 3 - 158
src/server/service/slackbot.js

@@ -87,29 +87,12 @@ class SlackBotService extends S2sMessageHandlable {
       user: body.user_id,
       text: 'No command',
       blocks: [
-        B.generateMarkdownSectionBlock('*No command.*\n Hint\n `/growi [command] [keyword]`'),
+        B.markdownSectionBlock('*No command.*\n Hint\n `/growi [command] [keyword]`'),
       ],
     });
     return;
   }
 
-  async togetterCommand(client, body, args, limit = 10) {
-    // TODO GW-6721 Get the time from args
-    const reusult = await client.conversations.history({
-      channel: body.channel_id,
-      limit,
-    });
-    console.log(reusult);
-    // Checkbox Message を返す
-    client.chat.postEphemeral({
-      channel: body.channel_id,
-      user: body.user_id,
-      text: 'Select messages to use.',
-      blocks: this.togetterMessageBlocks(),
-    });
-    return;
-  }
-
   generatePageLinkMrkdwn(pathname, href) {
     return `<${decodeURI(href)} | ${decodeURI(pathname)}>`;
   }
@@ -149,7 +132,7 @@ class SlackBotService extends S2sMessageHandlable {
       channel: channelId,
       blocks: [
         { type: 'divider' },
-        B.generateMarkdownSectionBlock(`${this.appendSpeechBaloon(`*${this.generatePageLinkMrkdwn(pathname, href)}*`, commentCount)}`),
+        B.markdownSectionBlock(`${this.appendSpeechBaloon(`*${this.generatePageLinkMrkdwn(pathname, href)}*`, commentCount)}`),
         {
           type: 'context',
           elements: [
@@ -200,151 +183,13 @@ class SlackBotService extends S2sMessageHandlable {
       client.chat.postMessage({
         channel: payload.user.id,
         blocks: [
-          B.generateMarkdownSectionBlock(`Cannot create new page to existed path\n *Contents* :memo:\n ${contentsBody}`)],
+          B.markdownSectionBlock(`Cannot create new page to existed path\n *Contents* :memo:\n ${contentsBody}`)],
       });
       logger.error('Failed to create page in GROWI.');
       throw err;
     }
   }
 
-  generateMarkdownSectionBlock(text) {
-    return {
-      type: 'section',
-      text: {
-        type: 'mrkdwn',
-        text,
-      },
-    };
-  }
-
-  divider() {
-    return {
-      type: 'divider',
-    };
-  }
-
-  generateInputSectionBlock(blockId, labelText, actionId, isMultiline, placeholder) {
-    return {
-      type: 'input',
-      block_id: blockId,
-      label: {
-        type: 'plain_text',
-        text: labelText,
-      },
-      element: {
-        type: 'plain_text_input',
-        action_id: actionId,
-        multiline: isMultiline,
-        placeholder: {
-          type: 'plain_text',
-          text: placeholder,
-        },
-      },
-    };
-  }
-
-  togetterMessageBlocks() {
-    return [
-      this.inputBlock(this.togetterCheckboxesElement(), 'selected_messages', 'Select massages to use.'),
-      this.actionsBlock(this.buttonElement('Show more', 'togetterShowMore')),
-      this.inputBlock(this.togetterInputBlockElement('page_path', '/'), 'page_path', 'Page path'),
-      this.actionsBlock(this.buttonElement('Cancel', 'togetterCancelPageCreation'), this.buttonElement('Create page', 'togetterCreatePage', 'primary')),
-    ];
-  }
-
-  actionsBlock(...elements) {
-    return {
-      type: 'actions',
-      elements: [
-        ...elements,
-      ],
-    };
-  }
-
-  inputBlock(element, blockId, labelText) {
-    return {
-      type: 'input',
-      block_id: blockId,
-      element,
-      label: {
-        type: 'plain_text',
-        text: labelText,
-        emoji: true,
-      },
-    };
-  }
-
-  /**
-   * Button element
-   * https://api.slack.com/reference/block-kit/block-elements#button
-   */
-  buttonElement(text, actionId, style = undefined) {
-    const button = {
-      type: 'button',
-      text: {
-        type: 'plain_text',
-        text,
-      },
-      action_id: actionId,
-    };
-    if (style === 'primary' || style === 'danger') {
-      button.style = style;
-    }
-    return button;
-  }
-
-  togetterCheckboxesElement() {
-    return {
-      type: 'checkboxes',
-      options: this.togetterCheckboxesElementOptions(),
-      action_id: 'checkboxes_changed',
-    };
-  }
-
-  togetterCheckboxesElementOptions() {
-    // options を conversations.history の結果でインクリメント
-    const options = [];
-    // 仮置き
-    for (let i = 0; i < 10; i++) {
-      const option = this.checkboxesElementOption('*username*  12:00PM', 'sample slack messages ... :star:', `selected-${i}`);
-      options.push(option);
-    }
-    return options;
-  }
-
-  /**
-   * Option object
-   * https://api.slack.com/reference/block-kit/composition-objects#option
-   */
-  checkboxesElementOption(text, description, value) {
-    return {
-      text: {
-        type: 'mrkdwn',
-        text,
-      },
-      description: {
-        type: 'mrkdwn',
-        text: description,
-      },
-      value,
-    };
-  }
-
-  /**
-   * Plain-text input element
-   * https://api.slack.com/reference/block-kit/block-elements#input
-   */
-  togetterInputBlockElement(actionId, placeholderText = 'Write something ...') {
-    return {
-      type: 'plain_text_input',
-      placeholder: {
-        type: 'plain_text',
-        text: placeholderText,
-      },
-      action_id: actionId,
-    };
-  }
-
 }
 
 module.exports = SlackBotService;