Skip to content

Commit 27eede9

Browse files
author
sachin-maheshwari
authored
Merge pull request #520 from topcoder-platform/dev
[PROD] Next Release
2 parents 6621a1d + f2b8e9e commit 27eede9

25 files changed

+1129
-158
lines changed

.circleci/config.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,7 @@ workflows:
6868
branches:
6969
only:
7070
- dev
71-
- change-validatations-in-job-jc
72-
- feature/enriching-skills-data-with-api-2
71+
- feature/shapeup4-cqrs-update
7372

7473
# Production builds are exectuted only on tagged commits to the
7574
# master branch.

config/default.js

+3
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ module.exports = {
9595
KAFKA_ERROR_TOPIC: process.env.KAFKA_ERROR_TOPIC || 'common.error.reporting',
9696
// The originator value for the kafka messages
9797
KAFKA_MESSAGE_ORIGINATOR: process.env.KAFKA_MESSAGE_ORIGINATOR || 'taas-api',
98+
99+
// topics for error
100+
TAAS_ERROR_TOPIC: process.env.TAAS_ERROR_TOPIC || 'taas.action.error',
98101
// topics for job service
99102
// the create job entity Kafka message topic
100103
TAAS_JOB_CREATE_TOPIC: process.env.TAAS_JOB_CREATE_TOPIC || 'taas.job.create',

data/demo-data.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"a2b4bc11-c641-4a19-9eb7-33980378f82e"
2020
],
2121
"status": "in-review",
22-
"isApplicationPageActive": false,
22+
"isApplicationPageActive": true,
2323
"minSalary": 100,
2424
"maxSalary": 200,
2525
"hoursPerWeek": 20,
@@ -51,7 +51,7 @@
5151
"a2b4bc11-c641-4a19-9eb7-33980378f82e"
5252
],
5353
"status": "in-review",
54-
"isApplicationPageActive": false,
54+
"isApplicationPageActive": true,
5555
"minSalary": 100,
5656
"maxSalary": 200,
5757
"hoursPerWeek": 80,
@@ -83,7 +83,7 @@
8383
"a2b4bc11-c641-4a19-9eb7-33980378f82e"
8484
],
8585
"status": "in-review",
86-
"isApplicationPageActive": false,
86+
"isApplicationPageActive": true,
8787
"minSalary": 100,
8888
"maxSalary": 200,
8989
"hoursPerWeek": 90,
@@ -115,7 +115,7 @@
115115
"a2b4bc11-c641-4a19-9eb7-33980378f82e"
116116
],
117117
"status": "in-review",
118-
"isApplicationPageActive": false,
118+
"isApplicationPageActive": true,
119119
"minSalary": 100,
120120
"maxSalary": 200,
121121
"hoursPerWeek": 20,
@@ -148,7 +148,7 @@
148148
"0b104b7c-0792-4118-8bc7-a274e9ee19e3"
149149
],
150150
"status": "closed",
151-
"isApplicationPageActive": false,
151+
"isApplicationPageActive": true,
152152
"minSalary": null,
153153
"maxSalary": null,
154154
"hoursPerWeek": null,

docs/swagger.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ paths:
118118
schema:
119119
type: integer
120120
description: The project id.
121+
- in: query
122+
name: isApplicationPageActive
123+
required: false
124+
schema:
125+
type: boolean
126+
description: Is application page active.
121127
- in: query
122128
name: projectIds
123129
required: false

docs/taas-ER-diagram.png

313 KB
Loading

