Skip to content

Commit dda4852

Browse files
Merge branch 'develop' into gig-filters-url
2 parents edbd33a + f3bb925 commit dda4852

File tree

10 files changed

+1156
-52
lines changed

10 files changed

+1156
-52
lines changed

.circleci/config.yml

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -275,37 +275,36 @@ workflows:
275275
filters:
276276
branches:
277277
only:
278-
- gig-filters-url
279-
- feature/recommender-sync-develop
278+
- develop
280279
# This is alternate dev env for parallel testing
281280
- "build-test":
282281
context : org-global
283282
filters:
284283
branches:
285-
only:
286-
- FAQ-theme
284+
only:
285+
- free
286+
287287
# This is alternate dev env for parallel testing
288288
- "build-qa":
289289
context : org-global
290290
filters:
291291
branches:
292292
only:
293-
- bug-bash
293+
- free
294294
# This is beta env for production soft releases
295295
- "build-prod-beta":
296296
context : org-global
297297
filters:
298298
branches:
299299
only:
300-
- bug-bash
300+
- free
301301
# This is stage env for production QA releases
302302
- "build-prod-staging":
303303
context : org-global
304304
filters:
305305
branches:
306306
only:
307307
- develop
308-
- feature/recommender-sync-develop
309308
- "approve-smoke-test-on-staging":
310309
type: approval
311310
requires:

src/assets/images/thinking-face-laptop-tablet.svg

Lines changed: 433 additions & 0 deletions
Loading

src/assets/images/thinking-face-mobile.svg

Lines changed: 433 additions & 0 deletions
Loading

src/server/services/recruitCRM.js

Lines changed: 103 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,62 @@ import fetch from 'isomorphic-fetch';
55
import config from 'config';
66
import qs from 'qs';
77
import _ from 'lodash';
8+
import { logger } from 'topcoder-react-lib';
89
import GrowsurfService from './growsurf';
10+
import { sendEmailDirect } from './sendGrid';
911

1012
const FormData = require('form-data');
1113

