Skip to content

Commit d82d898

Browse files
Merge pull request #5485 from topcoder-platform/develop
Release 2021/04/15 (v1.8.8)
2 parents c96d810 + f21c1de commit d82d898

File tree

21 files changed

+460
-48
lines changed

21 files changed

+460
-48
lines changed

.circleci/config.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,15 +276,13 @@ workflows:
276276
branches:
277277
only:
278278
- develop
279-
- remove-outage-banner
280279
# This is alternate dev env for parallel testing
281280
- "build-test":
282281
context : org-global
283282
filters:
284283
branches:
285-
only:
284+
only:
286285
- free
287-
288286
# This is alternate dev env for parallel testing
289287
- "build-qa":
290288
context : org-global
@@ -306,7 +304,6 @@ workflows:
306304
branches:
307305
only:
308306
- develop
309-
- remove-outage-banner
310307
- "approve-smoke-test-on-staging":
311308
type: approval
312309
requires:

Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ ARG MAILCHIMP_BASE_URL
5252
ARG NODE_CONFIG_ENV
5353
ARG OPEN_EXCHANGE_RATES_KEY
5454
ARG SEGMENT_IO_API_KEY
55+
ARG CHAMELEON_VERIFICATION_SECRET
5556
ARG SERVER_API_KEY
5657

5758
# TC M2M credentials for Community App server
@@ -72,6 +73,9 @@ ARG SENDGRID_API_KEY
7273
ARG GROWSURF_API_KEY
7374
ARG GROWSURF_CAMPAIGN_ID
7475

76+
# Optimizely
77+
ARG OPTIMIZELY_SDK_KEY
78+
7579
################################################################################
7680
# Setting of environment variables in the Docker image.
7781

@@ -108,6 +112,7 @@ ENV MAILCHIMP_BASE_URL=$MAILCHIMP_BASE_URL
108112
ENV NODE_CONFIG_ENV=$NODE_CONFIG_ENV
109113
ENV OPEN_EXCHANGE_RATES_KEY=$OPEN_EXCHANGE_RATES_KEY
110114
ENV SEGMENT_IO_API_KEY=$SEGMENT_IO_API_KEY
115+
ENV CHAMELEON_VERIFICATION_SECRET=$CHAMELEON_VERIFICATION_SECRET
111116
ENV SERVER_API_KEY=$SERVER_API_KEY
112117

113118
# TC M2M credentials for Community App server
@@ -131,6 +136,9 @@ ENV GROWSURF_API_KEY=$GROWSURF_API_KEY
131136
ENV GROWSURF_CAMPAIGN_ID=$GROWSURF_CAMPAIGN_ID
132137
ENV GSHEETS_API_KEY=$GSHEETS_API_KEY
133138

139+
# Optimizely
140+
ENV OPTIMIZELY_SDK_KEY=$OPTIMIZELY_SDK_KEY
141+
134142
################################################################################
135143
# Testing and build of the application inside the container.
136144

build.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ docker build -t $TAG \
3232
--build-arg NODE_CONFIG_ENV=$NODE_CONFIG_ENV \
3333
--build-arg OPEN_EXCHANGE_RATES_KEY=$OPEN_EXCHANGE_RATES_KEY \
3434
--build-arg SEGMENT_IO_API_KEY=$SEGMENT_IO_API_KEY \
35+
--build-arg CHAMELEON_VERIFICATION_SECRET=$CHAMELEON_VERIFICATION_SECRET \
3536
--build-arg SERVER_API_KEY=$SERVER_API_KEY \
3637
--build-arg TC_M2M_CLIENT_ID=$TC_M2M_CLIENT_ID \
3738
--build-arg TC_M2M_CLIENT_SECRET=$TC_M2M_CLIENT_SECRET \
@@ -48,6 +49,7 @@ docker build -t $TAG \
4849
--build-arg GROWSURF_API_KEY=$GROWSURF_API_KEY \
4950
--build-arg GROWSURF_CAMPAIGN_ID=$GROWSURF_CAMPAIGN_ID \
5051
--build-arg GSHEETS_API_KEY=$GSHEETS_API_KEY \
52+
--build-arg OPTIMIZELY_SDK_KEY=$OPTIMIZELY_SDK_KEY \
5153
--build-arg COMMUNITY_APP_URL=$COMMUNITY_APP_URL .
5254

