Skip to content

Prod Release - 3.3.1 #630

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Feb 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion config/custom-environment-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,11 @@
"EMBED_REPORTS_MAPPING": "EMBED_REPORTS_MAPPING",
"ALLOWED_USERS": "REPORTS_ALLOWED_USERS"
},
"DEFAULT_M2M_USERID": "DEFAULT_M2M_USERID"
"DEFAULT_M2M_USERID": "DEFAULT_M2M_USERID",
"salesforce": {
"CLIENT_AUDIENCE": "SALESFORCE_AUDIENCE",
"CLIENT_KEY": "SALESFORCE_CLIENT_KEY",
"SUBJECT": "SALESFORCE_SUBJECT",
"CLIENT_ID": "SALESFORCE_CLIENT_ID"
}
}
8 changes: 7 additions & 1 deletion config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,11 @@
"ALLOWED_USERS": "[]"
},
"DEFAULT_M2M_USERID": -101,
"taasJobApiUrl": "https://api.topcoder.com/v5/jobs"
"taasJobApiUrl": "https://api.topcoder.com/v5/jobs",
"salesforce": {
"CLIENT_KEY": "",
"CLIENT_AUDIENCE": "",
"SUBJECT": "",
"CLIENT_ID": ""
}
}
49 changes: 49 additions & 0 deletions docs/Project API.postman_collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,55 @@
},
"response": []
},
{
"name": "Create project member with no payload and return member details",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Status code is 201\", function () {",
" pm.response.to.have.status(201);",
" pm.environment.set(\"memberId\", pm.response.json().id);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Authorization",
"value": "Bearer {{jwt-token}}"
},
{
"key": "Content-Type",
"value": "application/json"
}
],
"url": {
"raw": "{{api-url}}/projects/{{projectId}}/members?fields=id,userId,role,isPrimary,deletedAt,createdAt,updatedAt,deletedBy,createdBy,updatedBy,handle,photoURL,workingHourStart,workingHourEnd,timeZone,email",
"host": [
"{{api-url}}"
],
"path": [
"projects",
"{{projectId}}",
"members"
],
"query": [
{
"key": "fields",
"value": "id,userId,role,isPrimary,deletedAt,createdAt,updatedAt,deletedBy,createdBy,updatedBy,handle,photoURL,workingHourStart,workingHourEnd,timeZone,email"
}
]
},
"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."
},
"response": []
},
{
"name": "Update project member",
"request": {
Expand Down
37 changes: 37 additions & 0 deletions docs/permissions.html
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,43 @@ <h2 class="anchor-container">
</div>
</div>
</div>
<div class="row">
<div class="col pt-5 pb-2">
<h2 class="anchor-container">
<a href="#section-project-billing accounts" name="section-project-billing accounts" class="anchor"></a>Project Billing Accounts
</h2>
</div>
</div>
<div class="row border-top">
<div class="col py-2">
<div class="permission-title anchor-container">
<a href="#READ_AVL_PROJECT_BILLING_ACCOUNTS" name="READ_AVL_PROJECT_BILLING_ACCOUNTS" class="anchor"></a>Read Available Project Billing Accounts
</div>
<div class="permission-variable"><small><code>READ_AVL_PROJECT_BILLING_ACCOUNTS</code></small></div>
<div class="text-black-50 small-text">Who can view the Billing Accounts available for the project</div>
</div>
<div class="col-9 py-2">
<div>
<span class="badge badge-primary" title="Allowed Project Role">manager</span>
<span class="badge badge-primary" title="Allowed Project Role">account_manager</span>
<span class="badge badge-primary" title="Allowed Project Role">program_manager</span>
<span class="badge badge-primary" title="Allowed Project Role">account_executive</span>
<span class="badge badge-primary" title="Allowed Project Role">solution_architect</span>
<span class="badge badge-primary" title="Allowed Project Role">project_manager</span>
<span class="badge badge-primary" title="Allowed Project Role">copilot</span>
</div>

<div>
<span class="badge badge-success" title="Allowed Topcoder Role">Connect Admin</span>
<span class="badge badge-success" title="Allowed Topcoder Role">administrator</span>
</div>

<div>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
<span class="badge badge-dark" title="Allowed Topcoder Role"></span>
</div>
</div>
</div>
<div class="row">
<div class="col pt-5 pb-2">
<h2 class="anchor-container">
Expand Down
4 changes: 2 additions & 2 deletions local/postgres-db/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
FROM postgres:9.5
COPY create-multiple-postgresql-databases.sh /docker-entrypoint-initdb.d/
FROM postgres:12.3
COPY create-multiple-postgresql-databases.sh /docker-entrypoint-initdb.d/
3 changes: 2 additions & 1 deletion src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,8 @@ export const M2M_SCOPES = {
ALL: 'all:projects',
READ: 'read:projects',
WRITE: 'write:projects',
WRITE_BILLING_ACCOUNTS: 'write:projects-billing-accounts',
READ_USER_BILLING_ACCOUNTS: 'read:user-billing-accounts',
WRITE_PROJECTS_BILLING_ACCOUNTS: 'write:projects-billing-accounts',
},
PROJECT_MEMBERS: {
ALL: 'all:project-members',
Expand Down
9 changes: 9 additions & 0 deletions src/events/busApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { createEvent } from '../services/busApi';
import models from '../models';
import util from '../util';
import createTaasJobsFromProject from '../events/projects/postTaasJobs';

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

// create taas jobs from project of type `talent-as-a-service`
if (project.type === 'talent-as-a-service') {
createTaasJobsFromProject(req, project, logger)
.catch((error) => {
logger.error(`Error while creating TaaS jobs: ${error}`);
});
}

// send event to bus api
createEvent(BUS_API_EVENT.PROJECT_CREATED, _.assign(project, {
refCode: _.get(project, 'details.utm.code'),
Expand Down
58 changes: 0 additions & 58 deletions src/events/projects/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import _ from 'lodash';
import Joi from 'joi';
import Promise from 'bluebird';
import config from 'config';
import axios from 'axios';
import util from '../../util';
import models from '../../models';
import { createPhaseTopic } from '../projectPhases';
Expand All @@ -15,27 +14,6 @@ const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
const eClient = util.getElasticSearchClient();

/**
* creates taas job
* @param {Object} data the job data
* @return {Object} the job created
*/
const createTaasJob = async (data) => {
const token = await util.getM2MToken();
const headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
};
const res = await axios
.post(config.taasJobApiUrl, data, { headers })
.catch((err) => {
const error = new Error();
error.message = _.get(err, 'response.data.message', error.message);
throw error;
});
return res.data;
};

/**
* Payload for deprecated BUS events like `connect.notification.project.updated`.
*/
Expand Down Expand Up @@ -186,42 +164,6 @@ async function projectCreatedKafkaHandler(app, topic, payload) {
await Promise.all(topicPromises);
app.logger.debug('Topics for phases are successfully created.');
}
try {
if (project.type === 'talent-as-a-service') {
const jobs = _.get(project, 'details.taasDefinition.taasJobs');
if (!jobs || !jobs.length) {
app.logger.debug(`no jobs found in the project id: ${project.id}`);
return;
}
app.logger.debug(`${jobs.length} jobs found in the project id: ${project.id}`);
await Promise.all(
_.map(
jobs,
(job) => {
// make sure that skills would be unique in the list and only include ones with 'skillId' (actually they all suppose to be with skillId)
const skills = _.chain(job.skills).map('skillId').uniq().compact()
.value();
return createTaasJob({
projectId: project.id,
title: job.title,
description: job.description,
skills,
numPositions: Number(job.people),
resourceType: _.get(job, 'role.value', ''),
rateType: 'weekly', // hardcode for now
workload: _.get(job, 'workLoad.title', '').toLowerCase(),
}).then((createdJob) => {
app.logger.debug(`jobId: ${createdJob.id} job created with title "${createdJob.title}"`);
}).catch((err) => {
app.logger.error(`Unable to create job with title "${job.title}": ${err.message}`);
});
},
),
);
}
} catch (error) {
app.logger.error(`Error while creating TaaS jobs: ${error}`);
}
}

module.exports = {
Expand Down
73 changes: 73 additions & 0 deletions src/events/projects/postTaasJobs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Event handler for the creation of `talent-as-a-service` projects.
*/

import _ from 'lodash';
import config from 'config';
import axios from 'axios';

/**
* Create taas job.
*
* @param {String} authHeader the authorization header
* @param {Object} data the job data
* @return {Object} the job created
*/
async function createTaasJob(authHeader, data) {
const headers = {
'Content-Type': 'application/json',
Authorization: authHeader,
};
const res = await axios
.post(config.taasJobApiUrl, data, { headers })
.catch((err) => {
const error = new Error();
error.message = _.get(err, 'response.data.message', error.message);
throw error;
});
return res.data;
}

/**
* Create taas jobs from project of type `talent-as-a-service` using the token from current user.
*
* @param {Object} req the request object
* @param {Object} project the project data
* @param {Object} logger the logger object
* @return {Object} the taas jobs created
*/
async function createTaasJobsFromProject(req, project, logger) {
const jobs = _.get(project, 'details.taasDefinition.taasJobs');
if (!jobs || !jobs.length) {
logger.debug(`no jobs found in the project id: ${project.id}`);
return;
}
logger.debug(`${jobs.length} jobs found in the project id: ${project.id}`);
await Promise.all(
_.map(
jobs,
(job) => {
// make sure that skills would be unique in the list and only include ones with 'skillId' (actually they all suppose to be with skillId)
const skills = _.chain(job.skills).map('skillId').uniq().compact()
.value();
return createTaasJob(req.headers.authorization, {
projectId: project.id,
title: job.title,
description: job.description,
duration: Number(job.duration),
skills,
numPositions: Number(job.people),
resourceType: _.get(job, 'role.value', ''),
rateType: 'weekly', // hardcode for now
workload: _.get(job, 'workLoad.title', '').toLowerCase(),
}).then((createdJob) => {
logger.debug(`jobId: ${createdJob.id} job created with title "${createdJob.title}"`);
}).catch((err) => {
logger.error(`Unable to create job with title "${job.title}": ${err.message}`);
});
},
),
);
}

module.exports = createTaasJobsFromProject;
31 changes: 28 additions & 3 deletions src/permissions/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,20 @@ const SCOPES_PROJECTS_WRITE = [
M2M_SCOPES.PROJECTS.WRITE,
];

/**
* M2M scopes to "read" available Billing Accounts for the project
*/
const SCOPES_PROJECTS_READ_AVL_BILLING_ACCOUNTS = [
M2M_SCOPES.CONNECT_PROJECT_ADMIN,
M2M_SCOPES.READ_USER_BILLING_ACCOUNTS,
];

/**
* M2M scopes to "write" billingAccountId property
*/
const SCOPES_PROJECTS_WRITE_BILLING_ACCOUNTS = [
const SCOPES_PROJECTS_WRITE_PROJECTS_BILLING_ACCOUNTS = [
M2M_SCOPES.CONNECT_PROJECT_ADMIN,
M2M_SCOPES.PROJECTS.WRITE_BILLING_ACCOUNTS,
M2M_SCOPES.PROJECTS.WRITE_PROJECTS_BILLING_ACCOUNTS,
];

/**
Expand Down Expand Up @@ -231,7 +239,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
USER_ROLE.MANAGER,
USER_ROLE.TOPCODER_ADMIN,
],
scopes: SCOPES_PROJECTS_WRITE_BILLING_ACCOUNTS,
scopes: SCOPES_PROJECTS_WRITE_PROJECTS_BILLING_ACCOUNTS,
},

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

/*
* Project Invite
*/
READ_AVL_PROJECT_BILLING_ACCOUNTS: {
meta: {
title: 'Read Available Project Billing Accounts',
group: 'Project Billing Accounts',
description: 'Who can view the Billing Accounts available for the project',
},
projectRoles: [
...PROJECT_ROLES_MANAGEMENT,
PROJECT_MEMBER_ROLE.COPILOT,
],
topcoderRoles: TOPCODER_ROLES_ADMINS,
scopes: SCOPES_PROJECTS_READ_AVL_BILLING_ACCOUNTS,
},

/*
* Project Member
*/
Expand Down
4 changes: 4 additions & 0 deletions src/permissions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ module.exports = () => {
Authorizer.setPolicy('project.edit', generalPermission(PERMISSION.UPDATE_PROJECT));
Authorizer.setPolicy('project.delete', generalPermission(PERMISSION.DELETE_PROJECT));

Authorizer.setPolicy('projectBillingAccounts.view', generalPermission([
PERMISSION.READ_AVL_PROJECT_BILLING_ACCOUNTS,
]));

Authorizer.setPolicy('projectMember.create', generalPermission([
PERMISSION.CREATE_PROJECT_MEMBER_OWN,
PERMISSION.CREATE_PROJECT_MEMBER_NOT_OWN,
Expand Down
Loading