Skip to content

Commit 80a6a8a

Browse files
committed
feat: enable worker and notification for parse cv
1 parent 7594e77 commit 80a6a8a

File tree

8 files changed

+165
-54
lines changed

8 files changed

+165
-54
lines changed

__tests__/notifications/index.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2170,3 +2170,37 @@ describe('warm intro notifications', () => {
21702170
expect(actual.attachments.length).toEqual(0);
21712171
});
21722172
});
2173+
2174+
describe('parsed_cv_profile notifications', () => {
2175+
beforeEach(async () => {
2176+
jest.resetAllMocks();
2177+
await saveFixtures(con, User, usersFixture);
2178+
});
2179+
2180+
it('should notify when parsed CV profile is ready', async () => {
2181+
const type = NotificationType.ParsedCVProfile;
2182+
const ctx: NotificationUserContext = {
2183+
userIds: ['1'],
2184+
user: usersFixture[0] as Reference<User>,
2185+
};
2186+
2187+
const actual = generateNotificationV2(type, ctx);
2188+
expect(actual.notification.type).toEqual(type);
2189+
expect(actual.userIds).toEqual(['1']);
2190+
expect(actual.notification.public).toEqual(true);
2191+
expect(actual.notification.referenceId).toEqual('1');
2192+
expect(actual.notification.targetUrl).toEqual(
2193+
'http://localhost:5002/idoshamun',
2194+
);
2195+
expect(actual.attachments!.length).toEqual(0);
2196+
expect(actual.avatars).toEqual([
2197+
{
2198+
image: 'https://daily.dev/ido.jpg',
2199+
name: 'Ido',
2200+
referenceId: '1',
2201+
targetUrl: 'http://localhost:5002/idoshamun',
2202+
type: 'user',
2203+
},
2204+
]);
2205+
});
2206+
});

__tests__/workers/opportunity/parseCVProfile.ts

Lines changed: 88 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
22
createGarmrMock,
33
createMockBrokkrTransport,
4-
expectSuccessfulTypedBackground,
4+
invokeTypedNotificationWorker,
55
saveFixtures,
66
} from '../../helpers';
77
import { DataSource } from 'typeorm';
@@ -15,6 +15,8 @@ import type { ServiceClient } from '../../../src/types';
1515
import * as brokkrCommon from '../../../src/common/brokkr';
1616
import { UserExperience } from '../../../src/entity/user/experiences/UserExperience';
1717
import { getSecondsTimestamp, updateFlagsStatement } from '../../../src/common';
18+
import type { NotificationUserContext } from '../../../src/notifications';
19+
import { NotificationType } from '../../../src/notifications/common';
1820

1921
let con: DataSource;
2022

