Просмотр исходного кода

Merge pull request #4672 from weseek/master

Release v4.4.13
Yuki Takei 4 лет назад
Родитель
Сommit
bf40d818dc
30 измененных файлов с 209 добавлено и 61 удалено
  1. 1 1
      lerna.json
  2. 1 1
      package.json
  3. 7 7
      packages/app/package.json
  4. 1 1
      packages/app/resource/locales/en_US/admin/admin.json
  5. 1 1
      packages/app/resource/locales/ja_JP/admin/admin.json
  6. 1 1
      packages/app/resource/locales/zh_CN/admin/admin.json
  7. 14 0
      packages/app/resource/search/mappings.json
  8. 2 1
      packages/app/src/components/Admin/ElasticsearchManagement/ElasticsearchManagement.jsx
  9. 1 0
      packages/app/src/server/crowi/index.js
  10. 17 0
      packages/app/src/server/events/comment.ts
  11. 18 0
      packages/app/src/server/models/comment.js
  12. 5 2
      packages/app/src/server/routes/apiv3/slack-integration.js
  13. 8 0
      packages/app/src/server/routes/comment.js
  14. 36 4
      packages/app/src/server/service/search-delegator/elasticsearch.js
  15. 5 0
      packages/app/src/server/service/search.js
  16. 15 5
      packages/app/src/server/util/slack-integration.ts
  17. 1 1
      packages/codemirror-textlint/package.json
  18. 1 1
      packages/core/package.json
  19. 1 1
      packages/plugin-attachment-refs/package.json
  20. 1 1
      packages/plugin-lsx/package.json
  21. 1 1
      packages/plugin-pukiwiki-like-linker/package.json
  22. 1 1
      packages/slack/package.json
  23. 1 0
      packages/slack/src/index.ts
  24. 6 0
      packages/slack/src/interfaces/channel.ts
  25. 4 4
      packages/slack/src/utils/interaction-payload-accessor.ts
  26. 2 2
      packages/slackbot-proxy/package.json
  27. 15 5
      packages/slackbot-proxy/src/controllers/slack.ts
  28. 34 15
      packages/slackbot-proxy/src/services/RelationsService.ts
  29. 7 4
      packages/slackbot-proxy/src/services/SelectGrowiService.ts
  30. 1 1
      packages/ui/package.json

+ 1 - 1
lerna.json

