Skip to content

Commit d1474d3

Browse files
author
vikasrohit
authored
Merge pull request #630 from topcoder-platform/develop
Prod Release - 3.3.1
2 parents 0315bcc + 15ca258 commit d1474d3

19 files changed

+588
-67
lines changed

config/custom-environment-variables.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,11 @@
7070
"EMBED_REPORTS_MAPPING": "EMBED_REPORTS_MAPPING",
7171
"ALLOWED_USERS": "REPORTS_ALLOWED_USERS"
7272
},
73-
"DEFAULT_M2M_USERID": "DEFAULT_M2M_USERID"
73+
"DEFAULT_M2M_USERID": "DEFAULT_M2M_USERID",
74+
"salesforce": {
75+
"CLIENT_AUDIENCE": "SALESFORCE_AUDIENCE",
76+
"CLIENT_KEY": "SALESFORCE_CLIENT_KEY",
77+
"SUBJECT": "SALESFORCE_SUBJECT",
78+
"CLIENT_ID": "SALESFORCE_CLIENT_ID"
79+
}
7480
}

config/default.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,11 @@
7676
"ALLOWED_USERS": "[]"
7777
},
7878
"DEFAULT_M2M_USERID": -101,
79-
"taasJobApiUrl": "https://api.topcoder.com/v5/jobs"
79+
"taasJobApiUrl": "https://api.topcoder.com/v5/jobs",
80+
"salesforce": {
81+
"CLIENT_KEY": "",
82+
"CLIENT_AUDIENCE": "",
83+
"SUBJECT": "",
84+
"CLIENT_ID": ""
85+
}
8086
}

docs/Project API.postman_collection.json

