Skip to content

Commit ebe33c0

Browse files
committed
feat: pre-populate queue preferences dialog with existing user data
When opening the Queue Preferences modal, fetch the user's existing record and set initial_options on each checkbox group so returning users see their current selections pre-filled.
1 parent 26d391c commit ebe33c0

2 files changed

Lines changed: 135 additions & 36 deletions

File tree

src/bot/__tests__/joinQueue.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ describe('joinQueue', () => {
1919
beforeEach(() => {
2020
shortCutParam = buildMockShortcutParam();
2121
languageRepo.listAll = jest.fn();
22+
userRepo.find = jest.fn().mockResolvedValue(null);
2223
shortCutParam.client.conversations.open = jest
2324
.fn()
2425
.mockResolvedValue({ channel: { id: DIRECT_MESSAGE_ID } });
@@ -53,6 +54,82 @@ describe('joinQueue', () => {
5354
expect(blockIds).toContain('interview-format-selection');
5455
});
5556

57+
describe('when user has existing preferences', () => {
58+
const existingUser = {
59+
id: 'some-user-id',
60+
name: 'Test User',
61+
languages: ['Javascript'],
62+
interviewTypes: [InterviewType.PAIRING],
63+
formats: [InterviewFormat.IN_PERSON],
64+
lastReviewedDate: undefined,
65+
lastPairingReviewedDate: undefined,
66+
};
67+
68+
beforeEach(() => {
69+
userRepo.find = jest.fn().mockResolvedValue(existingUser);
70+
});
71+
72+
it('should pre-select existing language preferences', async () => {
73+
await joinQueue.shortcut(shortCutParam);
74+
75+
const viewCall = (shortCutParam.client.views.open as jest.Mock).mock.calls[0][0];
76+
const langBlock = viewCall.view.blocks.find(
77+
(b: { block_id: string }) => b.block_id === ActionId.LANGUAGE_SELECTIONS,
78+
);
79+
const initialValues = langBlock.element.initial_options.map(
80+
(o: { value: string }) => o.value,
81+
);
82+
83+
expect(initialValues).toEqual(['Javascript']);
84+
});
85+
86+
it('should pre-select existing interview type preferences', async () => {
87+
await joinQueue.shortcut(shortCutParam);
88+
89+
const viewCall = (shortCutParam.client.views.open as jest.Mock).mock.calls[0][0];
90+
const typeBlock = viewCall.view.blocks.find(
91+
(b: { block_id: string }) => b.block_id === ActionId.INTERVIEW_TYPE_SELECTIONS,
92+
);
93+
const initialValues = typeBlock.element.initial_options.map(
94+
(o: { value: string }) => o.value,
95+
);
96+
97+
expect(initialValues).toEqual([InterviewType.PAIRING]);
98+
});
99+
100+
it('should pre-select existing format preferences', async () => {
101+
await joinQueue.shortcut(shortCutParam);
102+
103+
const viewCall = (shortCutParam.client.views.open as jest.Mock).mock.calls[0][0];
104+
const formatBlock = viewCall.view.blocks.find(
105+
(b: { block_id: string }) => b.block_id === ActionId.INTERVIEW_FORMAT_SELECTION,
106+
);
107+
const initialValues = formatBlock.element.initial_options.map(
108+
(o: { value: string }) => o.value,
109+
);
110+
111+
expect(initialValues).toEqual([InterviewFormat.IN_PERSON]);
112+
});
113+
});
114+
115+
describe('when user has no existing preferences', () => {
116+
beforeEach(() => {
117+
userRepo.find = jest.fn().mockResolvedValue(null);
118+
});
119+
120+
it('should open dialog with no pre-selected options', async () => {
121+
await joinQueue.shortcut(shortCutParam);
122+
123+
const viewCall = (shortCutParam.client.views.open as jest.Mock).mock.calls[0][0];
124+
const blocks = viewCall.view.blocks;
125+
const inputBlocks = blocks.filter((b: { type: string }) => b.type === 'input');
126+
127+
for (const block of inputBlocks) {
128+
expect(block.element.initial_options).toBeUndefined();
129+
}
130+
});
131+
});
132+
56133
it('should include Leave Queue as a danger button', async () => {
57134
await joinQueue.shortcut(shortCutParam);
58135

src/bot/joinQueue.ts

Lines changed: 58 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ActionParam, CallbackParam, ShortcutParam } from '@/slackTypes';
2+
import { User } from '@models/User';
23
import { languageRepo } from '@repos/languageRepo';
34
import { userRepo } from '@repos/userRepo';
45
import { App } from '@slack/bolt';
@@ -27,7 +28,43 @@ export const joinQueue = {
2728
app.action(ActionId.LEAVE_QUEUE, this.handleLeaveQueue.bind(this));
2829
},
2930

30-
dialog(languages: string[]): View {
31+
dialog(languages: string[], existingUser?: User): View {
32+
const initialOptions = <T extends string>(
33+
options: Option[],
34+
saved: T[] | undefined,
35+
): Option[] | undefined => {
36+
if (!saved?.length) return undefined;
37+
const filtered = options.filter(o => saved.includes(o.value as T));
38+
return filtered.length ? filtered : undefined;
39+
};
40+
41+
const languageOptions = languages.map<Option>(lang => ({
42+
text: { text: lang, type: 'plain_text' },
43+
value: lang,
44+
}));
45+
46+
const interviewTypeOptions: Option[] = [
47+
{
48+
text: { text: InterviewTypeLabel.get(InterviewType.HACKERRANK)!, type: 'plain_text' },
49+
value: InterviewType.HACKERRANK,
50+
},
51+
{
52+
text: { text: InterviewTypeLabel.get(InterviewType.PAIRING)!, type: 'plain_text' },
53+
value: InterviewType.PAIRING,
54+
},
55+
];
56+
57+
const formatOptions: Option[] = [
58+
{
59+
text: { text: InterviewFormatLabel.get(InterviewFormat.REMOTE)!, type: 'plain_text' },
60+
value: InterviewFormat.REMOTE,
61+
},
62+
{
63+
text: { text: InterviewFormatLabel.get(InterviewFormat.IN_PERSON)!, type: 'plain_text' },
64+
value: InterviewFormat.IN_PERSON,
65+
},
66+
];
67+
3168
return {
3269
title: { text: 'Queue Preferences', type: 'plain_text' },
3370
type: 'modal',
@@ -40,10 +77,10 @@ export const joinQueue = {
4077
element: {
4178
type: 'checkboxes',
4279
action_id: ActionId.LANGUAGE_SELECTIONS,
43-
options: languages.map<Option>(lang => ({
44-
text: { text: lang, type: 'plain_text' },
45-
value: lang,
46-
})),
80+
options: languageOptions,
81+
...(initialOptions(languageOptions, existingUser?.languages) && {
82+
initial_options: initialOptions(languageOptions, existingUser?.languages),
83+
}),
4784
},
4885
},
4986
{
@@ -53,19 +90,10 @@ export const joinQueue = {
5390
element: {
5491
type: 'checkboxes',
5592
action_id: ActionId.INTERVIEW_TYPE_SELECTIONS,
56-
options: [
57-
{
58-
text: {
59-
text: InterviewTypeLabel.get(InterviewType.HACKERRANK)!,
60-
type: 'plain_text',
61-
},
62-
value: InterviewType.HACKERRANK,
63-
},
64-
{
65-
text: { text: InterviewTypeLabel.get(InterviewType.PAIRING)!, type: 'plain_text' },
66-
value: InterviewType.PAIRING,
67-
},
68-
],
93+
options: interviewTypeOptions,
94+
...(initialOptions(interviewTypeOptions, existingUser?.interviewTypes) && {
95+
initial_options: initialOptions(interviewTypeOptions, existingUser?.interviewTypes),
96+
}),
6997
},
7098
},
7199
{
@@ -75,22 +103,10 @@ export const joinQueue = {
75103
element: {
76104
type: 'checkboxes',
77105
action_id: ActionId.INTERVIEW_FORMAT_SELECTION,
78-
options: [
79-
{
80-
text: {
81-
text: InterviewFormatLabel.get(InterviewFormat.REMOTE)!,
82-
type: 'plain_text',
83-
},
84-
value: InterviewFormat.REMOTE,
85-
},
86-
{
87-
text: {
88-
text: InterviewFormatLabel.get(InterviewFormat.IN_PERSON)!,
89-
type: 'plain_text',
90-
},
91-
value: InterviewFormat.IN_PERSON,
92-
},
93-
],
106+
options: formatOptions,
107+
...(initialOptions(formatOptions, existingUser?.formats) && {
108+
initial_options: initialOptions(formatOptions, existingUser?.formats),
109+
}),
94110
},
95111
},
96112
{
@@ -119,8 +135,14 @@ export const joinQueue = {
119135
log.d('joinQueue.shortcut', `Opening queue preferences, user.id=${shortcut.user.id}`);
120136
await ack();
121137
try {
122-
const languages = await languageRepo.listAll();
123-
await client.views.open({ trigger_id: shortcut.trigger_id, view: this.dialog(languages) });
138+
const [languages, existingUser] = await Promise.all([
139+
languageRepo.listAll(),
140+
userRepo.find(shortcut.user.id),
141+
]);
142+
await client.views.open({
143+
trigger_id: shortcut.trigger_id,
144+
view: this.dialog(languages, existingUser ?? undefined),
145+
});
124146
// eslint-disable-next-line @typescript-eslint/no-explicit-any
125147
} catch (err: any) {
126148
log.e('joinQueue.shortcut', 'Failed', err);

0 commit comments

Comments
 (0)