5355
# Copies "node_modules" from the created image, if necessary for caching.

config/custom-environment-variables.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ module.exports = {
9999
RECRUITCRM_API_KEY: 'RECRUITCRM_API_KEY',
100100
GROWSURF_API_KEY: 'GROWSURF_API_KEY',
101101
SENDGRID_API_KEY: 'SENDGRID_API_KEY',
102+
CHAMELEON_VERIFICATION_SECRET: 'CHAMELEON_VERIFICATION_SECRET',
102103
},
103104
GROWSURF_CAMPAIGN_ID: 'GROWSURF_CAMPAIGN_ID',
104105
AUTH_CONFIG: {
@@ -108,4 +109,7 @@ module.exports = {
108109
TOKEN_CACHE_TIME: 'TOKEN_CACHE_TIME',
109110
},
110111
GSHEETS_API_KEY: 'GSHEETS_API_KEY',
112+
OPTIMIZELY: {
113+
SDK_KEY: 'OPTIMIZELY_SDK_KEY',
114+
},
111115
};

config/default.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,4 +426,7 @@ module.exports = {
426426
DEBOUNCE_ON_CHANGE_TIME: 150,
427427
},
428428
ENABLE_RECOMMENDER: true,
429+
OPTIMIZELY: {
430+
SDK_KEY: '7V4CJhurXT3Y3bnzv1hv1',
431+
},
429432
};

docs/secure-identity-verification.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
## Setup
2+
1. Make sure you have a Chameleon Account and segment.com account
3+
2. Integrate Chameleon Account with Segment. https://help.trychameleon.com/en/articles/1161770-installing-using-segment
4+
3. Set Environment secret variable retrieved here https://app.trychameleon.com/settings/integrations/segment. Run the following command
5+
`export CHAMELEON_VERIFICATION_SECRET=<Your Chameleon Secret>`
6+
4. Run community app
7+
8+
## Verification
9+
1. Log in to topcoder-dev account
10+
2. Access http://local.topcoder-dev.com/challenges
11+
3. You will notice in the network tab there will be 2 requests POST to https://api.segment.io/v1/i, one will send it to segment and one will send only to chameleon (with request payload `{ integrations: { All: false, Chameleon: true }}`)
12+
13+
Repeat the proses and log in to different account and make sure the `uid_hash` is different for each different user.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
},
3838
"dependencies": {
3939
"@hapi/joi": "^16.1.4",
40+
"@optimizely/react-sdk": "^2.5.0",
4041
"@topcoder-platform/tc-auth-lib": "topcoder-platform/tc-auth-lib#1.0.4",
4142
"aos": "^2.3.4",
4243
"atob": "^2.1.1",
1.95 KB
Loading
1.52 KB
Loading
1.88 KB
Loading

src/client/index.jsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ const { setErrorsStore } = errors;
2323
* Performs AnalyticsJS identification of the user.
2424
* @param {Object} profile TC user profile.
2525
* @param {Array} roles User roles.
26+
* @param {String} userIdHash Unique Hash per user.
2627
*/
27-
function identify(profile, roles) {
28-
analytics.identify(profile.userId, {
28+
function identify(profile, roles, userIdHash) {
29+
const payload = {
2930
avatar: profile.photoURL,
3031
createdAt: profile.createdAt,
3132
email: profile.email,
@@ -39,7 +40,21 @@ function identify(profile, roles) {
3940
})),
4041
tracks: profile.tracks || [],
4142
username: profile.handle,
42-
});
43+
};
44+
analytics.identify(
45+
profile.userId,
46+
payload,
47+
{
48+
integrations: { Chameleon: false },
49+
},
50+
);
51+
analytics.identify(
52+
profile.userId,
53+
{ uid_hash: userIdHash, ...payload },
54+
{
55+
integrations: { All: false, Chameleon: true },
56+
},
57+
);
4358
}
4459