scripts/demo-email-notifications/index.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,22 @@ async function resetNotificationRecords () {
3131
// reset completed interview records
3232
localLogger.info('reset completed interview records')
3333
const pastTime = moment.duration(config.INTERVIEW_COMPLETED_PAST_TIME)
34-
const endTimestamp = moment().subtract(pastTime).add(config.INTERVIEW_COMPLETED_MATCH_WINDOW).toDate()
34+
const completedStartTimestamp = moment().subtract(pastTime).add(config.INTERVIEW_COMPLETED_MATCH_WINDOW).toDate()
3535
const completedInterview = await Interview.findById('9efd72c3-1dc7-4ce2-9869-8cca81d0adeb')
3636
const duration = 30
37-
const completedStartTimestamp = moment().subtract(pastTime).subtract(30, 'm').toDate()
38-
await completedInterview.update({ startTimestamp: completedStartTimestamp, duration, endTimestamp, status: Interviews.Status.Scheduled, guestNames: ['guest1', 'guest2'], hostName: 'hostName' })
37+
const completedEndTimestamp = moment(completedStartTimestamp).clone().add(30, 'm').toDate()
38+
await completedInterview.update({ startTimestamp: completedStartTimestamp, duration, endTimeStamp: completedEndTimestamp, status: Interviews.Status.Scheduled, guestNames: ['guest1', 'guest2'], hostName: 'hostName' })
39+
const completedInterview2 = await Interview.findById('3144fa65-ea1a-4bec-81b0-7cb1c8845826')
40+
await completedInterview2.update({ startTimestamp: completedStartTimestamp, duration, endTimeStamp: completedEndTimestamp, status: Interviews.Status.Scheduled, guestNames: ['guest1', 'guest2'], hostName: 'hostName' })
3941

4042
// reset post interview candidate action reminder records
4143
localLogger.info('reset post interview candidate action reminder records')
42-
const jobCandidate = await JobCandidate.findById('881a19de-2b0c-4bb9-b36a-4cb5e223bdb5')
44+
const jobCandidate = await JobCandidate.findById('827ee401-df04-42e1-abbe-7b97ce7937ff')
4345
await jobCandidate.update({ status: 'interview' })
44-
const c2Interview = await Interview.findById('077aa2ca-5b60-4ad9-a965-1b37e08a5046')
45-
await c2Interview.update({ startTimestamp: moment().subtract(moment.duration(config.POST_INTERVIEW_ACTION_MATCH_WINDOW)).subtract(30, 'm').toDate(), duration, endTimestamp, guestNames: ['guest1', 'guest2'], hostName: 'hostName' })
46-
const jobCandidateWithinOneDay = await JobCandidate.findById('827ee401-df04-42e1-abbe-7b97ce7937ff')
47-
await jobCandidateWithinOneDay.update({ status: 'interview' })
48-
const interviewWithinOneDay = await Interview.findById('3144fa65-ea1a-4bec-81b0-7cb1c8845826')
49-
await interviewWithinOneDay.update({ startTimestamp: completedStartTimestamp, duration, endTimestamp, guestNames: ['guest1', 'guest2'], hostName: 'hostName' })
46+
const c2Interview = await Interview.findById('3144fa65-ea1a-4bec-81b0-7cb1c8845826')
47+
await c2Interview.update({ startTimestamp: moment().subtract(moment.duration(config.POST_INTERVIEW_ACTION_MATCH_WINDOW)).subtract(30, 'm').toDate(), duration, endTimeStamp: completedEndTimestamp, guestNames: ['guest1', 'guest2'], hostName: 'hostName' })
48+
const c2InterviewR2 = await Interview.findById('b1f7ba76-640f-47e2-9463-59e51b51ec60')
49+
await c2InterviewR2.update({ status: 'Scheduled', startTimestamp: moment().subtract(moment.duration(config.POST_INTERVIEW_ACTION_MATCH_WINDOW)).subtract(30, 'm').toDate(), duration, endTimeStamp: completedEndTimestamp, guestNames: ['guest1', 'guest2'], hostName: 'hostName' })
5050

5151
// reset upcoming resource booking expiration records
5252
localLogger.info('reset upcoming resource booking expiration records')

src/common/helper.js

+21
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,26 @@ async function postEvent (topic, payload, options = {}) {
999999
await eventDispatcher.handleEvent(topic, { value: payload, options })
10001000
}
10011001

1002+
/**
1003+
* Send error event to Kafka
1004+
* @params {String} topic the topic name
1005+
* @params {Object} payload the payload
1006+
* @params {String} action for which operation error occurred
1007+
*/
1008+
async function postErrorEvent (topic, payload, action) {
1009+
_.set(payload, 'apiAction', action)
1010+
const client = getBusApiClient()
1011+
const message = {
1012+
topic,
1013+
originator: config.KAFKA_MESSAGE_ORIGINATOR,
1014+
timestamp: new Date().toISOString(),
1015+
'mime-type': 'application/json',
1016+
payload
1017+
}
1018+
logger.debug(`Publish error to Kafka topic ${topic}, ${JSON.stringify(message, null, 2)}`)
1019+
await client.postEvent(message)
1020+
}
1021+
10021022
/**
10031023
* Test if an error is document missing exception
10041024
*
@@ -2094,6 +2114,7 @@ module.exports = {
20942114
getM2MToken,
20952115
getM2MUbahnToken,
20962116
postEvent,
2117+
postErrorEvent,
20972118
getBusApiClient,
20982119
isDocumentMissingException,
20992120
getProjects,
+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/**
2+
* Interview Processor
3+
*/
4+
5+
const _ = require('lodash')
6+
const helper = require('../common/helper')
7+
const config = require('config')
8+
9+
const esClient = helper.getESClient()
10+
11+
/**
12+
* Updates jobCandidate via a painless script
13+
*
14+
* @param {String} jobCandidateId job candidate id
15+
* @param {String} script script definition
16+
*/
17+
async function updateJobCandidateViaScript (jobCandidateId, script) {
18+
await esClient.update({
19+
index: config.get('esConfig.ES_INDEX_JOB_CANDIDATE'),
20+
id: jobCandidateId,
21+
body: { script },
22+
refresh: 'wait_for'
23+
})
24+
}
25+
26+
/**
27+
* Process request interview entity.
28+
* Creates an interview record under jobCandidate.
29+
*
30+
* @param {Object} interview interview object
31+
*/
32+
async function processRequestInterview (interview) {
33+
// add interview in collection if there's already an existing collection
34+
// or initiate a new one with this interview
35+
const script = {
36+
source: `
37+
ctx._source.containsKey("interviews")
38+
? ctx._source.interviews.add(params.interview)
39+
: ctx._source.interviews = [params.interview]
40+
`,
41+
params: { interview }
42+
}
43+
await updateJobCandidateViaScript(interview.jobCandidateId, script)
44+
}
45+
46+
/**
47+
* Process update interview entity
48+
* Updates the interview record under jobCandidate.
49+
*
50+
* @param {Object} interview interview object
51+
*/
52+
async function processUpdateInterview (interview) {
53+
// if there's an interview with this id,
54+
// update it
55+
const script = {
56+
source: `
57+
if (ctx._source.containsKey("interviews")) {
58+
def target = ctx._source.interviews.find(i -> i.id == params.interview.id);
59+
if (target != null) {
60+
for (prop in params.interview.entrySet()) {
61+
target[prop.getKey()] = prop.getValue()
62+
}
63+
}
64+
}
65+
`,
66+
params: { interview }
67+
}
68+
await updateJobCandidateViaScript(interview.jobCandidateId, script)
69+
}
70+
71+
/**
72+
* Process bulk (partially) update interviews entity.
73+
* Currently supports status, updatedAt and updatedBy fields.
74+
* Update Joi schema to allow more fields.
75+
* (implementation should already handle new fields - just updating Joi schema should be enough)
76+
*
77+
* payload format:
78+
* {
79+
* "jobCandidateId": {
80+
* "interviewId": { ...fields },
81+
* "interviewId2": { ...fields },
82+
* ...
83+
* },
84+
* "jobCandidateId2": { // like above... },
85+
* ...
86+
* }
87+
*
88+
* @param {Object} jobCandidates job candidates
89+
*/
90+
async function processBulkUpdateInterviews (jobCandidates) {
91+
// script to update & params
92+
const script = {
93+
source: `
94+
def completedInterviews = params.jobCandidates[ctx._id];
95+
for (interview in completedInterviews.entrySet()) {
96+
def interviewId = interview.getKey();
97+
def affectedFields = interview.getValue();
98+
def target = ctx._source.interviews.find(i -> i.id == interviewId);
99+
if (target != null) {
100+
for (field in affectedFields.entrySet()) {
101+
target[field.getKey()] = field.getValue();
102+
}
103+
}
104+
}
105+
`,
106+
params: { jobCandidates }
107+
}
108+
// update interviews
109+
await esClient.updateByQuery({
110+
index: config.get('esConfig.ES_INDEX_JOB_CANDIDATE'),
111+
body: {
112+
script,
113+
query: {
114+
ids: {
115+
values: _.keys(jobCandidates)
116+
}
117+
}
118+
},
119+
refresh: true
120+
})
121+
}
122+
123+
module.exports = {
124+
processRequestInterview,
125+
processUpdateInterview,
126+
processBulkUpdateInterviews
127+
}
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Jobcandidate Processor
3+
*/
4+
5+
const config = require('config')
6+
const helper = require('../common/helper')
7+
8+
const esClient = helper.getESClient()
9+
10+
/**
11+
* Process create entity
12+
* @param {Object} entity entity object
13+
*/
14+
async function processCreate (entity) {
15+
await esClient.create({
16+
index: config.get('esConfig.ES_INDEX_JOB_CANDIDATE'),
17+
id: entity.id,
18+
body: entity,
19+
refresh: 'wait_for'
20+
})
21+
}
22+
23+
/**
24+
* Process update entity
25+
* @param {Object} entity entity object
26+
*/
27+
async function processUpdate (entity) {
28+
await esClient.update({
29+
index: config.get('esConfig.ES_INDEX_JOB_CANDIDATE'),
30+
id: entity.id,
31+
body: {
32+
doc: entity
33+
},
34+
refresh: 'wait_for'
35+
})
36+
}
37+
38+
/**
39+
* Process delete entity
40+
* @param {Object} entity entity object
41+
*/
42+
async function processDelete (entity) {
43+
await esClient.delete({
44+
index: config.get('esConfig.ES_INDEX_JOB_CANDIDATE'),
45+
id: entity.id,
46+
refresh: 'wait_for'
47+
})
48+
}
49+
50+
module.exports = {
51+
processCreate,
52+
processUpdate,
53+
processDelete
54+
}

src/esProcessors/JobProcessor.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Job Processor
3+
*/
4+
5+
const helper = require('../common/helper')
6+
const config = require('config')
7+
8+
const esClient = helper.getESClient()
9+
10+
/**
11+
* Process create entity
12+
* @param {Object} entity entity object
13+
*/
14+
async function processCreate (entity) {
15+
await esClient.create({
16+
index: config.get('esConfig.ES_INDEX_JOB'),
17+
id: entity.id,
18+
body: entity,
19+
refresh: 'wait_for'
20+
})
21+
}
22+
23+
/**
24+
* Process update entity
25+
* @param {Object} entity entity object
26+
*/
27+
async function processUpdate (entity) {
28+
await esClient.update({
29+
index: config.get('esConfig.ES_INDEX_JOB'),
30+
id: entity.id,
31+
body: {
32+
doc: entity
33+
},
34+
refresh: 'wait_for'
35+
})
36+
}
37+
38+
/**
39+
* Process delete entity
40+
* @param {Object} entity entity object
41+
*/
42+
async function processDelete (entity) {
43+
await esClient.delete({
44+
index: config.get('esConfig.ES_INDEX_JOB'),
45+
id: entity.id,
46+
refresh: 'wait_for'
47+
})
48+
}
49+
50+
module.exports = {
51+
processCreate,
52+
processUpdate,
53+
processDelete
54+
}

0 commit comments

Comments
 (0)