+49
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,55 @@
925925
},
926926
"response": []
927927
},
928+
{
929+
"name": "Create project member with no payload and return member details",
930+
"event": [
931+
{
932+
"listen": "test",
933+
"script": {
934+
"exec": [
935+
"pm.test(\"Status code is 201\", function () {",
936+
" pm.response.to.have.status(201);",
937+
" pm.environment.set(\"memberId\", pm.response.json().id);",
938+
"});"
939+
],
940+
"type": "text/javascript"
941+
}
942+
}
943+
],
944+
"request": {
945+
"method": "POST",
946+
"header": [
947+
{
948+
"key": "Authorization",
949+
"value": "Bearer {{jwt-token}}"
950+
},
951+
{
952+
"key": "Content-Type",
953+
"value": "application/json"
954+
}
955+
],
956+
"url": {
957+
"raw": "{{api-url}}/projects/{{projectId}}/members?fields=id,userId,role,isPrimary,deletedAt,createdAt,updatedAt,deletedBy,createdBy,updatedBy,handle,photoURL,workingHourStart,workingHourEnd,timeZone,email",
958+
"host": [
959+
"{{api-url}}"
960+
],
961+
"path": [
962+
"projects",
963+
"{{projectId}}",
964+
"members"
965+
],
966+
"query": [
967+
{
968+
"key": "fields",
969+
"value": "id,userId,role,isPrimary,deletedAt,createdAt,updatedAt,deletedBy,createdBy,updatedBy,handle,photoURL,workingHourStart,workingHourEnd,timeZone,email"
970+
}
971+
]
972+
},
973+
"description": "If the request payload is valid, than project customer should be added. This should sync with the direct project is project is associated with direct project."
974+
},
975+
"response": []
976+
},
928977
{
929978
"name": "Update project member",
930979
"request": {

docs/permissions.html

+37
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,43 @@ <h2 class="anchor-container">
375375
</div>
376376
</div>
377377
</div>
378+
<div class="row">
379+
<div class="col pt-5 pb-2">
380+
<h2 class="anchor-container">
381+
<a href="#section-project-billing accounts" name="section-project-billing accounts" class="anchor"></a>Project Billing Accounts
382+
</h2>
383+
</div>
384+
</div>
385+
<div class="row border-top">
386+
<div class="col py-2">
387+
<div class="permission-title anchor-container">
388+
<a href="#READ_AVL_PROJECT_BILLING_ACCOUNTS" name="READ_AVL_PROJECT_BILLING_ACCOUNTS" class="anchor"></a>Read Available Project Billing Accounts
389+
</div>
390+
<div class="permission-variable"><small><code>READ_AVL_PROJECT_BILLING_ACCOUNTS</code></small></div>
391+
<div class="text-black-50 small-text">Who can view the Billing Accounts available for the project</div>
392+
</div>
393+
<div class="col-9 py-2">
394+
<div>
395+
<span class="badge badge-primary" title="Allowed Project Role">manager</span>
396+
<span class="badge badge-primary" title="Allowed Project Role">account_manager</span>
397+
<span class="badge badge-primary" title="Allowed Project Role">program_manager</span>
398+
<span class="badge badge-primary" title="Allowed Project Role">account_executive</span>
399+
<span class="badge badge-primary" title="Allowed Project Role">solution_architect</span>
400+
<span class="badge badge-primary" title="Allowed Project Role">project_manager</span>
401+
<span class="badge badge-primary" title="Allowed Project Role">copilot</span>
402+
</div>
403+
404+
<div>
405+
<span class="badge badge-success" title="Allowed Topcoder Role">Connect Admin</span>
406+
<span class="badge badge-success" title="Allowed Topcoder Role">administrator</span>
407+
</div>
408+
409+
<div>
410+
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
411+
<span class="badge badge-dark" title="Allowed Topcoder Role"></span>
412+
</div>
413+
</div>
414+
</div>
378415
<div class="row">
379416
<div class="col pt-5 pb-2">
380417
<h2 class="anchor-container">

local/postgres-db/Dockerfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
FROM postgres:9.5
2-
COPY create-multiple-postgresql-databases.sh /docker-entrypoint-initdb.d/
1+
FROM postgres:12.3
2+
COPY create-multiple-postgresql-databases.sh /docker-entrypoint-initdb.d/

src/constants.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,8 @@ export const M2M_SCOPES = {
274274
ALL: 'all:projects',
275275
READ: 'read:projects',
276276
WRITE: 'write:projects',
277-
WRITE_BILLING_ACCOUNTS: 'write:projects-billing-accounts',
277+
READ_USER_BILLING_ACCOUNTS: 'read:user-billing-accounts',
278+
WRITE_PROJECTS_BILLING_ACCOUNTS: 'write:projects-billing-accounts',
278279
},
279280
PROJECT_MEMBERS: {
280281
ALL: 'all:project-members',

src/events/busApi.js

+9
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import { createEvent } from '../services/busApi';
1616
import models from '../models';
1717
import util from '../util';
18+
import createTaasJobsFromProject from '../events/projects/postTaasJobs';
1819

1920
/**
2021
* Map of project status and event name sent to bus api
@@ -57,6 +58,14 @@ module.exports = (app, logger) => {
5758
app.on(EVENT.ROUTING_KEY.PROJECT_DRAFT_CREATED, ({ req, project }) => {
5859
logger.debug('receive PROJECT_DRAFT_CREATED event');
5960

61+
// create taas jobs from project of type `talent-as-a-service`
62+
if (project.type === 'talent-as-a-service') {
63+
createTaasJobsFromProject(req, project, logger)
64+
.catch((error) => {
65+
logger.error(`Error while creating TaaS jobs: ${error}`);
66+
});
67+
}
68+
6069
// send event to bus api
6170
createEvent(BUS_API_EVENT.PROJECT_CREATED, _.assign(project, {
6271
refCode: _.get(project, 'details.utm.code'),

src/events/projects/index.js

-58
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import _ from 'lodash';
55
import Joi from 'joi';
66
import Promise from 'bluebird';
77
import config from 'config';
8-
import axios from 'axios';
98
import util from '../../util';
109
import models from '../../models';
1110
import { createPhaseTopic } from '../projectPhases';
@@ -15,27 +14,6 @@ const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
1514
const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
1615
const eClient = util.getElasticSearchClient();
1716

18-
/**
19-
* creates taas job
20-
* @param {Object} data the job data
21-
* @return {Object} the job created
22-
*/
23-
const createTaasJob = async (data) => {
24-
const token = await util.getM2MToken();
25-
const headers = {
26-
'Content-Type': 'application/json',
27-
Authorization: `Bearer ${token}`,
28-
};
29-
const res = await axios
30-
.post(config.taasJobApiUrl, data, { headers })
31-
.catch((err) => {
32-
const error = new Error();
33-
error.message = _.get(err, 'response.data.message', error.message);
34-
throw error;
35-
});
36-
return res.data;
37-
};
38-
3917
/**
4018
* Payload for deprecated BUS events like `connect.notification.project.updated`.
4119
*/
@@ -186,42 +164,6 @@ async function projectCreatedKafkaHandler(app, topic, payload) {
186164
await Promise.all(topicPromises);
187165
app.logger.debug('Topics for phases are successfully created.');
188166
}
189-
try {
190-
if (project.type === 'talent-as-a-service') {
191-
const jobs = _.get(project, 'details.taasDefinition.taasJobs');
192-
if (!jobs || !jobs.length) {
193-
app.logger.debug(`no jobs found in the project id: ${project.id}`);
194-
return;
195-
}
196-
app.logger.debug(`${jobs.length} jobs found in the project id: ${project.id}`);
197-
await Promise.all(
198-
_.map(
199-
jobs,
200-
(job) => {
201-
// make sure that skills would be unique in the list and only include ones with 'skillId' (actually they all suppose to be with skillId)
202-
const skills = _.chain(job.skills).map('skillId').uniq().compact()
203-
.value();
204-
return createTaasJob({
205-
projectId: project.id,
206-
title: job.title,
207-
description: job.description,
208-
skills,
209-
numPositions: Number(job.people),
210-
resourceType: _.get(job, 'role.value', ''),
211-
rateType: 'weekly', // hardcode for now
212-
workload: _.get(job, 'workLoad.title', '').toLowerCase(),
213-
}).then((createdJob) => {
214-
app.logger.debug(`jobId: ${createdJob.id} job created with title "${createdJob.title}"`);
215-
}).catch((err) => {
216-
app.logger.error(`Unable to create job with title "${job.title}": ${err.message}`);
217-
});
218-
},
219-
),
220-
);
221-
}
222-
} catch (error) {
223-
app.logger.error(`Error while creating TaaS jobs: ${error}`);
224-
}
225167
}
226168

227169
module.exports = {

src/events/projects/postTaasJobs.js

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* Event handler for the creation of `talent-as-a-service` projects.
3+
*/
4+
5+
import _ from 'lodash';
6+
import config from 'config';
7+
import axios from 'axios';
8+
9+
/**
10+
* Create taas job.
11+
*
12+
* @param {String} authHeader the authorization header
13+
* @param {Object} data the job data
14+
* @return {Object} the job created
15+
*/
16+
async function createTaasJob(authHeader, data) {
17+
const headers = {
18+
'Content-Type': 'application/json',
19+
Authorization: authHeader,
20+
};
21+
const res = await axios
22+
.post(config.taasJobApiUrl, data, { headers })
23+
.catch((err) => {
24+
const error = new Error();
25+
error.message = _.get(err, 'response.data.message', error.message);
26+
throw error;
27+
});
28+
return res.data;
29+
}
30+
31+
/**
32+
* Create taas jobs from project of type `talent-as-a-service` using the token from current user.
33+
*
34+
* @param {Object} req the request object
35+
* @param {Object} project the project data
36+
* @param {Object} logger the logger object
37+
* @return {Object} the taas jobs created
38+
*/
39+
async function createTaasJobsFromProject(req, project, logger) {
40+
const jobs = _.get(project, 'details.taasDefinition.taasJobs');
41+
if (!jobs || !jobs.length) {
42+
logger.debug(`no jobs found in the project id: ${project.id}`);
43+
return;
44+
}
45+
logger.debug(`${jobs.length} jobs found in the project id: ${project.id}`);
46+
await Promise.all(
47+
_.map(
48+
jobs,
49+
(job) => {
50+
// make sure that skills would be unique in the list and only include ones with 'skillId' (actually they all suppose to be with skillId)
51+
const skills = _.chain(job.skills).map('skillId').uniq().compact()
52+
.value();
53+
return createTaasJob(req.headers.authorization, {
54+
projectId: project.id,
55+
title: job.title,
56+
description: job.description,
57+
duration: Number(job.duration),
58+
skills,
59+
numPositions: Number(job.people),
60+
resourceType: _.get(job, 'role.value', ''),
61+
rateType: 'weekly', // hardcode for now
62+
workload: _.get(job, 'workLoad.title', '').toLowerCase(),
63+
}).then((createdJob) => {
64+
logger.debug(`jobId: ${createdJob.id} job created with title "${createdJob.title}"`);
65+
}).catch((err) => {
66+
logger.error(`Unable to create job with title "${job.title}": ${err.message}`);
67+
});
68+
},
69+
),
70+
);
71+
}
72+
73+
module.exports = createTaasJobsFromProject;

src/permissions/constants.js

+28-3
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,20 @@ const SCOPES_PROJECTS_WRITE = [
9191
M2M_SCOPES.PROJECTS.WRITE,
9292
];
9393

94+
/**
95+
* M2M scopes to "read" available Billing Accounts for the project
96+
*/
97+
const SCOPES_PROJECTS_READ_AVL_BILLING_ACCOUNTS = [
98+
M2M_SCOPES.CONNECT_PROJECT_ADMIN,
99+
M2M_SCOPES.READ_USER_BILLING_ACCOUNTS,
100+
];
101+
94102
/**
95103
* M2M scopes to "write" billingAccountId property
96104
*/
97-
const SCOPES_PROJECTS_WRITE_BILLING_ACCOUNTS = [
105+
const SCOPES_PROJECTS_WRITE_PROJECTS_BILLING_ACCOUNTS = [
98106
M2M_SCOPES.CONNECT_PROJECT_ADMIN,
99-
M2M_SCOPES.PROJECTS.WRITE_BILLING_ACCOUNTS,
107+
M2M_SCOPES.PROJECTS.WRITE_PROJECTS_BILLING_ACCOUNTS,
100108
];
101109

102110
/**
@@ -231,7 +239,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
231239
USER_ROLE.MANAGER,
232240
USER_ROLE.TOPCODER_ADMIN,
233241
],
234-
scopes: SCOPES_PROJECTS_WRITE_BILLING_ACCOUNTS,
242+
scopes: SCOPES_PROJECTS_WRITE_PROJECTS_BILLING_ACCOUNTS,
235243
},
236244

237245
DELETE_PROJECT: {
@@ -252,6 +260,23 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
252260
scopes: SCOPES_PROJECTS_WRITE,
253261
},
254262

263+
/*
264+
* Project Invite
265+
*/
266+
READ_AVL_PROJECT_BILLING_ACCOUNTS: {
267+
meta: {
268+
title: 'Read Available Project Billing Accounts',
269+
group: 'Project Billing Accounts',
270+
description: 'Who can view the Billing Accounts available for the project',
271+
},
272+
projectRoles: [
273+
...PROJECT_ROLES_MANAGEMENT,
274+
PROJECT_MEMBER_ROLE.COPILOT,
275+
],
276+
topcoderRoles: TOPCODER_ROLES_ADMINS,
277+
scopes: SCOPES_PROJECTS_READ_AVL_BILLING_ACCOUNTS,
278+
},
279+
255280
/*
256281
* Project Member
257282
*/

src/permissions/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ module.exports = () => {
2020
Authorizer.setPolicy('project.edit', generalPermission(PERMISSION.UPDATE_PROJECT));
2121
Authorizer.setPolicy('project.delete', generalPermission(PERMISSION.DELETE_PROJECT));
2222

23+
Authorizer.setPolicy('projectBillingAccounts.view', generalPermission([
24+
PERMISSION.READ_AVL_PROJECT_BILLING_ACCOUNTS,
25+
]));
26+
2327
Authorizer.setPolicy('projectMember.create', generalPermission([
2428
PERMISSION.CREATE_PROJECT_MEMBER_OWN,
2529
PERMISSION.CREATE_PROJECT_MEMBER_NOT_OWN,

0 commit comments

Comments
 (0)