diff --git a/next/api/src/integration/slack/index.ts b/next/api/src/integration/slack/index.ts index f9490b2e1..0b71ec129 100644 --- a/next/api/src/integration/slack/index.ts +++ b/next/api/src/integration/slack/index.ts @@ -75,18 +75,24 @@ class SlackIntegration { }); } - async getUserId(email: string): Promise { + async getUserId(email: string) { const id = this.userIdMap.get(email); if (id) { return id; } - const { user } = await this.client.users.lookupByEmail({ email }); - if (!user || !user.id) { - throw new Error('Slack API returns an invalid user'); + try { + const { user } = await this.client.users.lookupByEmail({ email }); + if (!user || !user.id) { + throw new Error('Slack API returns an invalid user'); + } + this.userIdMap.set(email, user.id); + return user.id; + } catch (error: any) { + if (error.data.error !== 'users_not_found') { + throw error; + } } - this.userIdMap.set(email, user.id); - return user.id; } async getChannelId(userId: string): Promise { @@ -117,16 +123,10 @@ class SlackIntegration { } async sendToUser(email: string, message: Message) { - let userId: string; - try { - userId = await this.getUserId(email); - } catch (error: any) { - if (error.data.error === 'users_not_found') { - return; - } - throw error; + const userId = await this.getUserId(email); + if (!userId) { + return; } - const channelId = await this.getChannelId(userId); return this.send(channelId, message); } @@ -146,11 +146,36 @@ class SlackIntegration { }); } - sendNewTicket = ({ ticket, from, to }: NewTicketContext) => { + async getCategoryMentionUserIds(categoryId: string) { + const category = await categoryService.findOne(categoryId); + if (!category || !category.meta) { + return; + } + + const emails = category.meta.slackNewTicketMentionUserEmails as string[]; + if (!emails) { + return; + } + + const userIds: string[] = []; + for (const email of emails) { + const id = await this.getUserId(email); + if (id) { + userIds.push(id); + } + } + return userIds; + } + + sendNewTicket = async ({ ticket, from, to }: NewTicketContext) => { const message = new NewTicketMessage(ticket, from, to); if (to?.email) { this.sendToUser(to.email, message); } + const userIds = await this.getCategoryMentionUserIds(ticket.categoryId); + if (userIds?.length) { + message.setMentions(userIds); + } this.broadcast(message, ticket.categoryId); }; diff --git a/next/api/src/integration/slack/message.ts b/next/api/src/integration/slack/message.ts index 235fca539..e8c72aa14 100644 --- a/next/api/src/integration/slack/message.ts +++ b/next/api/src/integration/slack/message.ts @@ -7,6 +7,8 @@ export class Message { protected color?: string; + protected mentions?: string[]; + constructor(readonly summary: string, content: string) { if (content.length > 1000) { content = content.slice(0, 1000) + '...'; @@ -14,7 +16,15 @@ export class Message { this.content = content; } + setMentions(mentions: string[]) { + this.mentions = mentions; + } + toJSON() { + let text = this.summary; + if (this.mentions?.length) { + text += '\n' + this.mentions.map((id) => `<@${id}>`).join(' '); + } const blocks = [ { type: 'section', @@ -25,7 +35,7 @@ export class Message { }, ]; return { - text: this.summary, + text, attachments: [{ color: this.color, blocks }], }; } diff --git a/next/web/src/App/Admin/Settings/Categories/CategoryForm.tsx b/next/web/src/App/Admin/Settings/Categories/CategoryForm.tsx index 4e781470d..e9830be0e 100644 --- a/next/web/src/App/Admin/Settings/Categories/CategoryForm.tsx +++ b/next/web/src/App/Admin/Settings/Categories/CategoryForm.tsx @@ -45,8 +45,31 @@ const FORM_ITEM_STYLE = { marginBottom: 16 }; const CategoryMetaOptions: MetaOptionsGroup[] = [ { - label: 'AI 分类', + key: 'slack', + label: 'Slack 新工单通知用户邮箱', + children: [ + { + key: 'slackNewTicketMentionUserEmails', + type: 'custom', + render: ({ value, onChange }) => ( + +