@@ -1,7 +1,7 @@
 {
 {
   "npmClient": "yarn",
   "npmClient": "yarn",
   "useWorkspaces": true,
   "useWorkspaces": true,
-  "version": "4.4.12",
+  "version": "4.4.13-RC.0",
   "packages": [
   "packages": [
     "packages/*"
     "packages/*"
   ]
   ]

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "growi",
   "name": "growi",
-  "version": "4.4.12",
+  "version": "4.4.13-RC.0",
   "description": "Team collaboration software using markdown",
   "description": "Team collaboration software using markdown",
   "tags": [
   "tags": [
     "wiki",
     "wiki",

+ 7 - 7
packages/app/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/app",
   "name": "@growi/app",
-  "version": "4.4.12",
+  "version": "4.4.13-RC.0",
   "license": "MIT",
   "license": "MIT",
   "scripts": {
   "scripts": {
     "//// for production": "",
     "//// for production": "",
@@ -58,11 +58,11 @@
     "@browser-bunyan/console-formatted-stream": "^1.6.2",
     "@browser-bunyan/console-formatted-stream": "^1.6.2",
     "@godaddy/terminus": "^4.9.0",
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
     "@google-cloud/storage": "^5.8.5",
-    "@growi/codemirror-textlint": "^4.4.12",
-    "@growi/plugin-attachment-refs": "^4.4.12",
-    "@growi/plugin-lsx": "^4.4.12",
-    "@growi/plugin-pukiwiki-like-linker": "^4.4.12",
-    "@growi/slack": "^4.4.12",
+    "@growi/codemirror-textlint": "^4.4.13-RC.0",
+    "@growi/plugin-attachment-refs": "^4.4.13-RC.0",
+    "@growi/plugin-lsx": "^4.4.13-RC.0",
+    "@growi/plugin-pukiwiki-like-linker": "^4.4.13-RC.0",
+    "@growi/slack": "^4.4.13-RC.0",
     "@promster/express": "^5.1.0",
     "@promster/express": "^5.1.0",
     "@promster/server": "^6.0.3",
     "@promster/server": "^6.0.3",
     "@slack/events-api": "^3.0.0",
     "@slack/events-api": "^3.0.0",
@@ -158,7 +158,7 @@
     "@alienfast/i18next-loader": "^1.0.16",
     "@alienfast/i18next-loader": "^1.0.16",
     "@atlaskit/drawer": "^5.3.7",
     "@atlaskit/drawer": "^5.3.7",
     "@atlaskit/navigation-next": "^8.0.5",
     "@atlaskit/navigation-next": "^8.0.5",
-    "@growi/ui": "^4.4.12",
+    "@growi/ui": "^4.4.13-RC.0",
     "@handsontable/react": "=2.1.0",
     "@handsontable/react": "=2.1.0",
     "@types/compression": "^1.7.0",
     "@types/compression": "^1.7.0",
     "@types/express": "^4.17.11",
     "@types/express": "^4.17.11",

+ 1 - 1
packages/app/resource/locales/en_US/admin/admin.json

@@ -342,7 +342,7 @@
       "manage_commands": "Manage GROWI commands",
       "manage_commands": "Manage GROWI commands",
       "multiple_growi_command": "Commands that could be sent to multiple GROWI instances at once",
       "multiple_growi_command": "Commands that could be sent to multiple GROWI instances at once",
       "single_growi_command": "Commands that could be sent to single GROWI instance at a time",
       "single_growi_command": "Commands that could be sent to single GROWI instance at a time",
-      "allowed_channels_description": "Input allowed channels for \"{{commandName}}\" command. Separate each channel with \",\" . Users can will be able to use \"{{commandName}}\" command from channels written here.",
+      "allowed_channels_description": "Enter the allowed channel ID or channel name for \"{{commandName}}\" command. Only channel IDs are allowed for private channels. Separate each channel with \",\" . Users will be able to use \"{{commandName}}\" command from channels written here.",
       "allow_all": "Allow all",
       "allow_all": "Allow all",
       "deny_all": "Deny all",
       "deny_all": "Deny all",
       "allow_specified": "Allow specified",
       "allow_specified": "Allow specified",

+ 1 - 1
packages/app/resource/locales/ja_JP/admin/admin.json

@@ -341,7 +341,7 @@
       "manage_commands": "使用可能なGROWIコマンドを設定する",
       "manage_commands": "使用可能なGROWIコマンドを設定する",
       "multiple_growi_command": "複数のGROWIに対して送信できるコマンド",
       "multiple_growi_command": "複数のGROWIに対して送信できるコマンド",
       "single_growi_command": "一つのGROWIに対して送信できるコマンド",
       "single_growi_command": "一つのGROWIに対して送信できるコマンド",
-      "allowed_channels_description": "\"{{commandName}}\" コマンドの使用を許可するチャンネルを \",\" 区切りで入力してください。ユーザーはここに記入されているチャンネルから \"{{commandName}}\" コマンドを使用することができます。",
+      "allowed_channels_description": "\"{{commandName}}\" コマンドの使用を許可するチャンネルの ID またはチャンネル名を \",\" 区切りで入力してください。プライベートチャンネルの場合はチャンネル ID を入力する必要があります。ユーザーはここに記入されているチャンネルから \"{{commandName}}\" コマンドを使用することができます。",
       "allow_all": "全てのチャンネルを許可",
       "allow_all": "全てのチャンネルを許可",
       "deny_all": "全てのチャンネルを拒否",
       "deny_all": "全てのチャンネルを拒否",
       "allow_specified": "特定のチャンネルを許可",
       "allow_specified": "特定のチャンネルを許可",

+ 1 - 1
packages/app/resource/locales/zh_CN/admin/admin.json

@@ -351,7 +351,7 @@
       "manage_commands": "管理 GROWI 命令",
       "manage_commands": "管理 GROWI 命令",
       "multiple_growi_command": "可以一次发送到多个 GROWI 实例的命令",
       "multiple_growi_command": "可以一次发送到多个 GROWI 实例的命令",
       "single_growi_command": "可以一次发送到一个 GROWI 实例的命令",
       "single_growi_command": "可以一次发送到一个 GROWI 实例的命令",
-      "allowed_channels_description": "为 \"{{commandName}}\" 命令输入允许的通道。每个通道之间用 \",\" 隔开。用户可以从这里写入的通道中使用 \"{{commandName}}\"。",
+      "allowed_channels_description": "为 \"{{commandName}}\" 命令输入允许的频道ID或频道名称。对于私人频道,只允许输入频道ID。每个频道之间用\",\"隔开。用户可以从这里写的频道中使用 \"{{commandName}}\" 命令。",
       "allow_all": "允许所有",
       "allow_all": "允许所有",
       "deny_all": "拒绝所有",
       "deny_all": "拒绝所有",
       "allow_specified": "允许指定",
       "allow_specified": "允许指定",

+ 14 - 0
packages/app/resource/search/mappings.json

@@ -65,6 +65,20 @@
             }
             }
           }
           }
         },
         },
+        "comments": {
+          "type": "text",
+          "fields": {
+            "ja": {
+              "type": "text",
+              "analyzer": "japanese"
+            },
+            "en": {
+              "type": "text",
+              "analyzer": "english_edge_ngram",
+              "search_analyzer": "standard"
+            }
+          }
+        },
         "username": {
         "username": {
           "type": "keyword"
           "type": "keyword"
         },
         },

+ 2 - 1
packages/app/src/components/Admin/ElasticsearchManagement/ElasticsearchManagement.jsx

@@ -53,7 +53,8 @@ class ElasticsearchManagement extends React.Component {
       });
       });
     });
     });
 
 
