Skip to content

Commit

Permalink
feat(next): mention users in slack on ticket created (#958)
Browse files Browse the repository at this point in the history
  • Loading branch information
sdjdd authored Jan 8, 2024
1 parent 08b9882 commit 5286b4a
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 26 deletions.
57 changes: 41 additions & 16 deletions next/api/src/integration/slack/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,24 @@ class SlackIntegration {
});
}

async getUserId(email: string): Promise<string> {
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<string> {
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
};

Expand Down
12 changes: 11 additions & 1 deletion next/api/src/integration/slack/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,24 @@ export class Message {

protected color?: string;

protected mentions?: string[];

constructor(readonly summary: string, content: string) {
if (content.length > 1000) {
content = content.slice(0, 1000) + '...';
}
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',
Expand All @@ -25,7 +35,7 @@ export class Message {
},
];
return {
text: this.summary,
text,
attachments: [{ color: this.color, blocks }],
};
}
Expand Down
32 changes: 27 additions & 5 deletions next/web/src/App/Admin/Settings/Categories/CategoryForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,31 @@ const FORM_ITEM_STYLE = { marginBottom: 16 };

const CategoryMetaOptions: MetaOptionsGroup<CategorySchema>[] = [
{
label: 'AI 分类',
key: 'slack',
label: 'Slack 新工单通知用户邮箱',
children: [
{
key: 'slackNewTicketMentionUserEmails',
type: 'custom',
render: ({ value, onChange }) => (
<Form.Item
extra="当前分类的工单被创建时,将在指定频道 @ 上述用户"
style={{ marginBottom: 0 }}
>
<TextArea
placeholder="每行一个"
autoSize={{ minRows: 2 }}
value={value?.join('\n')}
onChange={(e) => onChange(e.target.value.split('\n'))}
/>
</Form.Item>
),
},
],
},
{
key: 'classify',
label: 'AI 分类',
children: [
{
key: 'enableAIClassify',
Expand All @@ -64,8 +87,8 @@ const CategoryMetaOptions: MetaOptionsGroup<CategorySchema>[] = [
},
{
key: 'previewAIClassify',
type: 'component',
component: <AiClassifyTest />,
type: 'custom',
render: () => <AiClassifyTest />,
predicate: (v) => !!v.alias,
},
],
Expand Down Expand Up @@ -563,8 +586,7 @@ export function CategoryForm({
control={control}
name="meta"
render={({ field: { ref, ...rest } }) => (
console.log(getValues()),
(<MetaField {...rest} options={CategoryMetaOptions} record={getValues()} />)
<MetaField {...rest} options={CategoryMetaOptions} record={getValues()} />
)}
/>

Expand Down
11 changes: 7 additions & 4 deletions next/web/src/App/Admin/components/MetaField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export type MetaOption<T = any> = (
label: string;
}
| {
type: 'component';
component: ReactNode;
type: 'custom';
render: (options: { value: any; onChange: (value: any) => void }) => ReactNode;
}
) & { key: string; predicate?: (data: T) => boolean; description?: string };

Expand Down Expand Up @@ -53,8 +53,11 @@ const MetaOptionsForm = <T extends SchemaWithMeta>({
.filter(({ predicate }) => !record || !predicate || predicate(record))
.map((option) => (
<Form.Item key={option.key} help={option.description} className="!mb-0">
{option.type === 'component' ? (
option.component
{option.type === 'custom' ? (
option.render({
value: value?.[option.key],
onChange: handleFieldChangeFactory(option.key, (v) => v),
})
) : (
<div>
{option.type === 'boolean' ? (
Expand Down

0 comments on commit 5286b4a

Please sign in to comment.