14+
const JOB_FIELDS_RESPONSE = [
15+
'id',
16+
'slug',
17+
'country',
18+
'locality',
19+
'city',
20+
'name',
21+
'custom_fields',
22+
'enable_job_application_form',
23+
'created_on',
24+
'updated_on',
25+
'min_annual_salary',
26+
'salary_type',
27+
'max_annual_salary',
28+
'job_description_text',
29+
];
30+
const CANDIDATE_FIELDS_RESPONSE = [
31+
'id',
32+
'slug',
33+
'first_name',
34+
'last_name',
35+
'email',
36+
'contact_number',
37+
'skill',
38+
'resume',
39+
'locality',
40+
'salary_expectation',
41+
'custom_fields',
42+
];
43+
44+
/**
45+
* Send email to Kiril/Nick for debuging gig application errors
46+
* @param {Object} error the error
47+
*/
48+
function notifyKirilAndNick(error) {
49+
logger.error(error);
50+
sendEmailDirect({
51+
personalizations: [
52+
{
53+
to: [{ email: '[email protected]' }, { email: '[email protected]' }],
54+
subject: 'Gig application error alert',
55+
},
56+
],
57+
from: { email: '[email protected]' },
58+
content: [{
59+
type: 'text/plain', value: `The error occured as JSON string:\n\n ${JSON.stringify(error)}`,
60+
}],
61+
});
62+
}
63+
1264
/**
1365
* Auxiliary class that handles communication with recruitCRM
1466
*/
@@ -44,13 +96,17 @@ export default class RecruitCRMService {
4496
return this.getJobs(req, res, next);
4597
}
4698
if (response.status >= 400) {
47-
return res.send({
99+
const error = {
48100
error: true,
49101
status: response.status,
50102
url: `${this.private.baseUrl}/v1/jobs/search?${qs.stringify(req.query)}`,
51-
});
103+
errObj: await response.json(),
104+
};
105+
logger.error(error);
106+
return res.send(error);
52107
}
53108
const data = await response.json();
109+
data.data = _.map(data.data, j => _.pick(j, JOB_FIELDS_RESPONSE));
54110
return res.send(data);
55111
} catch (err) {
56112
return next(err);
@@ -76,14 +132,17 @@ export default class RecruitCRMService {
76132
return this.getJob(req, res, next);
77133
}
78134
if (response.status >= 400) {
79-
return res.send({
135+
const error = {
80136
error: true,
81137
status: response.status,
82138
url: `${this.private.baseUrl}/v1/jobs/${req.params.id}`,
83-
});
139+
errObj: await response.json(),
140+
};
141+
logger.error(error);
142+
return res.send(error);
84143
}
85144
const data = await response.json();
86-
return res.send(data);
145+
return res.send(_.pick(data, JOB_FIELDS_RESPONSE));
87146
} catch (err) {
88147
return next(err);
89148
}
@@ -108,11 +167,14 @@ export default class RecruitCRMService {
108167
return this.getJobs(req, res, next);
109168
}
110169
if (response.status >= 400) {
111-
return res.send({
170+
const error = {
112171
error: true,
113172
status: response.status,
114173
url: `${this.private.baseUrl}/v1/jobs/search?${qs.stringify(req.query)}`,
115-
});
174+
errObj: await response.json(),
175+
};
176+
logger.error(error);
177+
return res.send(error);
116178
}
117179
const data = await response.json();
118180
if (data.current_page < data.last_page) {
@@ -133,13 +195,17 @@ export default class RecruitCRMService {
133195
const pageData = await pageDataRsp.json();
134196
data.data = _.flatten(data.data.concat(pageData.data));
135197
}
136-
return res.send(data.data);
198+
return res.send(
199+
_.map(data.data, j => _.pick(j, JOB_FIELDS_RESPONSE)),
200+
);
137201
})
138202
.catch(e => res.send({
139203
error: e,
140204
}));
141205
}
142-
return res.send(data.data);
206+
return res.send(
207+
_.map(data.data, j => _.pick(j, JOB_FIELDS_RESPONSE)),
208+
);
143209
} catch (err) {
144210
return next(err);
145211
}
@@ -164,13 +230,17 @@ export default class RecruitCRMService {
164230
return this.searchCandidates(req, res, next);
165231
}
166232
if (response.status >= 400) {
167-
return res.send({
233+
const error = {
168234
error: true,
169235
status: response.status,
170236
url: `${this.private.baseUrl}/v1/candidates/search?${qs.stringify(req.query)}`,
171-
});
237+
errObj: await response.json(),
238+
};
239+
logger.error(error);
240+
return res.send(error);
172241
}
173242
const data = await response.json();
243+
data.data = _.map(data.data, j => _.pick(j, CANDIDATE_FIELDS_RESPONSE));
174244
return res.send(data);
175245
} catch (err) {
176246
return next(err);
@@ -215,6 +285,8 @@ export default class RecruitCRMService {
215285
form.custom_fields.push({
216286
field_id: 6, value: `https://app.growsurf.com/dashboard/campaign/${config.GROWSURF_CAMPAIGN_ID}/participant/${growRes.id}`,
217287
});
288+
} else {
289+
notifyKirilAndNick(growRes);
218290
}
219291
// clear the cookie
220292
res.cookie(config.GROWSURF_COOKIE, '', {
@@ -231,12 +303,14 @@ export default class RecruitCRMService {
231303
},
232304
});
233305
if (candidateResponse.status >= 300) {
234-
return res.send({
306+
const error = {
235307
error: true,
236308
status: candidateResponse.status,
237309
url: `${this.private.baseUrl}/v1/candidates/search?email=${form.email}`,
238310
errObj: await candidateResponse.json(),
239-
});
311+
};
312+
notifyKirilAndNick(error);
313+
return res.send(error);
240314
}
241315
let candidateData = await candidateResponse.json();
242316
if (candidateData.data) {
@@ -265,13 +339,15 @@ export default class RecruitCRMService {
265339
body: JSON.stringify(form),
266340
});
267341
if (workCandidateResponse.status >= 300) {
268-
return res.send({
342+
const error = {
269343
error: true,
270344
status: workCandidateResponse.status,
271345
url: `${this.private.baseUrl}/v1/candidates${candidateSlug ? `/${candidateSlug}` : ''}`,
272346
form,
273347
errObj: await workCandidateResponse.json(),
274-
});
348+
};
349+
notifyKirilAndNick(error);
350+
return res.send(error);
275351
}
276352
candidateData = await workCandidateResponse.json();
277353
// Attach resume to candidate if uploaded
@@ -286,7 +362,7 @@ export default class RecruitCRMService {
286362
body: fileData,
287363
});
288364
if (fileCandidateResponse.status >= 300) {
289-
return res.send({
365+
const error = {
290366
error: true,
291367
status: fileCandidateResponse.status,
292368
url: `${this.private.baseUrl}/v1/candidates/${candidateData.slug}`,
@@ -295,7 +371,9 @@ export default class RecruitCRMService {
295371
file,
296372
formHeaders,
297373
errObj: await fileCandidateResponse.json(),
298-
});
374+
};
375+
notifyKirilAndNick(error);
376+
return res.send(error);
299377
}
300378
candidateData = await fileCandidateResponse.json();
301379
}
@@ -314,14 +392,16 @@ export default class RecruitCRMService {
314392
success: true,
315393
});
316394
}
317-
return res.send({
395+
const error = {
318396
error: true,
319397
status: applyResponse.status,
320398
url: `${this.private.baseUrl}/v1/candidates/${candidateData.slug}/assign?job_slug=${id}`,
321399
form,
322400
candidateData,
323401
errObj,
324-
});
402+
};
403+
notifyKirilAndNick(error);
404+
return res.send(error);
325405
}
326406
// Set hired-stage
327407
const hireStageResponse = await fetch(`${this.private.baseUrl}/v1/candidates/${candidateData.slug}/hiring-stages/${id}`, {
@@ -337,13 +417,15 @@ export default class RecruitCRMService {
337417
}),
338418
});
339419
if (hireStageResponse.status >= 300) {
340-
return res.send({
420+
const error = {
341421
error: true,
342422
status: hireStageResponse.status,
343423
url: `$${this.private.baseUrl}/v1/candidates/${candidateData.slug}/hiring-stages/${id}`,
344424
form,
345425
errObj: await hireStageResponse.json(),
346-
});
426+
};
427+
notifyKirilAndNick(error);
428+
return res.send(error);
347429
}
348430
// respond to API call
349431
const data = await applyResponse.json();