@@ -70,10 +72,13 @@ describe('parseCVProfile worker', () => {
7072
'parseCV',
7173
);
7274

73-
await expectSuccessfulTypedBackground<'api.v1.candidate-preference-updated'>(
74-
worker,
75-
payload,
76-
);
75+
const result =
76+
await invokeTypedNotificationWorker<'api.v1.candidate-preference-updated'>(
77+
worker,
78+
payload,
79+
);
80+
81+
expect(result).toBeDefined();
7782

7883
expect(parseCVSpy).toHaveBeenCalledTimes(1);
7984

@@ -101,10 +106,13 @@ describe('parseCVProfile worker', () => {
101106
'parseCV',
102107
);
103108

104-
await expectSuccessfulTypedBackground<'api.v1.candidate-preference-updated'>(
105-
worker,
106-
payload,
107-
);
109+
const result =
110+
await invokeTypedNotificationWorker<'api.v1.candidate-preference-updated'>(
111+
worker,
112+
payload,
113+
);
114+
115+
expect(result).toBeUndefined();
108116

109117
expect(parseCVSpy).toHaveBeenCalledTimes(0);
110118

@@ -133,10 +141,13 @@ describe('parseCVProfile worker', () => {
133141
'parseCV',
134142
);
135143

136-
await expectSuccessfulTypedBackground<'api.v1.candidate-preference-updated'>(
137-
worker,
138-
payload,
139-
);
144+
const result =
145+
await invokeTypedNotificationWorker<'api.v1.candidate-preference-updated'>(
146+
worker,
147+
payload,
148+
);
149+
150+
expect(result).toBeUndefined();
140151

141152
expect(parseCVSpy).toHaveBeenCalledTimes(0);
142153

@@ -165,10 +176,13 @@ describe('parseCVProfile worker', () => {
165176
'parseCV',
166177
);
167178

168-
await expectSuccessfulTypedBackground<'api.v1.candidate-preference-updated'>(
169-
worker,
170-
payload,
171-
);
179+
const result =
180+
await invokeTypedNotificationWorker<'api.v1.candidate-preference-updated'>(
181+
worker,
182+
payload,
183+
);
184+
185+
expect(result).toBeUndefined();
172186

173187
expect(parseCVSpy).toHaveBeenCalledTimes(0);
174188

@@ -198,10 +212,13 @@ describe('parseCVProfile worker', () => {
198212
'parseCV',
199213
);
200214

201-
await expectSuccessfulTypedBackground<'api.v1.candidate-preference-updated'>(
202-
worker,
203-
payload,
204-
);
215+
const result =
216+
await invokeTypedNotificationWorker<'api.v1.candidate-preference-updated'>(
217+
worker,
218+
payload,
219+
);
220+
221+
expect(result).toBeUndefined();
205222

206223
expect(parseCVSpy).toHaveBeenCalledTimes(0);
207224

@@ -240,10 +257,13 @@ describe('parseCVProfile worker', () => {
240257
'parseCV',
241258
);
242259

243-
await expectSuccessfulTypedBackground<'api.v1.candidate-preference-updated'>(
244-
worker,
245-
payload,
246-
);
260+
const result =
261+
await invokeTypedNotificationWorker<'api.v1.candidate-preference-updated'>(
262+
worker,
263+
payload,
264+
);
265+
266+
expect(result).toBeUndefined();
247267

248268
expect(parseCVSpy).toHaveBeenCalledTimes(0);
249269

@@ -273,10 +293,13 @@ describe('parseCVProfile worker', () => {
273293
'parseCV',
274294
);
275295

276-
await expectSuccessfulTypedBackground<'api.v1.candidate-preference-updated'>(
277-
worker,
278-
payload,
279-
);
296+
const result =
297+
await invokeTypedNotificationWorker<'api.v1.candidate-preference-updated'>(
298+
worker,
299+
payload,
300+
);
301+
302+
expect(result).toBeUndefined();
280303

281304
expect(parseCVSpy).toHaveBeenCalledTimes(1);
282305

@@ -320,14 +343,46 @@ describe('parseCVProfile worker', () => {
320343
'parseCV',
321344
);
322345

323-
await expectSuccessfulTypedBackground<'api.v1.candidate-preference-updated'>(
324-
worker,
325-
payload,
326-
);
346+
const result =
347+
await invokeTypedNotificationWorker<'api.v1.candidate-preference-updated'>(
348+
worker,
349+
payload,
350+
);
351+
352+
expect(result).toBeUndefined();
327353

328354
expect(parseCVSpy).toHaveBeenCalledTimes(1);
329355

330356
const user = await con.getRepository(User).findOneBy({ id: userId });
331357
expect(user?.flags.lastCVParseAt).toBe(parseDate.toISOString());
332358
});
359+
360+
it('should send notification after successful parsing', async () => {
361+
const userId = '1-pcpw';
362+
363+
const payload = new CandidatePreferenceUpdated({
364+
payload: {
365+
userId,
366+
cv: {
367+
blob: userId,
368+
bucket: 'bucket-test',
369+
lastModified: getSecondsTimestamp(new Date()),
370+
},
371+
},
372+
});
373+
374+
const result =
375+
await invokeTypedNotificationWorker<'api.v1.candidate-preference-updated'>(
376+
worker,
377+
payload,
378+
);
379+
380+
expect(result!.length).toEqual(1);
381+
expect(result![0].type).toEqual(NotificationType.ParsedCVProfile);
382+
383+
const postContext = result![0].ctx as NotificationUserContext;
384+
385+
expect(postContext.userIds).toEqual(['1-pcpw']);
386+
expect(postContext.user.id).toEqual(userId);
387+
});
333388
});

src/notifications/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export enum NotificationType {
7979
PollResult = 'poll_result',
8080
PollResultAuthor = 'poll_result_author',
8181
WarmIntro = 'warm_intro',
82+
ParsedCVProfile = 'parsed_cv_profile',
8283
}
8384

8485
export enum NotificationPreferenceType {

src/notifications/generate.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,10 @@ export const notificationTitleMap: Record<
205205
poll_result_author: (ctx: NotificationPostContext) =>
206206
`<b>Your poll has ended!</b> Check the results for: <b>${ctx.post.title}</b>`,
207207
warm_intro: (ctx: NotificationWarmIntroContext) =>
208-
`We just sent an intro email to you and <b>${ctx.recruiter.name}</b> from <b>${ctx.organization.name}</b>!`,
208+
`We just sent an intro email to you and <b>${ctx.recruiter.name}</b> from <u>${ctx.organization.name}</u>!`,
209+
parsed_cv_profile: () => {
210+
return `Your CV was successfully parsed and your experiences are added to <u>your profile</u>.`;
211+
},
209212
};
210213

211214
export const generateNotificationMap: Record<
@@ -593,4 +596,15 @@ export const generateNotificationMap: Record<
593596
`<span>We reached out to them and received a positive response. Our team will be here to assist you with anything you need. <a href="mailto:[email protected]" target="_blank" class="text-text-link">contact us</a></span>`,
594597
);
595598
},
599+
parsed_cv_profile: (
600+
builder: NotificationBuilder,
601+
ctx: NotificationUserContext,
602+
) => {
603+
return builder
604+
.icon(NotificationIcon.Bell)
605+
.referenceUser(ctx.user)
606+
.avatarUser(ctx.user)
607+
.targetUser(ctx.user)
608+
.uniqueKey(new Date().toISOString());
609+
},
596610
};

src/workers/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ import { storeCandidateApplicationScore } from './opportunity/storeCandidateAppl
7373
import { extractCVMarkdown } from './extractCVMarkdown';
7474
import candidateAcceptedOpportunitySlack from './candidateAcceptedOpportunitySlack';
7575
import recruiterRejectedCandidateMatchEmail from './recruiterRejectedCandidateMatchEmail';
76-
import { parseCVProfileWorker } from './opportunity/parseCVProfile';
7776

7877
export { Worker } from './worker';
7978

@@ -150,7 +149,6 @@ export const typedWorkers: BaseTypedWorker<any>[] = [
150149
extractCVMarkdown,
151150
candidateAcceptedOpportunitySlack,
152151
recruiterRejectedCandidateMatchEmail,
153-
parseCVProfileWorker,
154152
];
155153

156154
export const personalizedDigestWorkers: Worker[] = [

src/workers/newNotificationV2Mail.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ export const notificationToTemplateId: Record<NotificationType, string> = {
127127
poll_result: '84',
128128
poll_result_author: '84',
129129
warm_intro: '85',
130+
parsed_cv_profile: '',
130131
};
131132

132133
type TemplateData = Record<string, unknown> & {
@@ -1146,6 +1147,9 @@ const notificationToTemplateData: Record<NotificationType, TemplateDataFunc> = {
11461147
cc: recruiter?.email,
11471148
};
11481149
},
1150+
parsed_cv_profile: async () => {
1151+
return null;
1152+
},
11491153
};
11501154

11511155
const formatTemplateDate = <T extends TemplateData>(data: T): T => {

src/workers/notifications/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { pollResultAuthorNotification } from './pollResultAuthorNotification';
3838
import { pollResultNotification } from './pollResultNotification';
3939
import { articleNewCommentCommentCommented } from './articleNewCommentCommentCommented';
4040
import { warmIntroNotification } from './warmIntroNotification';
41+
import { parseCVProfileWorker } from '../opportunity/parseCVProfile';
4142

4243
export function notificationWorkerToWorker(
4344
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -122,6 +123,7 @@ const notificationWorkers: TypedNotificationWorker<any>[] = [
122123
pollResultAuthorNotification,
123124
pollResultNotification,
124125
warmIntroNotification,
126+
parseCVProfileWorker,
125127
];
126128

127129
export const workers = [...notificationWorkers.map(notificationWorkerToWorker)];

src/workers/opportunity/parseCVProfile.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,20 @@ import {
22
BrokkrParseRequest,
33
CandidatePreferenceUpdated,
44
} from '@dailydotdev/schema';
5-
import type { TypedWorker } from '../worker';
5+
import type { TypedNotificationWorker } from '../worker';
66
import { User } from '../../entity/user/User';
77
import { getBrokkrClient } from '../../common/brokkr';
8-
import { isProd, updateFlagsStatement } from '../../common/utils';
8+
import { updateFlagsStatement } from '../../common/utils';
99
import { importUserExperienceFromJSON } from '../../common/profile/import';
1010
import { logger } from '../../logger';
11+
import { NotificationType } from '../../notifications/common';
12+
import type { NotificationUserContext } from '../../notifications';
1113

12-
export const parseCVProfileWorker: TypedWorker<'api.v1.candidate-preference-updated'> =
14+
export const parseCVProfileWorker: TypedNotificationWorker<'api.v1.candidate-preference-updated'> =
1315
{
1416
subscription: 'api.parse-cv-profile',
1517
parseMessage: ({ data }) => CandidatePreferenceUpdated.fromBinary(data),
16-
handler: async ({ data }, con) => {
17-
if (isProd) {
18-
// disabled for now so I can merge the code and will enable after backfill
19-
20-
return;
21-
}
22-
18+
handler: async (data, con) => {
2319
const { userId, cv } = data.payload || {};
2420

2521
if (!cv?.blob || !cv?.bucket) {
@@ -34,14 +30,11 @@ export const parseCVProfileWorker: TypedWorker<'api.v1.candidate-preference-upda
3430
return;
3531
}
3632

37-
const user: Pick<User, 'flags'> | null = await con
38-
.getRepository(User)
39-
.findOne({
40-
select: ['flags'],
41-
where: {
42-
id: userId,
43-
},
44-
});
33+
const user = await con.getRepository(User).findOne({
34+
where: {
35+
id: userId,
36+
},
37+
});
4538

4639
if (!user) {
4740
return;
@@ -94,6 +87,16 @@ export const parseCVProfileWorker: TypedWorker<'api.v1.candidate-preference-upda
9487
userId,
9588
transaction: true,
9689
});
90+
91+
return [
92+
{
93+
type: NotificationType.ParsedCVProfile,
94+
ctx: {
95+
user,
96+
userIds: [userId],
97+
} as NotificationUserContext,
98+
},
99+
];
97100
} catch (error) {
98101
// revert to previous date on error
99102
await con.getRepository(User).update(

0 commit comments

Comments
 (0)