4560
/**
@@ -74,7 +89,7 @@ function authenticate(store) {
7489
}).then(({ tctV2, tctV3 }) => {
7590
const { auth } = store.getState();
7691
if (auth.profile && !analyticsIdentitySet) {
77-
identify(auth.profile, _.get(auth, 'user.roles'));
92+
identify(auth.profile, _.get(auth, 'user.roles'), auth.userIdHash);
7893
analyticsIdentitySet = true;
7994
}
8095
if (auth.tokenV3 !== (tctV3 || null)) {
@@ -85,7 +100,7 @@ function authenticate(store) {
85100
const userId = profile && profile.userId;
86101
const prevUserId = _.get(store.getState(), 'auth.profile.userId');
87102
if (userId && userId !== prevUserId) {
88-
identify(profile, _.get(auth, user.roles));
103+
identify(profile, _.get(auth, user.roles), auth.userIdHash);
89104
analyticsIdentitySet = true;
90105
}
91106
});

src/server/services/recruitCRM.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const JOB_FIELDS_RESPONSE = [
2626
'salary_type',
2727
'max_annual_salary',
2828
'job_description_text',
29+
'job_status',
2930
];
3031
const CANDIDATE_FIELDS_RESPONSE = [
3132
'id',

src/shared/components/GUIKit/JobListCard/index.jsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,24 @@ import React from 'react';
66
import PT from 'prop-types';
77
import { config, Link } from 'topcoder-react-utils';
88
import { getSalaryType, getCustomField } from 'utils/gigs';
9+
import { withOptimizely } from '@optimizely/react-sdk';
910
import './style.scss';
1011
import IconBlackDuration from 'assets/images/icon-black-duration.svg';
1112
import IconBlackLocation from 'assets/images/icon-black-location.svg';
1213
import IconBlackPayment from 'assets/images/icon-black-payment.svg';
1314
import iconBlackSkills from 'assets/images/icon-skills.png';
15+
import newTag from 'assets/images/gig-work/tag-new.png';
16+
import hotTag from 'assets/images/gig-work/tag-hot.png';
17+
import dolarsTag from 'assets/images/gig-work/tag-dolars.png';
1418

15-
export default function JobListCard({
19+
const TAGS = {
20+
New: newTag,
21+
Hot: hotTag,
22+
$$$: dolarsTag,
23+
};
24+
function JobListCard({
1625
job,
26+
optimizely,
1727
}) {
1828
const duration = getCustomField(job.custom_fields, 'Duration');
1929
let skills = getCustomField(job.custom_fields, 'Technologies Required');
@@ -25,10 +35,17 @@ export default function JobListCard({
2535
skills = skills.join(', ');
2636
}
2737
}
38+
const tag = getCustomField(job.custom_fields, 'Job Tag');
39+
const onHotlistApply = () => {
40+
optimizely.track('View Details Click');
41+
};
2842

2943
return (
3044
<div styleName="container">
31-
<Link to={`${config.GIGS_PAGES_PATH}/${job.slug}`} styleName="gig-name">{job.name}</Link>
45+
{
46+
tag !== 'n/a' && <img src={TAGS[tag]} alt="gig-job-tag" styleName="gig-tag" />
47+
}
48+
<Link to={`${config.GIGS_PAGES_PATH}/${job.slug}`} styleName="gig-name" onClick={onHotlistApply}>{job.name}</Link>
3249
<div styleName="job-infos">
3350
<div styleName="icon-val">
3451
<img src={iconBlackSkills} alt="skills-icon" /> {skills}
@@ -43,7 +60,7 @@ export default function JobListCard({
4360
<IconBlackDuration /> {/^\d+$/.test(duration) ? `${duration} Weeks` : duration}
4461
</div>
4562
<div styleName="row-btn">
46-
<Link styleName="primary-green-md" to={`${config.GIGS_PAGES_PATH}/${job.slug}`}>VIEW DETAILS</Link>
63+
<Link styleName="primary-green-md" to={`${config.GIGS_PAGES_PATH}/${job.slug}`} onClick={onHotlistApply}>VIEW DETAILS</Link>
4764
</div>
4865
</div>
4966
</div>
@@ -56,4 +73,7 @@ JobListCard.defaultProps = {
5673

5774
JobListCard.propTypes = {
5875
job: PT.shape().isRequired,
76+
optimizely: PT.shape().isRequired,
5977
};
78+
79+
export default withOptimizely(JobListCard);

src/shared/components/GUIKit/JobListCard/style.scss

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,23 @@
77
display: flex;
88
flex-direction: column;
99
color: #2a2a2a;
10-
padding: 25px 35px;
10+
padding: 25px 35px 25px 44px;
1111
margin-bottom: 15px;
12+
position: relative;
1213

1314
@include gui-kit-headers;
1415
@include gui-kit-content;
1516
@include roboto-regular;
1617

18+
.gig-tag {
19+
position: absolute;
20+
top: -1px;
21+
left: 10px;
22+
width: 24px;
23+
height: 56px;
24+
border-radius: 0;
25+
}
26+
1727
.gig-name,
1828
.gig-name:visited,
1929
.gig-name:active,
@@ -50,18 +60,38 @@
5060

5161
&:first-child {
5262
width: 250px;
63+
64+
@media (max-width: 1280px) {
65+
width: auto;
66+
margin-right: 20px;
67+
}
5368
}
5469

5570
&:nth-child(2) {
5671
width: 204px;
72+
73+
@media (max-width: 1280px) {
74+
width: auto;
75+
margin-right: 20px;
76+
}
5777
}
5878

5979
&:nth-child(3) {
6080
width: 263px;
81+
82+
@media (max-width: 1280px) {
83+
width: auto;
84+
margin-right: 20px;
85+
}
6186
}
6287

6388
&:nth-child(4) {
6489
width: 255px;
90+
91+
@media (max-width: 1280px) {
92+
width: auto;
93+
margin-right: 20px;
94+
}
6595
}
6696

6797
&:last-child {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export default function GigDetails(props) {
6666
return (
6767
<div styleName="container">
6868
{
69-
job.error || job.enable_job_application_form !== 1 ? (
69+
job.error || job.job_status.id !== 1 || job.enable_job_application_form !== 1 ? (
7070
<div styleName="error">
7171
{ job.error ? <SadFace /> : null }
7272
<h3>{ job.error ? 'Gig does not exist' : 'This Gig has been Fulfilled'}</h3>

src/shared/containers/Gigs/RecruitCRMJobApply.jsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ import PT from 'prop-types';
1010
import React from 'react';
1111
import { connect } from 'react-redux';
1212
import { isValidEmail } from 'utils/tc';
13+
import { withOptimizely } from '@optimizely/react-sdk';
1314
import techSkills from './techSkills';
1415

16+
1517
const countries = require('i18n-iso-countries');
1618
countries.registerLocale(require('i18n-iso-countries/langs/en.json'));
1719

@@ -120,12 +122,13 @@ class RecruitCRMJobApplyContainer extends React.Component {
120122
}
121123

122124
onApplyClick() {
123-
const { applyForJob, job } = this.props;
125+
const { applyForJob, job, optimizely } = this.props;
124126
const { formData } = this.state;
125127
this.validateForm();
126128
this.setState((state) => {
127129
if (_.isEmpty(state.formErrors)) {
128130
applyForJob(job, formData);
131+
optimizely.track('Submit Application Form');
129132
}
130133
});
131134
}
@@ -269,6 +272,7 @@ RecruitCRMJobApplyContainer.propTypes = {
269272
application: PT.shape(),
270273
searchCandidates: PT.func.isRequired,
271274
recruitProfile: PT.shape(),
275+
optimizely: PT.shape().isRequired,
272276
};
273277

274278
function mapStateToProps(state, ownProps) {
@@ -312,4 +316,4 @@ function mapDispatchToActions(dispatch) {
312316
export default connect(
313317
mapStateToProps,
314318
mapDispatchToActions,
315-
)(RecruitCRMJobApplyContainer);
319+
)(withOptimizely(RecruitCRMJobApplyContainer));

0 commit comments

Comments
 (0)