Skip to content

Commit fa2f4cd

Browse files
Merge pull request #12 from topcoder-platform/dev
Dry Release v0.2
2 parents eb5dc09 + 378879b commit fa2f4cd

10 files changed

+136
-43
lines changed

README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ The following parameters can be set in config files or in env variables:
4747

4848
- `zapier.ZAPIER_COMPANYID_SLUG`: your company id in zapier; numeric value
4949
- `zapier.ZAPIER_CONTACTID_SLUG`: your contact id in zapier; numeric value
50-
- `zapier.ZAPIER_SWITCH`: decides whether posting message to zapier or not; possible values are `ON` and `OFF`, default is `OFF`
51-
- `zapier.ZAPIER_WEBHOOK`: the remote zapier zap webhook url for posting message
50+
- `zapier.ZAPIER_SWITCH`: decides whether posting job related message to zapier or not; possible values are `ON` and `OFF`, default is `OFF`
51+
- `zapier.ZAPIER_WEBHOOK`: the remote zapier zap webhook url for posting job related message
52+
- `zapier.ZAPIER_JOB_CANDIDATE_SWITCH`: decides whether posting job candidate related message to zapier or not; possible values are `ON` and `OFF`, default is `OFF`
53+
- `zapier.ZAPIER_JOB_CANDIDATE_WEBHOOK`: the remote zapier zap webhook url for posting job candidate related message
5254

5355
## Local Kafka and ElasticSearch setup
5456

config/default.js

+2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ module.exports = {
5858
ZAPIER_CONTACTID_SLUG: process.env.ZAPIER_CONTACTID_SLUG,
5959
ZAPIER_SWITCH: process.env.ZAPIER_SWITCH || 'OFF',
6060
ZAPIER_WEBHOOK: process.env.ZAPIER_WEBHOOK,
61+
ZAPIER_JOB_CANDIDATE_SWITCH: process.env.ZAPIER_JOB_CANDIDATE_SWITCH || 'OFF',
62+
ZAPIER_JOB_CANDIDATE_WEBHOOK: process.env.ZAPIER_JOB_CANDIDATE_WEBHOOK,
6163
TOPCODER_API_URL: process.env.TOPCODER_API_URL || 'http://api.topcoder-dev.com/v5'
6264
}
6365
}

src/app.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ const eventEmitter = new events.EventEmitter()
2121
process.env.PORT = config.PORT
2222

2323
const localLogger = {
24-
'info': (message) => logger.info({ component: 'app', message }),
25-
'debug': (message) => logger.debug({ component: 'app', message }),
26-
'error': (message) => logger.error({ component: 'app', message })
24+
info: (message) => logger.info({ component: 'app', message }),
25+
debug: (message) => logger.debug({ component: 'app', message }),
26+
error: (message) => logger.error({ component: 'app', message })
2727
}
2828