-    socket.on('finishAddPage', (data) => {
+    socket.on('finishAddPage', async(data) => {
+      await this.retrieveIndicesStatus();
       this.setState({
       this.setState({
         isRebuildingProcessing: false,
         isRebuildingProcessing: false,
         isRebuildingCompleted: true,
         isRebuildingCompleted: true,

+ 1 - 0
packages/app/src/server/crowi/index.js

@@ -81,6 +81,7 @@ function Crowi() {
     user: new (require('../events/user'))(this),
     user: new (require('../events/user'))(this),
     page: new (require('../events/page'))(this),
     page: new (require('../events/page'))(this),
     bookmark: new (require('../events/bookmark'))(this),
     bookmark: new (require('../events/bookmark'))(this),
+    comment: new (require('../events/comment'))(this),
     tag: new (require('../events/tag'))(this),
     tag: new (require('../events/tag'))(this),
     admin: new (require('../events/admin'))(this),
     admin: new (require('../events/admin'))(this),
   };
   };

+ 17 - 0
packages/app/src/server/events/comment.ts

@@ -0,0 +1,17 @@
+
+import util from 'util';
+
+const events = require('events');
+
+function CommentEvent(crowi) {
+  this.crowi = crowi;
+
+  events.EventEmitter.call(this);
+}
+util.inherits(CommentEvent, events.EventEmitter);
+
+CommentEvent.prototype.onCreate = function(comment) {};
+CommentEvent.prototype.onUpdate = function(comment) {};
+CommentEvent.prototype.onDelete = function(comment) {};
+
+module.exports = CommentEvent;

+ 18 - 0
packages/app/src/server/models/comment.js

@@ -51,6 +51,24 @@ module.exports = function(crowi) {
     return this.find({ revision: id }).sort({ createdAt: -1 });
     return this.find({ revision: id }).sort({ createdAt: -1 });
   };
   };
 
 
+
+  /**
+   * @return {object} key: page._id, value: comments
+   */
+  commentSchema.statics.getPageIdToCommentMap = async function(pageIds) {
+    const results = await this.aggregate()
+      .match({ page: { $in: pageIds } })
+      .group({ _id: '$page', comments: { $push: '$comment' } });
+
+    // convert to map
+    const idToCommentMap = {};
+    results.forEach((result, i) => {
+      idToCommentMap[result._id] = result.comments;
+    });
+
+    return idToCommentMap;
+  };
+
   commentSchema.statics.countCommentByPageId = function(page) {
   commentSchema.statics.countCommentByPageId = function(page) {
     const self = this;
     const self = this;
 
 

+ 5 - 2
packages/app/src/server/routes/apiv3/slack-integration.js

@@ -95,7 +95,10 @@ module.exports = (crowi) => {
 
 
     const tokenPtoG = req.headers['x-growi-ptog-tokens'];
     const tokenPtoG = req.headers['x-growi-ptog-tokens'];
     const extractPermissions = await extractPermissionsCommands(tokenPtoG);
     const extractPermissions = await extractPermissionsCommands(tokenPtoG);
-    const fromChannel = req.body.channel_name;
+    const fromChannel = {
+      id: req.body.channel_id,
+      name: req.body.channel_name,
+    };
     const siteUrl = crowi.appService.getSiteUrl();
     const siteUrl = crowi.appService.getSiteUrl();
 
 
     let commandPermission;
     let commandPermission;
@@ -141,7 +144,7 @@ module.exports = (crowi) => {
 
 
     const { actionId, callbackId } = interactionPayloadAccessor.getActionIdAndCallbackIdFromPayLoad();
     const { actionId, callbackId } = interactionPayloadAccessor.getActionIdAndCallbackIdFromPayLoad();
     const callbacIdkOrActionId = callbackId || actionId;
     const callbacIdkOrActionId = callbackId || actionId;
-    const fromChannel = interactionPayloadAccessor.getChannelName();
+    const fromChannel = interactionPayloadAccessor.getChannel();
 
 
     const tokenPtoG = req.headers['x-growi-ptog-tokens'];
     const tokenPtoG = req.headers['x-growi-ptog-tokens'];
     const extractPermissions = await extractPermissionsCommands(tokenPtoG);
     const extractPermissions = await extractPermissionsCommands(tokenPtoG);

+ 8 - 0
packages/app/src/server/routes/comment.js

@@ -231,6 +231,7 @@ module.exports = function(crowi, app) {
     const position = commentForm.comment_position || -1;
     const position = commentForm.comment_position || -1;
     const isMarkdown = commentForm.is_markdown;
     const isMarkdown = commentForm.is_markdown;
     const replyTo = commentForm.replyTo;
     const replyTo = commentForm.replyTo;
+    const commentEvent = crowi.event('comment');
 
 
     // check whether accessible
     // check whether accessible
     const isAccessible = await Page.isAccessiblePageByViewer(pageId, req.user);
     const isAccessible = await Page.isAccessiblePageByViewer(pageId, req.user);
@@ -241,6 +242,7 @@ module.exports = function(crowi, app) {
     let createdComment;
     let createdComment;
     try {
     try {
       createdComment = await Comment.create(pageId, req.user._id, revisionId, comment, position, isMarkdown, replyTo);
       createdComment = await Comment.create(pageId, req.user._id, revisionId, comment, position, isMarkdown, replyTo);
+      commentEvent.emit('create', createdComment);
     }
     }
     catch (err) {
     catch (err) {
       logger.error(err);
       logger.error(err);
@@ -345,6 +347,8 @@ module.exports = function(crowi, app) {
     const commentId = commentForm.comment_id;
     const commentId = commentForm.comment_id;
     const revision = commentForm.revision_id;
     const revision = commentForm.revision_id;
 
 
+    const commentEvent = crowi.event('comment');
+
     if (commentStr === '') {
     if (commentStr === '') {
       return res.json(ApiResponse.error('Comment text is required'));
       return res.json(ApiResponse.error('Comment text is required'));
     }
     }
@@ -375,6 +379,7 @@ module.exports = function(crowi, app) {
         { _id: commentId },
         { _id: commentId },
         { $set: { comment: commentStr, isMarkdown, revision } },
         { $set: { comment: commentStr, isMarkdown, revision } },
       );
       );
+      commentEvent.emit('create', updatedComment);
     }
     }
     catch (err) {
     catch (err) {
       logger.error(err);
       logger.error(err);
@@ -428,6 +433,8 @@ module.exports = function(crowi, app) {
    * @apiParam {String} comment_id Comment Id.
    * @apiParam {String} comment_id Comment Id.
    */
    */
   api.remove = async function(req, res) {
   api.remove = async function(req, res) {
+    const commentEvent = crowi.event('comment');
+
     const commentId = req.body.comment_id;
     const commentId = req.body.comment_id;
     if (!commentId) {
     if (!commentId) {
       return Promise.resolve(res.json(ApiResponse.error('\'comment_id\' is undefined')));
       return Promise.resolve(res.json(ApiResponse.error('\'comment_id\' is undefined')));
@@ -452,6 +459,7 @@ module.exports = function(crowi, app) {
 
 
       await comment.removeWithReplies();
       await comment.removeWithReplies();
       await Page.updateCommentCount(comment.page);
       await Page.updateCommentCount(comment.page);
+      commentEvent.emit('delete', comment);
     }
     }
     catch (err) {
     catch (err) {
       return res.json(ApiResponse.error(err));
       return res.json(ApiResponse.error(err));

+ 36 - 4
packages/app/src/server/service/search-delegator/elasticsearch.js

@@ -314,6 +314,7 @@ class ElasticsearchDelegator {
       body: page.revision.body,
       body: page.revision.body,
       // username: page.creator?.username, // available Node.js v14 and above
       // username: page.creator?.username, // available Node.js v14 and above
       username: page.creator != null ? page.creator.username : null,
       username: page.creator != null ? page.creator.username : null,
+      comments: page.comments,
       comment_count: page.commentCount,
       comment_count: page.commentCount,
       bookmark_count: bookmarkCount,
       bookmark_count: bookmarkCount,
       like_count: page.liker.length || 0,
       like_count: page.liker.length || 0,
@@ -371,6 +372,7 @@ class ElasticsearchDelegator {
     const Page = mongoose.model('Page');
     const Page = mongoose.model('Page');
     const { PageQueryBuilder } = Page;
     const { PageQueryBuilder } = Page;
     const Bookmark = mongoose.model('Bookmark');
     const Bookmark = mongoose.model('Bookmark');
+    const Comment = mongoose.model('Comment');
     const PageTagRelation = mongoose.model('PageTagRelation');
     const PageTagRelation = mongoose.model('PageTagRelation');
 
 
     const socket = this.socketIoService.getAdminSocket();
     const socket = this.socketIoService.getAdminSocket();
@@ -431,6 +433,28 @@ class ElasticsearchDelegator {
       },
       },
     });
     });
 
 
+
+    const appendCommentStream = new Transform({
+      objectMode: true,
+      async transform(chunk, encoding, callback) {
+        const pageIds = chunk.map(doc => doc._id);
+
+        const idToCommentMap = await Comment.getPageIdToCommentMap(pageIds);
+        const idsHavingComment = Object.keys(idToCommentMap);
+
+        // append comments
+        chunk
+          .filter(doc => idsHavingComment.includes(doc._id.toString()))
+          .forEach((doc) => {
+            // append comments from idToCommentMap
+            doc.comments = idToCommentMap[doc._id.toString()];
+          });
+
+        this.push(chunk);
+        callback();
+      },
+    });
+
     const appendTagNamesStream = new Transform({
     const appendTagNamesStream = new Transform({
       objectMode: true,
       objectMode: true,
       async transform(chunk, encoding, callback) {
       async transform(chunk, encoding, callback) {
@@ -503,6 +527,7 @@ class ElasticsearchDelegator {
       .pipe(thinOutStream)
       .pipe(thinOutStream)
       .pipe(batchStream)
       .pipe(batchStream)
       .pipe(appendBookmarkCountStream)
       .pipe(appendBookmarkCountStream)
+      .pipe(appendCommentStream)
       .pipe(appendTagNamesStream)
       .pipe(appendTagNamesStream)
       .pipe(writeStream);
       .pipe(writeStream);
 
 
@@ -579,7 +604,7 @@ class ElasticsearchDelegator {
   }
   }
 
 
   createSearchQuerySortedByScore(option) {
   createSearchQuerySortedByScore(option) {
-    let fields = ['path', 'bookmark_count', 'comment_count', 'updated_at', 'tag_names'];
+    let fields = ['path', 'bookmark_count', 'comment_count', 'updated_at', 'tag_names', 'comments'];
     if (option) {
     if (option) {
       fields = option.fields || fields;
       fields = option.fields || fields;
     }
     }
@@ -635,7 +660,7 @@ class ElasticsearchDelegator {
         multi_match: {
         multi_match: {
           query: parsedKeywords.match.join(' '),
           query: parsedKeywords.match.join(' '),
           type: 'most_fields',
           type: 'most_fields',
-          fields: ['path.ja^2', 'path.en^2', 'body.ja', 'body.en'],
+          fields: ['path.ja^2', 'path.en^2', 'body.ja', 'body.en', 'comments.ja', 'comments.en'],
         },
         },
       };
       };
       query.body.query.bool.must.push(q);
       query.body.query.bool.must.push(q);
@@ -645,7 +670,7 @@ class ElasticsearchDelegator {
       const q = {
       const q = {
         multi_match: {
         multi_match: {
           query: parsedKeywords.not_match.join(' '),
           query: parsedKeywords.not_match.join(' '),
-          fields: ['path.ja', 'path.en', 'body.ja', 'body.en'],
+          fields: ['path.ja', 'path.en', 'body.ja', 'body.en', 'comments.ja', 'comments.en'],
           operator: 'or',
           operator: 'or',
         },
         },
       };
       };
@@ -657,12 +682,13 @@ class ElasticsearchDelegator {
       parsedKeywords.phrase.forEach((phrase) => {
       parsedKeywords.phrase.forEach((phrase) => {
         phraseQueries.push({
         phraseQueries.push({
           multi_match: {
           multi_match: {
-            query: phrase, // each phrase is quoteted words
+            query: phrase, // each phrase is quoteted words like "This is GROWI"
             type: 'phrase',
             type: 'phrase',
             fields: [
             fields: [
               // Not use "*.ja" fields here, because we want to analyze (parse) search words
               // Not use "*.ja" fields here, because we want to analyze (parse) search words
               'path.raw^2',
               'path.raw^2',
               'body',
               'body',
+              'comments',
             ],
             ],
           },
           },
         });
         });
@@ -1023,6 +1049,12 @@ class ElasticsearchDelegator {
     return this.updateOrInsertPageById(pageId);
     return this.updateOrInsertPageById(pageId);
   }
   }
 
 
+  async syncCommentChanged(comment) {
+    logger.debug('SearchClient.syncCommentChanged', comment);
+
+    return this.updateOrInsertPageById(comment.page);
+  }
+
   async syncTagChanged(page) {
   async syncTagChanged(page) {
     logger.debug('SearchClient.syncTagChanged', page.path);
     logger.debug('SearchClient.syncTagChanged', page.path);
 
 

+ 5 - 0
packages/app/src/server/service/search.js

@@ -73,6 +73,11 @@ class SearchService {
     bookmarkEvent.on('create', this.delegator.syncBookmarkChanged.bind(this.delegator));
     bookmarkEvent.on('create', this.delegator.syncBookmarkChanged.bind(this.delegator));
     bookmarkEvent.on('delete', this.delegator.syncBookmarkChanged.bind(this.delegator));
     bookmarkEvent.on('delete', this.delegator.syncBookmarkChanged.bind(this.delegator));
 
 
+    const commentEvent = this.crowi.event('comment');
+    commentEvent.on('create', this.delegator.syncCommentChanged.bind(this.delegator));
+    commentEvent.on('update', this.delegator.syncCommentChanged.bind(this.delegator));
+    commentEvent.on('delete', this.delegator.syncCommentChanged.bind(this.delegator));
+
     const tagEvent = this.crowi.event('tag');
     const tagEvent = this.crowi.event('tag');
     tagEvent.on('update', this.delegator.syncTagChanged.bind(this.delegator));
     tagEvent.on('update', this.delegator.syncTagChanged.bind(this.delegator));
   }
   }

+ 15 - 5
packages/app/src/server/util/slack-integration.ts

@@ -1,9 +1,9 @@
-import { getSupportedGrowiActionsRegExp } from '@growi/slack';
+import { getSupportedGrowiActionsRegExp, IChannelOptionalId } from '@growi/slack';
 
 
 type CommandPermission = { [key:string]: string[] | boolean }
 type CommandPermission = { [key:string]: string[] | boolean }
 
 
 export const checkPermission = (
 export const checkPermission = (
-    commandPermission:CommandPermission, commandOrActionIdOrCallbackId:string, fromChannel:string,
+    commandPermission: CommandPermission, commandOrActionIdOrCallbackId: string, fromChannel: IChannelOptionalId,
 ):boolean => {
 ):boolean => {
   let isPermitted = false;
   let isPermitted = false;
 
 
@@ -23,9 +23,19 @@ export const checkPermission = (
       isPermitted = true;
       isPermitted = true;
       return;
       return;
     }
     }
-    if (Array.isArray(permission) && permission.includes(fromChannel)) {
-      isPermitted = true;
-      return;
+
+    if (Array.isArray(permission)) {
+      if (permission.includes(fromChannel.name)) {
+        isPermitted = true;
+        return;
+      }
+
+      if (fromChannel.id == null) return;
+
+      if (permission.includes(fromChannel.id)) {
+        isPermitted = true;
+        return;
+      }
     }
     }
   });
   });
 
 

+ 1 - 1
packages/codemirror-textlint/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/codemirror-textlint",
   "name": "@growi/codemirror-textlint",
-  "version": "4.4.12",
+  "version": "4.4.13-RC.0",
   "license": "MIT",
   "license": "MIT",
   "main": "dist/index.js",
   "main": "dist/index.js",
   "scripts": {
   "scripts": {

+ 1 - 1
packages/core/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/core",
   "name": "@growi/core",
-  "version": "4.4.12",
+  "version": "4.4.13-RC.0",
   "description": "GROWI Core Libraries",
   "description": "GROWI Core Libraries",
   "license": "MIT",
   "license": "MIT",
   "keywords": [
   "keywords": [

+ 1 - 1
packages/plugin-attachment-refs/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/plugin-attachment-refs",
   "name": "@growi/plugin-attachment-refs",
-  "version": "4.4.12",
+  "version": "4.4.13-RC.0",
   "description": "GROWI Plugin to add ref/refimg/refs/refsimg tags",
   "description": "GROWI Plugin to add ref/refimg/refs/refsimg tags",
   "license": "MIT",
   "license": "MIT",
   "keywords": [
   "keywords": [

+ 1 - 1
packages/plugin-lsx/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/plugin-lsx",
   "name": "@growi/plugin-lsx",
-  "version": "4.4.12",
+  "version": "4.4.13-RC.0",
   "description": "GROWI plugin to list pages",
   "description": "GROWI plugin to list pages",
   "license": "MIT",
   "license": "MIT",
   "keywords": [
   "keywords": [

+ 1 - 1
packages/plugin-pukiwiki-like-linker/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/plugin-pukiwiki-like-linker",
   "name": "@growi/plugin-pukiwiki-like-linker",
-  "version": "4.4.12",
+  "version": "4.4.13-RC.0",
   "description": "GROWI plugin to add PukiwikiLikeLinker",
   "description": "GROWI plugin to add PukiwikiLikeLinker",
   "license": "MIT",
   "license": "MIT",
   "keywords": [
   "keywords": [

+ 1 - 1
packages/slack/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/slack",
   "name": "@growi/slack",
-  "version": "4.4.12",
+  "version": "4.4.13-RC.0",
   "license": "MIT",
   "license": "MIT",
   "main": "dist/index.js",
   "main": "dist/index.js",
   "typings": "dist/index.d.ts",
   "typings": "dist/index.d.ts",

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

@@ -22,6 +22,7 @@ export const defaultSupportedCommandsNameForSingleUse: string[] = [
   'keep',
   'keep',
 ];
 ];
 
 
+export * from './interfaces/channel';
 export * from './interfaces/growi-command-processor';
 export * from './interfaces/growi-command-processor';
 export * from './interfaces/growi-interaction-processor';
 export * from './interfaces/growi-interaction-processor';
 export * from './interfaces/growi-command';
 export * from './interfaces/growi-command';

+ 6 - 0
packages/slack/src/interfaces/channel.ts

@@ -0,0 +1,6 @@
+export type IChannel = {
+  id: string,
+  name: string,
+}
+
+export type IChannelOptionalId = Omit<IChannel, 'id'> & Partial<IChannel>;

+ 4 - 4
packages/slack/src/utils/interaction-payload-accessor.ts

@@ -1,5 +1,6 @@
 import assert from 'assert';
 import assert from 'assert';
 import { IInteractionPayloadAccessor } from '../interfaces/request-from-slack';
 import { IInteractionPayloadAccessor } from '../interfaces/request-from-slack';
+import { IChannel } from '../interfaces/channel';
 import loggerFactory from './logger';
 import loggerFactory from './logger';
 
 
 const logger = loggerFactory('@growi/slack:utils:interaction-payload-accessor');
 const logger = loggerFactory('@growi/slack:utils:interaction-payload-accessor');
@@ -68,16 +69,15 @@ export class InteractionPayloadAccessor implements IInteractionPayloadAccessor {
     return { actionId, callbackId };
     return { actionId, callbackId };
   }
   }
 
 
-  getChannelName(): string | null {
+  getChannel(): IChannel | null {
     // private_metadata should have the channelName parameter when view_submission
     // private_metadata should have the channelName parameter when view_submission
     const privateMetadata = this.getViewPrivateMetaData();
     const privateMetadata = this.getViewPrivateMetaData();
     if (privateMetadata != null && privateMetadata.channelName != null) {
     if (privateMetadata != null && privateMetadata.channelName != null) {
-      return privateMetadata.channelName;
+      throw new Error('PrivateMetaDatas are not implemented after removal of modal from slash commands. Use payload instead.');
     }
     }
-
     const channel = this.payload.channel;
     const channel = this.payload.channel;
     if (channel != null) {
     if (channel != null) {
-      return this.payload.channel.name;
+      return channel;
     }
     }
 
 
     return null;
     return null;

+ 2 - 2
packages/slackbot-proxy/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/slackbot-proxy",
   "name": "@growi/slackbot-proxy",
-  "version": "4.4.12",
+  "version": "4.4.13-slackbot-proxy.0",
   "license": "MIT",
   "license": "MIT",
   "scripts": {
   "scripts": {
     "build": "yarn tsc && tsc-alias -p tsconfig.build.json",
     "build": "yarn tsc && tsc-alias -p tsconfig.build.json",
@@ -25,7 +25,7 @@
   },
   },
   "dependencies": {
   "dependencies": {
     "@godaddy/terminus": "^4.9.0",
     "@godaddy/terminus": "^4.9.0",
-    "@growi/slack": "^4.4.12",
+    "@growi/slack": "^4.4.13-RC.0",
     "@slack/oauth": "^2.0.1",
     "@slack/oauth": "^2.0.1",
     "@slack/web-api": "^6.2.4",
     "@slack/web-api": "^6.2.4",
     "@tsed/common": "^6.43.0",
     "@tsed/common": "^6.43.0",

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

@@ -12,7 +12,7 @@ import {
   markdownSectionBlock, GrowiCommand, parseSlashCommand, respondRejectedErrors, generateWebClient,
   markdownSectionBlock, GrowiCommand, parseSlashCommand, respondRejectedErrors, generateWebClient,
   InvalidGrowiCommandError, requiredScopes, REQUEST_TIMEOUT_FOR_PTOG,
   InvalidGrowiCommandError, requiredScopes, REQUEST_TIMEOUT_FOR_PTOG,
   parseSlackInteractionRequest, verifySlackRequest,
   parseSlackInteractionRequest, verifySlackRequest,
-  respond, supportedGrowiCommands,
+  respond, supportedGrowiCommands, IChannelOptionalId,
 } from '@growi/slack';
 } from '@growi/slack';
 
 
 import { Relation } from '~/entities/relation';
 import { Relation } from '~/entities/relation';
@@ -227,16 +227,21 @@ export class SlackCtrl {
     const allowedRelationsForBroadcastUse:Relation[] = [];
     const allowedRelationsForBroadcastUse:Relation[] = [];
     const disallowedGrowiUrls: Set<string> = new Set();
     const disallowedGrowiUrls: Set<string> = new Set();
 
 
+    const channel: IChannelOptionalId = {
+      id: body.channel_id,
+      name: body.channel_name,
+    };
+
     // check permission
     // check permission
     await Promise.all(relations.map(async(relation) => {
     await Promise.all(relations.map(async(relation) => {
       const isSupportedForSingleUse = await this.relationsService.isPermissionsForSingleUseCommands(
       const isSupportedForSingleUse = await this.relationsService.isPermissionsForSingleUseCommands(
-        relation, growiCommand.growiCommandType, body.channel_name,
+        relation, growiCommand.growiCommandType, channel,
       );
       );
 
 
       let isSupportedForBroadcastUse = false;
       let isSupportedForBroadcastUse = false;
       if (!isSupportedForSingleUse) {
       if (!isSupportedForSingleUse) {
         isSupportedForBroadcastUse = await this.relationsService.isPermissionsUseBroadcastCommands(
         isSupportedForBroadcastUse = await this.relationsService.isPermissionsUseBroadcastCommands(
-          relation, growiCommand.growiCommandType, body.channel_name,
+          relation, growiCommand.growiCommandType, channel,
         );
         );
       }
       }
 
 
@@ -346,8 +351,13 @@ export class SlackCtrl {
 
 
     const privateMeta = interactionPayloadAccessor.getViewPrivateMetaData();
     const privateMeta = interactionPayloadAccessor.getViewPrivateMetaData();
 
 
-    const channelName = interactionPayload.channel?.name || privateMeta?.body?.channel_name || privateMeta?.channelName;
-    const permission = await this.relationsService.checkPermissionForInteractions(relations, actionId, callbackId, channelName);
+    const channelFromMeta = {
+      name: privateMeta?.body?.channel_name || privateMeta?.channelName,
+    };
+
+    const channel: IChannelOptionalId = interactionPayload.channel || channelFromMeta;
+    const permission = await this.relationsService.checkPermissionForInteractions(relations, actionId, callbackId, channel);
+
     const {
     const {
       allowedRelations, disallowedGrowiUrls, commandName, rejectedResults,
       allowedRelations, disallowedGrowiUrls, commandName, rejectedResults,
     } = permission;
     } = permission;

+ 34 - 15
packages/slackbot-proxy/src/services/RelationsService.ts

@@ -3,7 +3,7 @@ import { Inject, Service } from '@tsed/di';
 import axios from 'axios';
 import axios from 'axios';
 import { addHours } from 'date-fns';
 import { addHours } from 'date-fns';
 
 
-import { REQUEST_TIMEOUT_FOR_PTOG, getSupportedGrowiActionsRegExp } from '@growi/slack';
+import { REQUEST_TIMEOUT_FOR_PTOG, getSupportedGrowiActionsRegExp, IChannelOptionalId } from '@growi/slack';
 import { Relation, PermissionSettingsInterface } from '~/entities/relation';
 import { Relation, PermissionSettingsInterface } from '~/entities/relation';
 import { RelationRepository } from '~/repositories/relation';
 import { RelationRepository } from '~/repositories/relation';
 
 
@@ -24,6 +24,7 @@ type CheckEachRelationResult = {
   eachRelationCommandName:string,
   eachRelationCommandName:string,
 }
 }
 
 
+
 @Service()
 @Service()
 export class RelationsService {
 export class RelationsService {
 
 
@@ -76,7 +77,7 @@ export class RelationsService {
     return relation;
     return relation;
   }
   }
 
 
-  private isPermitted(permissionSettings: PermissionSettingsInterface, growiCommandType: string, channelName: string): boolean {
+  private isPermitted(permissionSettings: PermissionSettingsInterface, growiCommandType: string, channel: IChannelOptionalId): boolean {
     // TODO assert (permissionSettings != null)
     // TODO assert (permissionSettings != null)
 
 
     const permissionForCommand = permissionSettings[growiCommandType];
     const permissionForCommand = permissionSettings[growiCommandType];
@@ -86,13 +87,23 @@ export class RelationsService {
     }
     }
 
 
     if (Array.isArray(permissionForCommand)) {
     if (Array.isArray(permissionForCommand)) {
-      return permissionForCommand.includes(channelName);
+      if (permissionForCommand.includes(channel.name)) {
+        return true;
+      }
+
+      if (channel.id == null) {
+        return false;
+      }
+
+      if (permissionForCommand.includes(channel.id)) {
+        return true;
+      }
     }
     }
+    return permissionForCommand as boolean;
 
 
-    return permissionForCommand;
   }
   }
 
 
-  async isPermissionsForSingleUseCommands(relation: Relation, growiCommandType: string, channelName: string): Promise<boolean> {
+  async isPermissionsForSingleUseCommands(relation: Relation, growiCommandType: string, channel: IChannelOptionalId): Promise<boolean> {
     // TODO assert (relation != null)
     // TODO assert (relation != null)
     if (relation == null) {
     if (relation == null) {
       return false;
       return false;
@@ -110,10 +121,10 @@ export class RelationsService {
 
 
     // TODO assert (relationToEval.permissionsForSingleUseCommands != null) because syncRelation success
     // TODO assert (relationToEval.permissionsForSingleUseCommands != null) because syncRelation success
 
 
-    return this.isPermitted(relationToEval.permissionsForSingleUseCommands, growiCommandType, channelName);
+    return this.isPermitted(relationToEval.permissionsForSingleUseCommands, growiCommandType, channel);
   }
   }
 
 
-  async isPermissionsUseBroadcastCommands(relation: Relation, growiCommandType: string, channelName: string):Promise<boolean> {
+  async isPermissionsUseBroadcastCommands(relation: Relation, growiCommandType: string, channel: IChannelOptionalId):Promise<boolean> {
     // TODO assert (relation != null)
     // TODO assert (relation != null)
     if (relation == null) {
     if (relation == null) {
       return false;
       return false;
@@ -131,11 +142,11 @@ export class RelationsService {
 
 
     // TODO assert (relationToEval.permissionsForSingleUseCommands != null) because syncRelation success
     // TODO assert (relationToEval.permissionsForSingleUseCommands != null) because syncRelation success
 
 
-    return this.isPermitted(relationToEval.permissionsForBroadcastUseCommands, growiCommandType, channelName);
+    return this.isPermitted(relationToEval.permissionsForBroadcastUseCommands, growiCommandType, channel);
   }
   }
 
 
   async checkPermissionForInteractions(
   async checkPermissionForInteractions(
-      relations:Relation[], actionId:string, callbackId:string, channelName:string,
+      relations: Relation[], actionId: string, callbackId: string, channel: IChannelOptionalId,
   ):Promise<CheckPermissionForInteractionsResults> {
   ):Promise<CheckPermissionForInteractionsResults> {
 
 
     const allowedRelations:Relation[] = [];
     const allowedRelations:Relation[] = [];
@@ -143,7 +154,7 @@ export class RelationsService {
     let commandName = '';
     let commandName = '';
 
 
     const results = await Promise.allSettled(relations.map((relation) => {
     const results = await Promise.allSettled(relations.map((relation) => {
-      const relationResult = this.checkEachRelation(relation, actionId, callbackId, channelName);
+      const relationResult = this.checkEachRelation(relation, actionId, callbackId, channel);
       const { allowedRelation, disallowedGrowiUrl, eachRelationCommandName } = relationResult;
       const { allowedRelation, disallowedGrowiUrl, eachRelationCommandName } = relationResult;
 
 
       if (allowedRelation != null) {
       if (allowedRelation != null) {
@@ -164,8 +175,7 @@ export class RelationsService {
     };
     };
   }
   }
 
 
-  checkEachRelation(relation:Relation, actionId:string, callbackId:string, channelName:string):CheckEachRelationResult {
-
+  checkEachRelation(relation:Relation, actionId:string, callbackId:string, channel: IChannelOptionalId): CheckEachRelationResult {
     let allowedRelation:Relation|null = null;
     let allowedRelation:Relation|null = null;
     let disallowedGrowiUrl:string|null = null;
     let disallowedGrowiUrl:string|null = null;
     let eachRelationCommandName = '';
     let eachRelationCommandName = '';
@@ -198,9 +208,18 @@ export class RelationsService {
       }
       }
 
 
       // check permission at channel level
       // check permission at channel level
-      if (Array.isArray(permissionForInteractions) && permissionForInteractions.includes(channelName)) {
-        allowedRelation = relation;
-        return;
+      if (Array.isArray(permissionForInteractions)) {
+        if (permissionForInteractions.includes(channel.name)) {
+          allowedRelation = relation;
+          return;
+        }
+
+        if (channel.id == null) return;
+
+        if (permissionForInteractions.includes(channel.id)) {
+          allowedRelation = relation;
+          return;
+        }
       }
       }
 
 
       disallowedGrowiUrl = relation.growiUri;
       disallowedGrowiUrl = relation.growiUri;

+ 7 - 4
packages/slackbot-proxy/src/services/SelectGrowiService.ts

@@ -28,6 +28,8 @@ type SendCommandBody = {
   // eslint-disable-next-line camelcase
   // eslint-disable-next-line camelcase
   trigger_id: string,
   trigger_id: string,
   // eslint-disable-next-line camelcase
   // eslint-disable-next-line camelcase
+  channel_id: string,
+  // eslint-disable-next-line camelcase
   channel_name: string,
   channel_name: string,
 }
 }
 
 
@@ -209,9 +211,9 @@ export class SelectGrowiService implements GrowiCommandProcessor<SelectGrowiComm
     }
     }
 
 
     // increment sendCommandBody
     // increment sendCommandBody
-    const channelName = interactionPayloadAccessor.getChannelName();
-    if (channelName == null) {
-      logger.error('Growi command failed: channelName not found.');
+    const channel = interactionPayloadAccessor.getChannel();
+    if (channel == null) {
+      logger.error('Growi command failed: channel not found.');
       await respond(responseUrl, {
       await respond(responseUrl, {
         text: 'Growi command failed',
         text: 'Growi command failed',
         blocks: [
         blocks: [
@@ -222,7 +224,8 @@ export class SelectGrowiService implements GrowiCommandProcessor<SelectGrowiComm
     }
     }
     const sendCommandBody: SendCommandBody = {
     const sendCommandBody: SendCommandBody = {
       trigger_id: interactionPayload.trigger_id,
       trigger_id: interactionPayload.trigger_id,
-      channel_name: channelName,
+      channel_id: channel.id,
+      channel_name: channel.name,
     };
     };
 
 
     return {
     return {

+ 1 - 1
packages/ui/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/ui",
   "name": "@growi/ui",
-  "version": "4.4.12",
+  "version": "4.4.13-RC.0",
   "description": "GROWI UI Libraries",
   "description": "GROWI UI Libraries",
   "license": "MIT",
   "license": "MIT",
   "keywords": [
   "keywords": [