Skip to content

Commit c176f25

Browse files
authored
add candidate type and include test duration info in message to reviewers (#513)
1 parent 8916ca5 commit c176f25

17 files changed

+152
-18
lines changed

src/bot/__tests__/acceptReviewRequest.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { acceptReviewRequest } from '@bot/acceptReviewRequest';
22
import { buildMockActionParam, buildMockApp } from '@utils/slackMocks';
3-
import { BlockId } from '@bot/enums';
3+
import { BlockId, CandidateType } from '@bot/enums';
44
import { chatService } from '@/services/ChatService';
55
import { userRepo } from '@repos/userRepo';
66
import { addUserToAcceptedReviewers } from '@/services/RequestService';
@@ -20,6 +20,13 @@ describe('acceptReviewRequest', () => {
2020
activeReviewRepo.getReviewByThreadIdOrUndefined = jest.fn();
2121
});
2222

23+
const expectedCandidateTypeBlock = {
24+
type: 'section',
25+
text: {
26+
type: 'mrkdwn',
27+
text: '*Candidate Type:* Full-time',
28+
},
29+
};
2330
const expectedHackerRankUrlBlock = {
2431
type: 'section',
2532
text: {
@@ -71,6 +78,7 @@ describe('acceptReviewRequest', () => {
7178
(activeReviewRepo.getReviewByThreadIdOrUndefined as jest.Mock).mockResolvedValue({
7279
hackerRankUrl: 'https://www.sourceallies.com',
7380
requestorId: 'requester123',
81+
candidateType: CandidateType.FULL_TIME,
7482
acceptedReviewers: [],
7583
declinedReviewers: [],
7684
pendingReviewers: [{ userId: action.body.user.id }],
@@ -126,6 +134,7 @@ describe('acceptReviewRequest', () => {
126134
);
127135
expectUpdatedWithBlocks(
128136
action,
137+
expectedCandidateTypeBlock,
129138
expectedHackerRankUrlBlock,
130139
expectedHackerRankInstructionsBlock,
131140
expectedHackerRankAccountHelpBlock,

src/bot/__tests__/getReviewInfo.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { App } from '@slack/bolt';
22
import { getReviewInfo } from '@bot/getReviewInfo';
33
import { buildMockGlobalShortcutParam, buildMockWebClient } from '@utils/slackMocks';
4-
import { Deadline, Interaction } from '@bot/enums';
4+
import { CandidateType, Deadline, Interaction } from '@bot/enums';
55
import { GlobalShortcutParam } from '@/slackTypes';
66
import { activeReviewRepo } from '@repos/activeReviewsRepo';
77
import { ActiveReview } from '@models/ActiveReview';
@@ -47,6 +47,7 @@ describe('getReviewInfo', () => {
4747
requestedAt: new Date(1650504468906),
4848
dueBy: Deadline.END_OF_DAY,
4949
candidateIdentifier: 'some-id',
50+
candidateType: CandidateType.FULL_TIME,
5051
reviewersNeededCount: 1,
5152
acceptedReviewers: [],
5253
declinedReviewers: [],

src/bot/__tests__/requestReview.test.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { activeReviewRepo } from '@/database/repos/activeReviewsRepo';
22
import { QueueService } from '@/services';
33
import { chatService } from '@/services/ChatService';
44
import { ShortcutParam } from '@/slackTypes';
5-
import { ActionId, Deadline, Interaction } from '@bot/enums';
5+
import { ActionId, CandidateType, Deadline, Interaction } from '@bot/enums';
66
import { requestReview } from '@bot/requestReview';
77
import { languageRepo } from '@repos/languageRepo';
88
import { App, SlackViewAction, ViewStateValue } from '@slack/bolt';
@@ -157,10 +157,17 @@ describe('requestReview', () => {
157157
expect(blocks[2].element.initial_value).toEqual('2');
158158
});
159159

160-
it('should setup the fifth response block for the HackerRank URL input', () => {
160+
it('should setup the fifth response block for the candidate type dropdown', () => {
161161
const { mock } = param.client.views.open as jest.Mock;
162162
const blocks = mock.calls[0][0].view.blocks;
163-
expect(blocks[4]).toEqual({
163+
expect(blocks[4].block_id).toEqual(ActionId.CANDIDATE_TYPE);
164+
expect(blocks[4].type).toEqual('input');
165+
});
166+
167+
it('should setup the sixth response block for the HackerRank URL input', () => {
168+
const { mock } = param.client.views.open as jest.Mock;
169+
const blocks = mock.calls[0][0].view.blocks;
170+
expect(blocks[5]).toEqual({
164171
type: 'input',
165172
block_id: ActionId.HACKERRANK_URL,
166173
label: {
@@ -265,6 +272,15 @@ describe('requestReview', () => {
265272
value: 'some-identifier',
266273
},
267274
},
275+
[ActionId.CANDIDATE_TYPE]: {
276+
[ActionId.CANDIDATE_TYPE]: {
277+
type: 'static_select',
278+
selected_option: {
279+
text: { type: 'plain_text', text: 'Full-time' },
280+
value: CandidateType.FULL_TIME,
281+
},
282+
},
283+
},
268284
[ActionId.HACKERRANK_URL]: {
269285
[ActionId.HACKERRANK_URL]: {
270286
type: 'plain_text_input',
@@ -331,10 +347,13 @@ describe('requestReview', () => {
331347
• Go
332348
• Javascript
333349
350+
*Candidate Type: Full-time*
351+
334352
*The review is needed by end of day Monday*
335353
336354
_Candidate Identifier: some-identifier_
337355
`.trim(),
356+
token: undefined,
338357
});
339358
});
340359

@@ -354,6 +373,7 @@ _Candidate Identifier: some-identifier_
354373
requestedAt: expect.any(Date),
355374
dueBy: Deadline.MONDAY,
356375
candidateIdentifier: 'some-identifier',
376+
candidateType: CandidateType.FULL_TIME,
357377
reviewersNeededCount: '1',
358378
acceptedReviewers: [],
359379
declinedReviewers: [],

src/bot/acceptReviewRequest.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ActionParam } from '@/slackTypes';
22
import { App } from '@slack/bolt';
33
import log from '@utils/log';
4-
import { ActionId, BlockId } from './enums';
4+
import { ActionId, BlockId, CandidateTypeLabel } from './enums';
55
import { userRepo } from '@repos/userRepo';
66
import { mention, textBlock } from '@utils/text';
77
import { reportErrorAndContinue } from '@utils/reportError';
@@ -86,6 +86,9 @@ export const acceptReviewRequest = {
8686
// Add HackerRank URL with instructions if available
8787
const review = await activeReviewRepo.getReviewByThreadIdOrUndefined(threadId);
8888
if (review) {
89+
blocks.push(
90+
textBlock(`*Candidate Type:* ${CandidateTypeLabel.get(review.candidateType)}`),
91+
);
8992
blocks.push(
9093
textBlock(`*HackerRank Report:* <${review.hackerRankUrl}|View Candidate Assessment>`),
9194
);

src/bot/enums.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const enum ActionId {
1919
REVIEW_DEADLINE = 'review-deadline',
2020
NUMBER_OF_REVIEWERS = 'number-of-reviewers',
2121
CANDIDATE_IDENTIFIER = 'candidate-identifier',
22+
CANDIDATE_TYPE = 'candidate-type',
2223
REVIEWER_DM_ACCEPT = 'reviewer-dm-accept',
2324
REVIEWER_DM_DECLINE = 'reviewer-dm-deny',
2425
HACKERRANK_URL = 'hackerrank-url',
@@ -46,3 +47,13 @@ export const DeadlineLabel = new Map<Deadline, string>([
4647
[Deadline.THURSDAY, 'Thursday'],
4748
[Deadline.FRIDAY, 'Friday'],
4849
]);
50+
51+
export const enum CandidateType {
52+
FULL_TIME = 'full-time',
53+
APPRENTICE = 'apprentice',
54+
}
55+
56+
export const CandidateTypeLabel = new Map<CandidateType, string>([
57+
[CandidateType.FULL_TIME, 'Full-time'],
58+
[CandidateType.APPRENTICE, 'Apprentice'],
59+
]);

src/bot/requestReview.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@ import { blockUtils } from '@utils/blocks';
99
import log from '@utils/log';
1010
import { bold, codeBlock, compose, italic, mention, ul } from '@utils/text';
1111
import { PendingReviewer } from '@models/ActiveReview';
12-
import { ActionId, Deadline, DeadlineLabel, Interaction } from './enums';
12+
import {
13+
ActionId,
14+
CandidateType,
15+
CandidateTypeLabel,
16+
Deadline,
17+
DeadlineLabel,
18+
Interaction,
19+
} from './enums';
1320
import { chatService } from '@/services/ChatService';
1421
import { determineExpirationTime } from '@utils/reviewExpirationUtils';
1522

@@ -89,6 +96,19 @@ export const requestReview = {
8996
},
9097
},
9198
},
99+
{
100+
type: 'input',
101+
block_id: ActionId.CANDIDATE_TYPE,
102+
label: {
103+
text: 'What type of candidate is this?',
104+
type: 'plain_text',
105+
},
106+
element: {
107+
type: 'static_select',
108+
action_id: ActionId.CANDIDATE_TYPE,
109+
options: buildCandidateTypeOptions(),
110+
},
111+
},
92112
{
93113
type: 'input',
94114
block_id: ActionId.HACKERRANK_URL,
@@ -160,19 +180,24 @@ export const requestReview = {
160180
const deadline = blockUtils.getBlockValue(body, ActionId.REVIEW_DEADLINE);
161181
const numberOfRequestedReviewers = blockUtils.getBlockValue(body, ActionId.NUMBER_OF_REVIEWERS);
162182
const candidateIdentifier = blockUtils.getBlockValue(body, ActionId.CANDIDATE_IDENTIFIER);
183+
const candidateType = blockUtils.getBlockValue(body, ActionId.CANDIDATE_TYPE);
163184
const hackerRankUrl = blockUtils.getBlockValue(body, ActionId.HACKERRANK_URL);
164185

165186
const numberOfReviewersValue = numberOfRequestedReviewers.value;
166187
const deadlineValue = deadline.selected_option.value;
167188
const deadlineDisplay = deadline.selected_option.text.text;
168189
const candidateIdentifierValue = candidateIdentifier.value;
190+
const candidateTypeValue = candidateType.selected_option.value;
191+
const candidateTypeDisplay = candidateType.selected_option.text.text;
169192
const hackerRankUrlValue = hackerRankUrl.value;
170193
log.d(
171194
'requestReview.callback',
172195
'Parsed values:',
173196
JSON.stringify({
174197
numberOfReviewersValue,
175198
candidateIdentifierValue,
199+
candidateTypeValue,
200+
candidateTypeDisplay,
176201
hackerRankUrlValue,
177202
deadlineValue,
178203
deadlineDisplay,
@@ -190,6 +215,7 @@ export const requestReview = {
190215
user,
191216
)} has requested ${numberOfReviewersValue} reviews for a HackerRank done in the following languages:`,
192217
ul(...languages),
218+
bold(`Candidate Type: ${candidateTypeDisplay}`),
193219
bold(`The review is needed by end of day ${deadlineDisplay}`),
194220
candidateIdentifierValue ? italic(`Candidate Identifier: ${candidateIdentifierValue}`) : '',
195221
),
@@ -226,6 +252,7 @@ export const requestReview = {
226252
{ id: user.id },
227253
languages,
228254
deadlineDisplay,
255+
candidateTypeDisplay,
229256
);
230257
const pendingReviewer: PendingReviewer = {
231258
userId: reviewer.id,
@@ -242,6 +269,7 @@ export const requestReview = {
242269
requestedAt: new Date(),
243270
dueBy: deadlineValue,
244271
candidateIdentifier: candidateIdentifierValue,
272+
candidateType: candidateTypeValue,
245273
reviewersNeededCount: numberOfReviewersValue,
246274
acceptedReviewers: [],
247275
declinedReviewers: [],
@@ -265,3 +293,17 @@ function buildDeadlineOptions(): PlainTextOption[] {
265293
function buildOption(deadline: Deadline): PlainTextOption {
266294
return { text: { text: DeadlineLabel.get(deadline) || '', type: 'plain_text' }, value: deadline };
267295
}
296+
297+
function buildCandidateTypeOptions(): PlainTextOption[] {
298+
return [
299+
buildCandidateTypeOption(CandidateType.FULL_TIME),
300+
buildCandidateTypeOption(CandidateType.APPRENTICE),
301+
];
302+
}
303+
304+
function buildCandidateTypeOption(candidateType: CandidateType): PlainTextOption {
305+
return {
306+
text: { text: CandidateTypeLabel.get(candidateType) || '', type: 'plain_text' },
307+
value: candidateType,
308+
};
309+
}

src/cron/__tests__/reviewProcessor.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Deadline } from '@/bot/enums';
1+
import { CandidateType, Deadline } from '@/bot/enums';
22
import { ActiveReview, PendingReviewer } from '@/database/models/ActiveReview';
33
import { activeReviewRepo } from '@/database/repos/activeReviewsRepo';
44
import { RequestService } from '@/services';
@@ -15,6 +15,7 @@ function mockReview(pendingReviewers: PendingReviewer[]): ActiveReview {
1515
acceptedReviewers: [],
1616
dueBy: Deadline.MONDAY,
1717
candidateIdentifier: '',
18+
candidateType: CandidateType.FULL_TIME,
1819
languages: [],
1920
pendingReviewers,
2021
declinedReviewers: [],

src/database/models/ActiveReview.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Deadline } from '@bot/enums';
1+
import { CandidateType, Deadline } from '@bot/enums';
22

33
export interface ActiveReview {
44
threadId: string;
@@ -7,6 +7,10 @@ export interface ActiveReview {
77
requestedAt: Date;
88
dueBy: Deadline;
99
candidateIdentifier: string;
10+
/**
11+
* The type of candidate (full-time or apprentice)
12+
*/
13+
candidateType: CandidateType;
1014
/**
1115
* The number of reviewers requested for this review. It should not change over the life of the
1216
* review

src/database/repos/activeReviewsRepo.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ enum Column {
1111
REQUESTED_AT = 'requestedAt',
1212
DUE_BY = 'dueBy',
1313
CANDIDATE_IDENTIFIER = 'candidateIdentifier',
14+
CANDIDATE_TYPE = 'candidateType',
1415
REVIEWERS_NEEDED_COUNT = 'reviewersNeededCount',
1516
ACCEPTED_REVIEWERS = 'acceptedReviewers',
1617
PENDING_REVIEWERS = 'pendingReviewers',
@@ -34,6 +35,7 @@ function mapRowToActiveReview(row: GoogleSpreadsheetRow): ActiveReview {
3435
requestedAt: parseDateRow(row.get(Column.REQUESTED_AT)),
3536
dueBy: row.get(Column.DUE_BY),
3637
candidateIdentifier: row.get(Column.CANDIDATE_IDENTIFIER),
38+
candidateType: row.get(Column.CANDIDATE_TYPE),
3739
reviewersNeededCount: Number(row.get(Column.REVIEWERS_NEEDED_COUNT)),
3840
acceptedReviewers: JSON.parse(row.get(Column.ACCEPTED_REVIEWERS)),
3941
pendingReviewers: JSON.parse(row.get(Column.PENDING_REVIEWERS)),
@@ -50,6 +52,7 @@ function mapActiveReviewToRow(activeReview: ActiveReview): Record<string, any> {
5052
[Column.REQUESTED_AT]: activeReview.requestedAt.getTime(),
5153
[Column.DUE_BY]: activeReview.dueBy,
5254
[Column.CANDIDATE_IDENTIFIER]: activeReview.candidateIdentifier,
55+
[Column.CANDIDATE_TYPE]: activeReview.candidateType,
5356
[Column.REVIEWERS_NEEDED_COUNT]: activeReview.reviewersNeededCount,
5457
[Column.ACCEPTED_REVIEWERS]: JSON.stringify(activeReview.acceptedReviewers),
5558
[Column.PENDING_REVIEWERS]: JSON.stringify(activeReview.pendingReviewers),

src/services/ChatService.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,15 @@ export const chatService = {
8989
requestor: { id: string },
9090
languages: string[],
9191
deadlineDisplay: string,
92+
candidateTypeDisplay: string,
9293
): Promise<string> {
9394
const request = requestBuilder.buildReviewRequest(
9495
reviewerId,
9596
threadId,
9697
requestor,
9798
languages,
9899
deadlineDisplay,
100+
candidateTypeDisplay,
99101
);
100102
const requestWithToken = {
101103
...request,

0 commit comments

Comments
 (0)