2929
const topicServiceMapping = {
@@ -47,7 +47,7 @@ localLogger.info('Starting kafka consumer')
4747
const consumer = new Kafka.GroupConsumer(helper.getKafkaOptions())
4848

4949
let count = 0
50-
let mutex = new Mutex()
50+
const mutex = new Mutex()
5151

5252
async function getLatestCount () {
5353
const release = await mutex.acquire()
@@ -71,7 +71,7 @@ const dataHandler = (messageSet, topic, partition) => Promise.each(messageSet, a
7171
localLogger.info(`Handle Kafka event message; Topic: ${topic}; Partition: ${partition}; Offset: ${
7272
m.offset}; Message: ${message}.`)
7373
let messageJSON
74-
let messageCount = await getLatestCount()
74+
const messageCount = await getLatestCount()
7575

7676
localLogger.debug(`Current message count: ${messageCount}`)
7777
try {

src/bootstrap.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ global.Promise = require('bluebird')
66

77
Joi.rateType = () => Joi.string().valid('hourly', 'daily', 'weekly', 'monthly')
88
Joi.jobStatus = () => Joi.string().valid('sourcing', 'in-review', 'assigned', 'closed', 'cancelled')
9-
Joi.jobCandidateStatus = () => Joi.string().valid('open', 'selected', 'shortlist', 'rejected')
9+
Joi.jobCandidateStatus = () => Joi.string().valid('open', 'selected', 'shortlist', 'rejected', 'cancelled')
1010
Joi.workload = () => Joi.string().valid('full-time', 'fractional')
11+
Joi.title = () => Joi.string().max(64)
1112

1213
const zapierSwitch = Joi.string().label('ZAPIER_SWITCH').valid(...Object.values(constants.Zapier.Switch))
1314

1415
// validate configuration
1516
try {
1617
Joi.attempt(config.zapier.ZAPIER_SWITCH, zapierSwitch)
18+
Joi.attempt(config.zapier.ZAPIER_JOB_CANDIDATE_SWITCH, zapierSwitch)
1719
} catch (err) {
1820
console.error(err.message)
1921
process.exit(1)

src/common/constants.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ module.exports = {
1212
},
1313
MessageType: {
1414
JobCreate: 'job:create',
15-
JobUpdate: 'job:update'
15+
JobUpdate: 'job:update',
16+
JobCandidateCreate: 'jobcandidate:create',
17+
JobCandidateUpdate: 'jobcandidate:update'
1618
}
1719
}
1820
}

src/common/helper.js

+7-22
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ const elasticsearch = require('@elastic/elasticsearch')
1010
const _ = require('lodash')
1111
const { Mutex } = require('async-mutex')
1212
const m2mAuth = require('tc-core-library-js').auth.m2m
13-
const constants = require('./constants')
1413

1514
AWS.config.region = config.esConfig.AWS_REGION
1615

@@ -154,33 +153,19 @@ async function getM2MToken () {
154153
/**
155154
* Post message to zapier via webhook url.
156155
*
157-
* @param {Object} message the message object
156+
* @param {String} webhook the webhook url
157+
* @param {Object} message the message data
158158
* @returns {undefined}
159159
*/
160-
async function postMessageToZapier ({ type, payload }) {
161-
if (config.zapier.ZAPIER_SWITCH === constants.Zapier.Switch.OFF) {
162-
logger.debug({ component: 'helper', context: 'postMessageToZapier', message: 'Zapier Switch off via config, no messages sent' })
163-
return
164-
}
165-
const requestBody = {
166-
type,
167-
payload,
168-
companySlug: config.zapier.ZAPIER_COMPANYID_SLUG,
169-
contactSlug: config.zapier.ZAPIER_CONTACTID_SLUG
170-
}
171-
if (type === constants.Zapier.MessageType.JobCreate) {
172-
const token = await getM2MToken()
173-
requestBody.authToken = token
174-
requestBody.topcoderApiUrl = config.zapier.TOPCODER_API_URL
175-
}
176-
logger.debug({ component: 'helper', context: 'postMessageToZapier', message: `request body: ${JSON.stringify(requestBody)}` })
177-
await request.post(config.zapier.ZAPIER_WEBHOOK)
178-
.send(requestBody)
160+
async function postMessageViaWebhook (webhook, message) {
161+
logger.debug({ component: 'helper', context: 'postMessageToZapier', message: `message: ${JSON.stringify(message)}` })
162+
await request.post(webhook).send(message)
179163
}
180164

181165
module.exports = {
182166
getKafkaOptions,
183167
getESClient,
184168
checkEsMutexRelease,
185-
postMessageToZapier
169+
getM2MToken,
170+
postMessageViaWebhook
186171
}

src/scripts/createIndex.js

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ async function createIndex () {
1818
projectId: { type: 'integer' },
1919
externalId: { type: 'keyword' },
2020
description: { type: 'text' },
21+
title: { type: 'text' },
2122
startDate: { type: 'date' },
2223
endDate: { type: 'date' },
2324
numPositions: { type: 'integer' },
@@ -42,6 +43,8 @@ async function createIndex () {
4243
jobId: { type: 'keyword' },
4344
userId: { type: 'keyword' },
4445
status: { type: 'keyword' },
46+
externalId: { type: 'keyword' },
47+
resume: { type: 'text' },
4548
createdAt: { type: 'date' },
4649
createdBy: { type: 'keyword' },
4750
updatedAt: { type: 'date' },

src/services/JobCandidateProcessorService.js

+67-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,64 @@ const config = require('config')
1111

1212
const esClient = helper.getESClient()
1313

14+
const localLogger = {
15+
debug: ({ context, message }) => logger.debug({ component: 'JobCandidateProcessorService', context, message })
16+
}
17+
18+
/**
19+
* Update job candidate status in recruit CRM.
20+
*
21+
* @param {Object} message the message object
22+
* @returns {undefined}
23+
*/
24+
async function updateCandidateStatus ({ type, payload }) {
25+
if (!payload.status) {
26+
localLogger.debug({ context: 'updateCandidateStatus', message: 'status not updated' })
27+
return
28+
}
29+
if (!['rejected', 'shortlist'].includes(payload.status)) {
30+
localLogger.debug({ context: 'updateCandidateStatus', message: `not interested status: ${payload.status}` })
31+
return
32+
}
33+
const { body: jobCandidate } = await esClient.getSource({
34+
index: config.get('esConfig.ES_INDEX_JOB_CANDIDATE'),
35+
id: payload.id
36+
})
37+
if (!jobCandidate.externalId) {
38+
localLogger.debug({ context: 'updateCandidateStatus', message: `id: ${jobCandidate.id} candidate without externalId - ignored` })
39+
return
40+
}
41+
const { body: job } = await esClient.getSource({
42+
index: config.get('esConfig.ES_INDEX_JOB'),
43+
id: jobCandidate.jobId
44+
})
45+
const message = {
46+
type,
47+
status: jobCandidate.status,
48+
jobCandidateSlug: jobCandidate.externalId,
49+
jobSlug: job.externalId
50+
}
51+
await helper.postMessageViaWebhook(config.zapier.ZAPIER_JOB_CANDIDATE_WEBHOOK, message)
52+
}
53+
54+
/**
55+
* Post message to zapier for JobCandidate.
56+
*
57+
* @param {Object} message the message object
58+
* @returns {undefined}
59+
*/
60+
async function postMessageToZapier ({ type, payload }) {
61+
if (config.zapier.ZAPIER_JOB_CANDIDATE_SWITCH === constants.Zapier.Switch.OFF) {
62+
localLogger.debug({ context: 'postMessageToZapier', message: 'Zapier Switch off via config, no messages sent' })
63+
return
64+
}
65+
if (type === constants.Zapier.MessageType.JobCandidateUpdate) {
66+
await updateCandidateStatus({ type, payload })
67+
return
68+
}
69+
throw new Error(`unrecognized message type: ${type}`)
70+
}
71+
1472
/**
1573
* Process create entity message
1674
* @param {Object} message the kafka message
@@ -39,7 +97,9 @@ processCreate.schema = {
3997
userId: Joi.string().uuid().required(),
4098
createdAt: Joi.date().required(),
4199
createdBy: Joi.string().uuid().required(),
42-
status: Joi.jobCandidateStatus().required()
100+
status: Joi.jobCandidateStatus().required(),
101+
externalId: Joi.string(),
102+
resume: Joi.string().uri()
43103
}).required()
44104
}).required(),
45105
transactionId: Joi.string().required()
@@ -61,6 +121,10 @@ async function processUpdate (message, transactionId) {
61121
},
62122
refresh: constants.esRefreshOption
63123
})
124+
await postMessageToZapier({
125+
type: constants.Zapier.MessageType.JobCandidateUpdate,
126+
payload: data
127+
})
64128
}
65129

66130
processUpdate.schema = {
@@ -74,6 +138,8 @@ processUpdate.schema = {
74138
jobId: Joi.string().uuid(),
75139
userId: Joi.string().uuid(),
76140
status: Joi.jobCandidateStatus(),
141+
externalId: Joi.string(),
142+
resume: Joi.string().uri(),
77143
updatedAt: Joi.date(),
78144
updatedBy: Joi.string().uuid()
79145
}).required()

src/services/JobProcessorService.js

+38-7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,35 @@ const config = require('config')
1111

1212
const esClient = helper.getESClient()
1313

14+
const localLogger = {
15+
debug: ({ context, message }) => logger.debug({ component: 'JobProcessorService', context, message })
16+
}
17+
18+
/**
19+
* Post message to zapier for Job.
20+
*
21+
* @param {Object} message the message object
22+
* @returns {undefined}
23+
*/
24+
async function postMessageToZapier ({ type, payload }) {
25+
if (config.zapier.ZAPIER_SWITCH === constants.Zapier.Switch.OFF) {
26+
localLogger.debug({ context: 'postMessageToZapier', message: 'Zapier Switch off via config, no messages sent' })
27+
return
28+
}
29+
const message = {
30+
type,
31+
payload,
32+
companySlug: config.zapier.ZAPIER_COMPANYID_SLUG,
33+
contactSlug: config.zapier.ZAPIER_CONTACTID_SLUG
34+
}
35+
if (type === constants.Zapier.MessageType.JobCreate) {
36+
const token = await helper.getM2MToken()
37+
message.authToken = token
38+
message.topcoderApiUrl = config.zapier.TOPCODER_API_URL
39+
}
40+
await helper.postMessageViaWebhook(config.zapier.ZAPIER_WEBHOOK, message)
41+
}
42+
1443
/**
1544
* Process create entity message
1645
* @param {Object} message the kafka message
@@ -25,7 +54,7 @@ async function processCreate (message, transactionId) {
2554
body: _.omit(job, 'id'),
2655
refresh: constants.esRefreshOption
2756
})
28-
await helper.postMessageToZapier({
57+
await postMessageToZapier({
2958
type: constants.Zapier.MessageType.JobCreate,
3059
payload: job
3160
})
@@ -40,12 +69,13 @@ processCreate.schema = {
4069
payload: Joi.object().keys({
4170
id: Joi.string().uuid().required(),
4271
projectId: Joi.number().integer().required(),
43-
externalId: Joi.string().required(),
44-
description: Joi.string().required(),
45-
startDate: Joi.date().required(),
46-
endDate: Joi.date().required(),
72+
externalId: Joi.string(),
73+
description: Joi.string(),
74+
title: Joi.title().required(),
75+
startDate: Joi.date(),
76+
endDate: Joi.date(),
4777
numPositions: Joi.number().integer().min(1).required(),
48-
resourceType: Joi.string().required(),
78+
resourceType: Joi.string(),
4979
rateType: Joi.rateType(),
5080
workload: Joi.workload(),
5181
skills: Joi.array().items(Joi.string().uuid()).required(),
@@ -73,7 +103,7 @@ async function processUpdate (message, transactionId) {
73103
},
74104
refresh: constants.esRefreshOption
75105
})
76-
await helper.postMessageToZapier({
106+
await postMessageToZapier({
77107
type: constants.Zapier.MessageType.JobUpdate,
78108
payload: data
79109
})
@@ -90,6 +120,7 @@ processUpdate.schema = {
90120
projectId: Joi.number().integer(),
91121
externalId: Joi.string(),
92122
description: Joi.string(),
123+
title: Joi.title(),
93124
startDate: Joi.date(),
94125
endDate: Joi.date(),
95126
numPositions: Joi.number().integer().min(1),

src/services/ResourceBookingProcessorService.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ processCreate.schema = {
3838
projectId: Joi.number().integer().required(),
3939
userId: Joi.string().uuid().required(),
4040
jobId: Joi.string().uuid(),
41-
startDate: Joi.date().required(),
42-
endDate: Joi.date().required(),
43-
memberRate: Joi.number().required(),
44-
customerRate: Joi.number().required(),
41+
startDate: Joi.date(),
42+
endDate: Joi.date(),
43+
memberRate: Joi.number(),
44+
customerRate: Joi.number(),
4545
rateType: Joi.rateType().required(),
4646
createdAt: Joi.date().required(),
4747
createdBy: Joi.string().uuid().required(),

0 commit comments

Comments
 (0)