src/server/services/sendGrid.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,33 @@ export const sendEmail = async (req, res) => {
3838
}
3939
};
4040

41+
/**
42+
* Send email directly via the SendGrid API
43+
* @param {Object} msg the payload
44+
* @returns Promise
45+
*/
46+
export const sendEmailDirect = async (msg) => {
47+
try {
48+
const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
49+
method: 'POST',
50+
headers: {
51+
'Content-Type': 'application/json',
52+
Authorization: `Bearer ${config.SECRET.SENDGRID_API_KEY}`,
53+
},
54+
body: JSON.stringify(msg),
55+
});
56+
return response;
57+
} catch (error) {
58+
logger.error(error);
59+
const { message, code, response } = error;
60+
if (error.response) {
61+
const { headers, body } = response;
62+
return {
63+
code, message, headers, body,
64+
};
65+
}
66+
return { message };
67+
}
68+
};
69+
4170
export default undefined;

src/shared/components/Contentful/Route.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { Route, Switch, Redirect } from 'react-router-dom';
1414
import Viewport from 'components/Contentful/Viewport';
1515
import PasswordScreen from 'components/Contentful/PasswordScreen';
1616
import { isomorphy } from 'topcoder-react-utils';
17+
import { removeTrailingSlash } from 'utils/url';
1718

1819
// Concatenates a base and segment and handles optional trailing slashes
1920
const buildUrl = (base, segment) => `${_.trimEnd(base, '/')}/${_.trim(segment, '/')}`;
@@ -169,8 +170,10 @@ export default function ContentfulRoute(props) {
169170
render={(data) => {
170171
const { fields } = Object.values(data.entries.items)[0];
171172
const url = path || buildUrl(baseUrl, fields.url);
173+
// eslint-disable-next-line no-restricted-globals
174+
const currentPathname = typeof location === 'undefined' ? '' : removeTrailingSlash(location.pathname);
172175
const redirectToUrl = _.trim(fields.redirectToUrl);
173-
return redirectToUrl ? (
176+
return redirectToUrl && currentPathname === url ? (
174177
<RedirectWithStatus status={301} from={url} to={redirectToUrl} />
175178
) : (
176179
<Route

src/shared/components/Gigs/GigApply/index.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ export default function GigApply(props) {
5353
{
5454
application.error ? (
5555
<React.Fragment>
56-
<p styleName="error-text">{application.errorObj.message || JSON.stringify(application.errorObj)}</p>
56+
{
57+
application.errorObj ? (
58+
<p styleName="error-text">{application.errorObj.message || JSON.stringify(application.errorObj)}</p>
59+
) : null
60+
}
5761
<p>Looks like there is a problem on our end. Please try again.<br />If this persists please contact <a href="mailto:[email protected]">[email protected]</a>.</p>
5862
<p>Please send us an email at <a href="mailto:[email protected]">[email protected]</a> with the subject ‘Gig Error’<br />and paste the URL for the gig you are attempting to apply for so that we know of your interest.</p>
5963
</React.Fragment>

0 commit comments

Comments
 (0)