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

Merge pull request #5300 from weseek/feat/gw7697-linking-growi-user-with-slack-id

feat: gw7697 linking growi user with slack
Mudana-Grune 4 лет назад
Родитель
Сommit
b5e1724d6c

+ 11 - 0
packages/app/src/client/services/PersonalContainer.js

@@ -30,6 +30,7 @@ export default class PersonalContainer extends Container {
       uploadedPictureSrc: this.getUploadedPictureSrc(this.appContainer.currentUser),
       externalAccounts: [],
       apiToken: '',
+      slackId: '',
     };
 
   }
@@ -55,6 +56,7 @@ export default class PersonalContainer extends Container {
         lang: currentUser.lang,
         isGravatarEnabled: currentUser.isGravatarEnabled,
         apiToken: currentUser.apiToken,
+        slackId: currentUser.slackId,
       });
     }
     catch (err) {
@@ -114,6 +116,13 @@ export default class PersonalContainer extends Container {
     this.setState({ email: inputValue });
   }
 
+  /**
+   * Change Slack ID
+   */
+  changeSlackId(inputValue) {
+    this.setState({ slackId: inputValue });
+  }
+
   /**
    * Change isEmailPublished
    */
@@ -147,6 +156,7 @@ export default class PersonalContainer extends Container {
         email: this.state.email,
         isEmailPublished: this.state.isEmailPublished,
         lang: this.state.lang,
+        slackId: this.state.slackId,
       });
       const { updatedUser } = response.data;
 
