Skip to content

Commit d6001eb

Browse files
Merge pull request #5395 from topcoder-platform/develop
Release 2021/02/25 (v1.7.9)
2 parents d719f76 + 0d5ada6 commit d6001eb

File tree

29 files changed

+963
-116
lines changed

29 files changed

+963
-116
lines changed

.circleci/config.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ workflows:
289289
filters:
290290
branches:
291291
only:
292-
- free
292+
- gigs-housekeep
293293
# This is beta env for production soft releases
294294
- "build-prod-beta":
295295
context : org-global
@@ -304,7 +304,6 @@ workflows:
304304
branches:
305305
only:
306306
- develop
307-
- nav-vanilla-forum
308307
- "approve-smoke-test-on-staging":
309308
type: approval
310309
requires:

Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ ARG AUTH_SECRET
6767
ARG COMMUNITY_APP_URL
6868
ARG GSHEETS_API_KEY
6969

70+
# Gig work referrals
71+
ARG SENDGRID_API_KEY
72+
ARG GROWSURF_API_KEY
73+
ARG GROWSURF_CAMPAIGN_ID
74+
7075
################################################################################
7176
# Setting of environment variables in the Docker image.
7277

@@ -121,6 +126,9 @@ ENV CONTENTFUL_EDU_CDN_API_KEY=$CONTENTFUL_EDU_CDN_API_KEY
121126
ENV CONTENTFUL_EDU_PREVIEW_API_KEY=$CONTENTFUL_EDU_PREVIEW_API_KEY
122127
ENV RECRUITCRM_API_KEY=$RECRUITCRM_API_KEY
123128
ENV COMMUNITY_APP_URL=$COMMUNITY_APP_URL
129+
ENV SENDGRID_API_KEY=$SENDGRID_API_KEY
130+
ENV GROWSURF_API_KEY=$GROWSURF_API_KEY
131+
ENV GROWSURF_CAMPAIGN_ID=$GROWSURF_CAMPAIGN_ID
124132
ENV GSHEETS_API_KEY=$GSHEETS_API_KEY
125133

126134
################################################################################

__tests__/shared/components/GUIKit/Textarea/__snapshots__/index.jsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ exports[`Default render 1`] = `
1010
id="textAreaInput"
1111
onChange={[Function]}
1212
placeholder=""
13+
readOnly={false}
1314
/>
1415
</div>
1516
`;

build.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ docker build -t $TAG \
4444
--build-arg CONTENTFUL_COMCAST_CDN_API_KEY=$CONTENTFUL_COMCAST_CDN_API_KEY \
4545
--build-arg CONTENTFUL_COMCAST_PREVIEW_API_KEY=$CONTENTFUL_COMCAST_PREVIEW_API_KEY \
4646
--build-arg RECRUITCRM_API_KEY=$RECRUITCRM_API_KEY \
47+
--build-arg SENDGRID_API_KEY=$SENDGRID_API_KEY \
48+
--build-arg GROWSURF_API_KEY=$GROWSURF_API_KEY \
49+
--build-arg GROWSURF_CAMPAIGN_ID=$GROWSURF_CAMPAIGN_ID \
4750
--build-arg GSHEETS_API_KEY=$GSHEETS_API_KEY \
4851
--build-arg COMMUNITY_APP_URL=$COMMUNITY_APP_URL .
4952

config/custom-environment-variables.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ module.exports = {
9797
},
9898

9999
RECRUITCRM_API_KEY: 'RECRUITCRM_API_KEY',
100+
GROWSURF_API_KEY: 'GROWSURF_API_KEY',
101+
SENDGRID_API_KEY: 'SENDGRID_API_KEY',
100102
},
103+
GROWSURF_CAMPAIGN_ID: 'GROWSURF_CAMPAIGN_ID',
101104
AUTH_CONFIG: {
102105
AUTH0_URL: 'TC_M2M_AUTH0_URL',
103106
AUTH0_AUDIENCE: 'TC_M2M_AUDIENCE',

config/default.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,16 @@ module.exports = {
248248
},
249249

250250
RECRUITCRM_API_KEY: '',
251+
GROWSURF_API_KEY: '',
252+
SENDGRID_API_KEY: '',
253+
},
254+
255+
GROWSURF_CAMPAIGN_ID: '',
256+
GROWSURF_COOKIE: '_tc_gigs_ref',
257+
GROWSURF_COOKIE_SETTINGS: {
258+
secure: true,
259+
domain: '',
260+
expires: 7, // days
251261
},
252262

253263
GSHEETS_API_KEY: 'AIzaSyBRdKySN5JNCb2H6ZxJdTTvp3cWU51jiOQ',

src/server/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import mailChimpRouter from './routes/mailchimp';
2828
import mockDocuSignFactory from './__mocks__/docu-sign-mock';
2929
import recruitCRMRouter from './routes/recruitCRM';
3030
import mmLeaderboardRouter from './routes/mmLeaderboard';
31+
import growsurfRouter from './routes/growsurf';
3132
import gSheetsRouter from './routes/gSheet';
3233

3334
/* Dome API for topcoder communities */
@@ -138,6 +139,7 @@ async function onExpressJsSetup(server) {
138139
server.use('/api/mailchimp', mailChimpRouter);
139140
server.use('/api/recruit', recruitCRMRouter);
140141
server.use('/api/mml', mmLeaderboardRouter);
142+
server.use('/api/growsurf', growsurfRouter);
141143
server.use('/api/gsheets', gSheetsRouter);
142144

143145
// serve demo api

src/server/routes/growsurf.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* The routes related to Growsurf integration
3+
*/
4+
5+
import express from 'express';
6+
import GrowsurfService from '../services/growsurf';
7+
8+
const cors = require('cors');
9+
10+
const routes = express.Router();
11+
12+
// Enables CORS on those routes according config above
13+
// ToDo configure CORS for set of our trusted domains
14+
routes.use(cors());
15+
routes.options('*', cors());
16+
17+
routes.get('/participants', (req, res) => new GrowsurfService().getParticipant(req, res).then(res.send.bind(res)));
18+
routes.post('/participants', (req, res) => new GrowsurfService().getOrCreateParticipant(req, res).then(res.send.bind(res)));
19+
20+
export default routes;

src/server/routes/mailchimp.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import express from 'express';
66
import MailchimpService from '../services/mailchimp';
7+
import { sendEmail } from '../services/sendGrid';
78

89
const routes = express.Router();
910
/* Sets Access-Control-Allow-Origin header to avoid CORS error.
@@ -28,4 +29,6 @@ routes.get('/campaign-folders', (req, res) => new MailchimpService().getCampaign
2829

2930
routes.get('/campaigns', (req, res) => new MailchimpService().getCampaigns(req).then(res.send.bind(res)));
3031

32+
routes.post('/email', (req, res) => sendEmail(req, res).then(res.send.bind(res)));
33+
3134
export default routes;

src/server/services/growsurf.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* Server-side functions necessary for effective integration with growsurf
3+
*/
4+
import fetch from 'isomorphic-fetch';
5+
import config from 'config';
6+
7+
/**
8+
* Auxiliary class that handles communication with growsurf
9+
*/
10+
export default class GrowsurfService {
11+
/**
12+
* Creates a new service instance.
13+
* @param {String} baseUrl The base API endpoint.
14+
*/
15+
constructor(baseUrl = 'https://api.growsurf.com/v2') {
16+
this.private = {
17+
baseUrl,
18+
apiKey: config.SECRET.GROWSURF_API_KEY,
19+
authorization: `Bearer ${config.SECRET.GROWSURF_API_KEY}`,
20+
};
21+
}
22+
23+
/**
24+
* Gets get participant by email or id.
25+
* @return {Promise}
26+
* @param {Object} req the request.
27+
* @param {Object} res the response.
28+
*/
29+
async getParticipant(req, res) {
30+
const { participantId } = req.query;
31+
const response = await fetch(`${this.private.baseUrl}/campaign/${config.GROWSURF_CAMPAIGN_ID}/participant/${participantId}`, {
32+
method: 'GET',
33+
headers: {
34+
'Content-Type': 'application/json',
35+
Authorization: this.private.authorization,
36+
},
37+
});
38+
if (response.status >= 300) {
39+
res.status(response.status);
40+
return {
41+
error: await response.json(),
42+
code: response.status,
43+
url: `${this.private.baseUrl}/campaign/${config.GROWSURF_CAMPAIGN_ID}/participant/${participantId}`,
44+
};
45+
}
46+
const data = await response.json();
47+
return data;
48+
}
49+
50+
/**
51+
* Add participant
52+
* @return {Promise}
53+
* @param {Object} body the request payload.
54+
*/
55+
async addParticipant(body) {
56+
const response = await fetch(`${this.private.baseUrl}/campaign/${config.GROWSURF_CAMPAIGN_ID}/participant`, {
57+
method: 'POST',
58+
headers: {
59+
'Content-Type': 'application/json',
60+
Authorization: this.private.authorization,
61+
},
62+
body,
63+
});
64+
if (response.status >= 300) {
65+
return {
66+
error: await response.json(),
67+
code: response.status,
68+
url: `${this.private.baseUrl}/campaign/${config.GROWSURF_CAMPAIGN_ID}/participant`,
69+
body,
70+
private: this.private, // to remove in final release
71+
};
72+
}
73+
const data = await response.json();
74+
return data;
75+
}
76+
77+
/**
78+
* Gets get participant by email or id
79+
* if not exxists create it
80+
* @return {Promise}
81+
* @param {Object} req the request.
82+
* @param {Object} res the response.
83+
*/
84+
async getOrCreateParticipant(req, res) {
85+
const { body } = req;
86+
const result = await this.addParticipant(JSON.stringify({
87+
email: body.email,
88+
firstName: body.firstName,
89+
lastName: body.lastName,
90+
}));
91+
if (result.error) {
92+
res.status(result.code);
93+
}
94+
return result;
95+
}
96+
}

src/server/services/recruitCRM.js

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import fetch from 'isomorphic-fetch';
55
import config from 'config';
66
import qs from 'qs';
77
import _ from 'lodash';
8+
import GrowsurfService from './growsurf';
89

910
const FormData = require('form-data');
1011

@@ -188,7 +189,35 @@ export default class RecruitCRMService {
188189
const fileData = new FormData();
189190
fileData.append('resume', file.buffer, file.originalname);
190191
let candidateSlug;
192+
let referralCookie = req.cookies[config.GROWSURF_COOKIE];
193+
if (referralCookie) referralCookie = JSON.parse(referralCookie);
191194
try {
195+
// referral tracking via growsurf
196+
if (referralCookie && referralCookie.gigId === id) {
197+
const gs = new GrowsurfService();
198+
const growRes = await gs.addParticipant(JSON.stringify({
199+
email: form.email,
200+
referredBy: referralCookie.referralId,
201+
referralStatus: 'CREDIT_PENDING',
202+
firstName: form.first_name,
203+
lastName: form.last_name,
204+
metadata: {
205+
gigId: id,
206+
},
207+
}));
208+
// If everything set in Growsurf
209+
// add referral link to candidate profile in recruitCRM
210+
if (!growRes.error) {
211+
form.custom_fields.push({
212+
field_id: 6, value: `https://app.growsurf.com/dashboard/campaign/${config.GROWSURF_CAMPAIGN_ID}/participant/${growRes.id}`,
213+
});
214+
}
215+
// clear the cookie
216+
res.cookie(config.GROWSURF_COOKIE, '', {
217+
maxAge: 0,
218+
overwrite: true,
219+
});
220+
}
192221
// Check if candidate exsits in the system?
193222
const candidateResponse = await fetch(`${this.private.baseUrl}/v1/candidates/search?email=${form.email}`, {
194223
method: 'GET',
@@ -197,7 +226,7 @@ export default class RecruitCRMService {
197226
Authorization: this.private.authorization,
198227
},
199228
});
200-
if (candidateResponse.status >= 400) {
229+
if (candidateResponse.status >= 300) {
201230
return res.send({
202231
error: true,
203232
status: candidateResponse.status,
@@ -231,7 +260,7 @@ export default class RecruitCRMService {
231260
},
232261
body: JSON.stringify(form),
233262
});
234-
if (workCandidateResponse.status >= 400) {
263+
if (workCandidateResponse.status >= 300) {
235264
return res.send({
236265
error: true,
237266
status: workCandidateResponse.status,
@@ -251,7 +280,7 @@ export default class RecruitCRMService {
251280
},
252281
body: fileData,
253282
});
254-
if (fileCandidateResponse.status >= 400) {
283+
if (fileCandidateResponse.status >= 300) {
255284
return res.send({
256285
error: true,
257286
status: fileCandidateResponse.status,
@@ -272,7 +301,7 @@ export default class RecruitCRMService {
272301
Authorization: this.private.authorization,
273302
},
274303
});
275-
if (applyResponse.status >= 400) {
304+
if (applyResponse.status >= 300) {
276305
const errObj = await applyResponse.json();
277306
if (errObj.errorCode === 422 && errObj.errorMessage === 'Candidate is already assigned to this job') {
278307
return res.send({
@@ -301,7 +330,7 @@ export default class RecruitCRMService {
301330
status_id: '10',
302331
}),
303332
});
304-
if (hireStageResponse.status >= 400) {
333+
if (hireStageResponse.status >= 300) {
305334
return res.send({
306335
error: true,
307336
status: hireStageResponse.status,

src/server/services/sendGrid.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Server-side functions necessary for sending emails via Sendgrid APIs
3+
*/
4+
import config from 'config';
5+
import { logger } from 'topcoder-react-lib';
6+
import fetch from 'isomorphic-fetch';
7+
8+
/**
9+
* Sends emails via the Sendgrid API
10+
* https://sendgrid.com/docs/api-reference/
11+
* @param {Object} req the request
12+
* @param {Object} res the response
13+
*/
14+
export const sendEmail = async (req, res) => {
15+
try {
16+
const msg = req.body;
17+
const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
18+
method: 'POST',
19+
headers: {
20+
'Content-Type': 'application/json',
21+
Authorization: `Bearer ${config.SECRET.SENDGRID_API_KEY}`,
22+
},
23+
body: JSON.stringify(msg),
24+
});
25+
res.status(response.status);
26+
return {};
27+
} catch (error) {
28+
logger.error(error);
29+
const { message, code, response } = error;
30+
res.status(code || 500);
31+
if (error.response) {
32+
const { headers, body } = response;
33+
return {
34+
message, headers, body,
35+
};
36+
}
37+
return { message };
38+
}
39+
};
40+
41+
export default undefined;

0 commit comments

Comments
 (0)