@@ -155,6 +165,7 @@ export default class PersonalContainer extends Container {
         email: updatedUser.email,
         isEmailPublished: updatedUser.isEmailPublished,
         lang: updatedUser.lang,
+        slackId: updatedUser.slackId,
       });
     }
     catch (err) {

+ 13 - 0
packages/app/src/components/Me/BasicInfoSettings.jsx

@@ -128,6 +128,19 @@ class BasicInfoSettings extends React.Component {
             }
           </div>
         </div>
+        <div className="form-group row">
+          <label htmlFor="userForm[slackId]" className="text-left text-md-right col-md-3 col-form-label">{t('Slack ID')}</label>
+          <div className="col-md-6">
+            <input
+              className="form-control"
+              type="text"
+              key={personalContainer.state.slackId}
+              name="userForm[slackId]"
+              defaultValue={personalContainer.state.slackId}
+              onChange={(e) => { personalContainer.changeSlackId(e.target.value) }}
+            />
+          </div>
+        </div>
 
         <div className="row my-3">
           <div className="offset-4 col-5">

+ 17 - 0
packages/app/src/server/models/user.js

@@ -47,6 +47,7 @@ module.exports = function(crowi) {
     name: { type: String },
     username: { type: String, required: true, unique: true },
     email: { type: String, unique: true, sparse: true },
+    slackId: { type: String },
     // === Crowi settings
     // username: { type: String, index: true },
     // email: { type: String, required: true, index: true },
@@ -689,6 +690,22 @@ module.exports = function(crowi) {
     user.save();
   };
 
+  userSchema.statics.findUserBySlackId = async function(slackId) {
+    const user = this.findOne({ slackId });
+    if (!user) {
+      throw new Error('User not found');
+    }
+    return user;
+  };
+
+  userSchema.statics.findUsersBySlackIds = async function(slackIds) {
+    const users = this.find({ slackId: { $in: slackIds } });
+    if (!users) {
+      throw new Error('No user found');
+    }
+    return users;
+  };
+
   class UserUpperLimitException {
 
     constructor() {

+ 2 - 0
packages/app/src/server/routes/apiv3/personal-setting.js

@@ -79,6 +79,7 @@ module.exports = (crowi) => {
       body('email').isEmail(),
       body('lang').isString().isIn(listLocaleIds()),
       body('isEmailPublished').isBoolean(),
+      body('slackId').optional().isString(),
     ],
     imageType: [
       body('isGravatarEnabled').isBoolean(),
@@ -221,6 +222,7 @@ module.exports = (crowi) => {
       user.email = req.body.email;
       user.lang = req.body.lang;
       user.isEmailPublished = req.body.isEmailPublished;
+      user.slackId = req.body.slackId;
 
       const updatedUser = await user.save();
       req.i18n.changeLanguage(req.body.lang);

+ 2 - 2
packages/app/src/server/service/slack-command-handler/create-page-service.js

@@ -11,7 +11,7 @@ class CreatePageService {
     this.crowi = crowi;
   }
 
-  async createPageInGrowi(interactionPayloadAccessor, path, contentsBody, respondUtil) {
+  async createPageInGrowi(interactionPayloadAccessor, path, contentsBody, respondUtil, userId) {
     const Page = this.crowi.model('Page');
     const reshapedContentsBody = reshapeContentsBody(contentsBody);
 
@@ -20,7 +20,7 @@ class CreatePageService {
     const normalizedPath = pathUtils.normalizePath(sanitizedPath);
 
     // generate a dummy id because Operation to create a page needs ObjectId
-    const dummyObjectIdOfUser = new mongoose.Types.ObjectId();
+    const dummyObjectIdOfUser = userId != null ? userId : new mongoose.Types.ObjectId();
     const page = await Page.create(normalizedPath, reshapedContentsBody, dummyObjectIdOfUser, {});
 
     // Send a message when page creation is complete

+ 37 - 4
packages/app/src/server/service/slack-command-handler/keep.js

@@ -12,6 +12,7 @@ module.exports = (crowi) => {
   const createPageService = new CreatePageService(crowi);
   const BaseSlackCommandHandler = require('./slack-command-handler');
   const handler = new BaseSlackCommandHandler();
+  const { User } = crowi.models;
 
   handler.handleCommand = async function(growiCommand, client, body, respondUtil) {
     await respondUtil.respond({
@@ -32,8 +33,9 @@ module.exports = (crowi) => {
   handler.createPage = async function(client, payload, interactionPayloadAccessor, respondUtil) {
     let result = [];
     const channelId = payload.channel.id; // this must exist since the type is always block_actions
-    const userChannelId = payload.user.id;
+    const user = await User.findUserBySlackId(payload.user.id);
 
+    const userId = user != null ? user._id : null;
     // validate form
     const { path, oldest, newest } = await this.keepValidateForm(client, payload, interactionPayloadAccessor);
     // get messages
@@ -43,7 +45,7 @@ module.exports = (crowi) => {
 
     const contentsBody = cleanedContents.join('');
     // create and send url message
-    await this.keepCreatePageAndSendPreview(client, interactionPayloadAccessor, path, userChannelId, contentsBody, respondUtil);
+    await this.keepCreatePageAndSendPreview(client, interactionPayloadAccessor, path, userId, contentsBody, respondUtil);
   };
 
   handler.keepValidateForm = async function(client, payload, interactionPayloadAccessor) {
@@ -137,10 +139,41 @@ module.exports = (crowi) => {
     return result;
   };
 
+  /**
+   * Get all growi users from messages
+   * @param {*} messages (array of messages)
+   * @returns users object with matching slack ID
+   */
+  handler.getGrowiUsersFromMessages = async function(messages) {
+    const users = messages.map((message) => {
+      return message.user;
+    });
+    const growiUsers = await User.findUsersBySlackIds(users);
+    return growiUsers;
+  };
+  /**
+   * Convert slack ID to growi user if slack ID is found in messages
+   * @param {*} messages
+   */
+  handler.messagesWithGrowiUser = async function(messages) {
+    const growiUsers = await this.getGrowiUsersFromMessages(messages);
+
+    messages.map(async(message) => {
+      const growiUser = growiUsers.find(user => user.slackId === message.user);
+      if (growiUser) {
+        message.user = `${growiUser.name} (@${growiUser.username})`;
+      }
+      else {
+        message.user = `This slack ID is not registered (${message.user})`;
+      }
+    });
+  };
+
   handler.keepCleanMessages = async function(messages) {
     const cleanedContents = [];
     let lastMessage = {};
     const grwTzoffset = crowi.appService.getTzoffset() * 60;
+    await this.messagesWithGrowiUser(messages);
     messages
       .sort((a, b) => {
         return a.ts - b.ts;
@@ -164,8 +197,8 @@ module.exports = (crowi) => {
     return cleanedContents;
   };
 
-  handler.keepCreatePageAndSendPreview = async function(client, interactionPayloadAccessor, path, userChannelId, contentsBody, respondUtil) {
-    await createPageService.createPageInGrowi(interactionPayloadAccessor, path, contentsBody, respondUtil);
+  handler.keepCreatePageAndSendPreview = async function(client, interactionPayloadAccessor, path, userId, contentsBody, respondUtil) {
+    await createPageService.createPageInGrowi(interactionPayloadAccessor, path, contentsBody, respondUtil, userId);
 
     // TODO: contentsBody text characters must be less than 3001
     // send preview to dm