Skip to content

Commit 64d0c41

Browse files
authored
Merge pull request #662 from eisbilir/feature/new-milestone-concept
allow update phase members with create-update phase
2 parents 1a2e995 + 9237cb8 commit 64d0c41

File tree

13 files changed

+537
-83
lines changed

13 files changed

+537
-83
lines changed

docs/Project API.postman_collection.json

Lines changed: 282 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"info": {
3-
"_postman_id": "3eba12ae-a066-4d5a-bdd5-3121377e476b",
3+
"_postman_id": "4c51e04b-42d3-4c9f-bf08-af02f51f7756",
44
"name": "Project API",
55
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
66
},
@@ -4577,6 +4577,208 @@
45774577
{
45784578
"name": "Project Phase",
45794579
"item": [
4580+
{
4581+
"name": "Before Start",
4582+
"item": [
4583+
{
4584+
"name": "Create project type",
4585+
"event": [
4586+
{
4587+
"listen": "test",
4588+
"script": {
4589+
"exec": [
4590+
"pm.test(\"Status code is 201\", function () {",
4591+
" pm.response.to.have.status(201);",
4592+
" if(pm.response.status === \"Created\") {",
4593+
" const response = pm.response.json()",
4594+
" pm.environment.set(\"projectTypeId\", response.key);",
4595+
" }",
4596+
"});"
4597+
],
4598+
"type": "text/javascript"
4599+
}
4600+
}
4601+
],
4602+
"request": {
4603+
"method": "POST",
4604+
"header": [
4605+
{
4606+
"key": "Content-Type",
4607+
"value": "application/json"
4608+
},
4609+
{
4610+
"key": "Authorization",
4611+
"value": "Bearer {{jwt-token}}"
4612+
}
4613+
],
4614+
"body": {
4615+
"mode": "raw",
4616+
"raw": "{\r\n \"key\": \"new key\",\r\n \"displayName\": \"new displayName\",\r\n \"icon\": \"http://example.com/icon4.ico\",\r\n \t\"question\": \"question 4\",\r\n \t\"info\": \"info 4\",\r\n \t\"aliases\": [\"key-41\", \"key_42\"],\r\n \t\"metadata\": {}\r\n }"
4617+
},
4618+
"url": {
4619+
"raw": "{{api-url}}/projects/metadata/projectTypes",
4620+
"host": [
4621+
"{{api-url}}"
4622+
],
4623+
"path": [
4624+
"projects",
4625+
"metadata",
4626+
"projectTypes"
4627+
]
4628+
}
4629+
},
4630+
"response": []
4631+
},
4632+
{
4633+
"name": "Create project",
4634+
"event": [
4635+
{
4636+
"listen": "test",
4637+
"script": {
4638+
"exec": [
4639+
"pm.test(\"Status code is 201\", function () {",
4640+
" pm.response.to.have.status(201);",
4641+
" if(pm.response.status === \"Created\") {",
4642+
" const response = pm.response.json()",
4643+
" pm.environment.set(\"projectId\", response.id);",
4644+
" }",
4645+
"});"
4646+
],
4647+
"type": "text/javascript"
4648+
}
4649+
}
4650+
],
4651+
"request": {
4652+
"method": "POST",
4653+
"header": [
4654+
{
4655+
"key": "Authorization",
4656+
"value": "Bearer {{jwt-token}}"
4657+
},
4658+
{
4659+
"key": "Content-Type",
4660+
"value": "application/json"
4661+
}
4662+
],
4663+
"body": {
4664+
"mode": "raw",
4665+
"raw": "{\n\t\"name\": \"test project\",\n\t\"description\": \"Hello I am a test project\",\n\t\"type\": \"{{projectTypeId}}\"\n}"
4666+
},
4667+
"url": {
4668+
"raw": "{{api-url}}/projects",
4669+
"host": [
4670+
"{{api-url}}"
4671+
],
4672+
"path": [
4673+
"projects"
4674+
]
4675+
},
4676+
"description": "Valid request body. Project should be created successfully."
4677+
},
4678+
"response": []
4679+
},
4680+
{
4681+
"name": "Create project member - 1",
4682+
"event": [
4683+
{
4684+
"listen": "test",
4685+
"script": {
4686+
"exec": [
4687+
"pm.test(\"Status code is 201\", function () {",
4688+
" pm.response.to.have.status(201);",
4689+
" if(pm.response.status === \"Created\") {",
4690+
" const response = pm.response.json()",
4691+
" pm.environment.set(\"phaseMemberId-1\", response.userId);",
4692+
" }",
4693+
"});"
4694+
],
4695+
"type": "text/javascript"
4696+
}
4697+
}
4698+
],
4699+
"request": {
4700+
"method": "POST",
4701+
"header": [
4702+
{
4703+
"key": "Authorization",
4704+
"value": "Bearer {{jwt-token}}"
4705+
},
4706+
{
4707+
"key": "Content-Type",
4708+
"value": "application/json"
4709+
}
4710+
],
4711+
"body": {
4712+
"mode": "raw",
4713+
"raw": "{\n \"userId\": \"40158994\",\n \"role\": \"copilot\"\n}"
4714+
},
4715+
"url": {
4716+
"raw": "{{api-url}}/projects/{{projectId}}/members",
4717+
"host": [
4718+
"{{api-url}}"
4719+
],
4720+
"path": [
4721+
"projects",
4722+
"{{projectId}}",
4723+
"members"
4724+
]
4725+
},
4726+
"description": "If the request payload is valid, than project member should be created."
4727+
},
4728+
"response": []
4729+
},
4730+
{
4731+
"name": "Create project member - 2",
4732+
"event": [
4733+
{
4734+
"listen": "test",
4735+
"script": {
4736+
"exec": [
4737+
"pm.test(\"Status code is 201\", function () {",
4738+
" pm.response.to.have.status(201);",
4739+
" if(pm.response.status === \"Created\") {",
4740+
" const response = pm.response.json()",
4741+
" pm.environment.set(\"phaseMemberId-2\", response.userId);",
4742+
" }",
4743+
"});"
4744+
],
4745+
"type": "text/javascript"
4746+
}
4747+
}
4748+
],
4749+
"request": {
4750+
"method": "POST",
4751+
"header": [
4752+
{
4753+
"key": "Authorization",
4754+
"value": "Bearer {{jwt-token}}"
4755+
},
4756+
{
4757+
"key": "Content-Type",
4758+
"value": "application/json"
4759+
}
4760+
],
4761+
"body": {
4762+
"mode": "raw",
4763+
"raw": "{\n \"userId\": \"40153800\",\n \"role\": \"copilot\"\n}"
4764+
},
4765+
"url": {
4766+
"raw": "{{api-url}}/projects/{{projectId}}/members",
4767+
"host": [
4768+
"{{api-url}}"
4769+
],
4770+
"path": [
4771+
"projects",
4772+
"{{projectId}}",
4773+
"members"
4774+
]
4775+
},
4776+
"description": "If the request payload is valid, than project member should be created."
4777+
},
4778+
"response": []
4779+
}
4780+
]
4781+
},
45804782
{
45814783
"name": "Create Phase",
45824784
"event": [
@@ -4715,6 +4917,52 @@
47154917
},
47164918
"response": []
47174919
},
4920+
{
4921+
"name": "Create Phase with members",
4922+
"event": [
4923+
{
4924+
"listen": "test",
4925+
"script": {
4926+
"exec": [
4927+
"pm.test(\"Status code is 201\", function () {",
4928+
" pm.response.to.have.status(201);",
4929+
" pm.environment.set(\"phaseId\", pm.response.json().id);",
4930+
"});"
4931+
],
4932+
"type": "text/javascript"
4933+
}
4934+
}
4935+
],
4936+
"request": {
4937+
"method": "POST",
4938+
"header": [
4939+
{
4940+
"key": "Authorization",
4941+
"value": "Bearer {{jwt-token}}"
4942+
},
4943+
{
4944+
"key": "Content-Type",
4945+
"value": "application/json"
4946+
}
4947+
],
4948+
"body": {
4949+
"mode": "raw",
4950+
"raw": "{\n\t\"name\": \"test project phase\",\n\t\"status\": \"active\",\n\t\"startDate\": \"2018-05-15T00:00:00\",\n\t\"endDate\": \"2018-05-16T00:00:00\",\n\t\"budget\": 20,\n\t\"details\": {\n\t\t\"aDetails\": \"a details\"\n\t},\n \"members\": [{{phaseMemberId-1}},{{phaseMemberId-2}}]\n}"
4951+
},
4952+
"url": {
4953+
"raw": "{{api-url}}/projects/{{projectId}}/phases",
4954+
"host": [
4955+
"{{api-url}}"
4956+
],
4957+
"path": [
4958+
"projects",
4959+
"{{projectId}}",
4960+
"phases"
4961+
]
4962+
}
4963+
},
4964+
"response": []
4965+
},
47184966
{
47194967
"name": "List Phase",
47204968
"request": {
@@ -5008,6 +5256,39 @@
50085256
},
50095257
"response": []
50105258
},
5259+
{
5260+
"name": "Update Phase with members",
5261+
"request": {
5262+
"method": "PATCH",
5263+
"header": [
5264+
{
5265+
"key": "Authorization",
5266+
"value": "Bearer {{jwt-token}}"
5267+
},
5268+
{
5269+
"key": "Content-Type",
5270+
"value": "application/json"
5271+
}
5272+
],
5273+
"body": {
5274+
"mode": "raw",
5275+
"raw": "{\n\t\"name\": \"test project phase xxx\",\n\t\"status\": \"inactive\",\n\t\"startDate\": \"2018-05-14T00:00:00\",\n\t\"endDate\": \"2018-05-15T00:00:00\",\n\t\"budget\": 30,\n\t\"progress\": 15,\n\t\"details\": {\n\t\t\"message\": \"phase details\"\n\t},\n \"members\": [{{phaseMemberId-1}},{{phaseMemberId-2}}]\n}"
5276+
},
5277+
"url": {
5278+
"raw": "{{api-url}}/projects/{{projectId}}/phases/{{phaseId}}",
5279+
"host": [
5280+
"{{api-url}}"
5281+
],
5282+
"path": [
5283+
"projects",
5284+
"{{projectId}}",
5285+
"phases",
5286+
"{{phaseId}}"
5287+
]
5288+
}
5289+
},
5290+
"response": []
5291+
},
50115292
{
50125293
"name": "Delete Phase",
50135294
"request": {

src/permissions/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ module.exports = () => {
9898
Authorizer.setPolicy('project.updatePhaseProduct', copilotAndAbove);
9999
Authorizer.setPolicy('project.deletePhaseProduct', copilotAndAbove);
100100

101-
Authorizer.setPolicy('phaseMember.update', projectAdmin);
102-
Authorizer.setPolicy('phaseMember.delete', projectAdmin);
101+
Authorizer.setPolicy('phaseMember.update', copilotAndAbove);
102+
Authorizer.setPolicy('phaseMember.delete', copilotAndAbove);
103103
Authorizer.setPolicy('phaseMember.view', generalPermission(PERMISSION.READ_PROJECT_MEMBER));
104104

105105
Authorizer.setPolicy('milestoneTemplate.clone', projectAdmin);

src/routes/phaseMembers/delete.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ describe('Delete phase member', () => {
130130
.expect(403, done);
131131
});
132132

133-
it('should return 200 for connect admin', (done) => {
133+
it('should return 204 for connect admin', (done) => {
134134
request(server)
135135
.delete(`/v5/projects/${id}/phases/${phaseId}/members/${copilotUser.userId}`)
136136
.set({

src/routes/phaseMembers/update.js

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { middleware as tcMiddleware } from 'tc-core-library-js';
55
import models from '../../models';
66
import util from '../../util';
77
import { EVENT, RESOURCES, ROUTES } from '../../constants';
8+
import updateService from './updateService';
89

910
/**
1011
* API to update a project phase members.
@@ -28,10 +29,7 @@ module.exports = [
2829
async (req, res, next) => {
2930
const projectId = _.parseInt(req.params.projectId);
3031
const phaseId = _.parseInt(req.params.phaseId);
31-
const createdBy = _.parseInt(req.authUser.userId);
32-
const updatedBy = _.parseInt(req.authUser.userId);
3332
const newPhaseMembers = req.body.userIds;
34-
const transaction = await models.sequelize.transaction();
3533
try {
3634
// chekc if project and phase exist
3735
const phase = await models.ProjectPhase.findOne({
@@ -48,31 +46,12 @@ module.exports = [
4846
err.status = 404;
4947
throw (err);
5048
}
51-
const projectMembers = _.map(await models.ProjectMember.getActiveProjectMembers(projectId), 'userId');
52-
const notProjectMembers = _.difference(newPhaseMembers, projectMembers);
53-
if (notProjectMembers.length > 0) {
54-
const err = new Error(`Members with id: ${notProjectMembers} are not members of project ${projectId}`);
55-
err.status = 404;
56-
throw (err);
57-
}
5849
const phaseMembers = await models.ProjectPhaseMember.getPhaseMembers(phaseId);
59-
const existentPhaseMembers = _.map(phaseMembers, 'userId');
60-
let updatedPhaseMembers = _.cloneDeep(phaseMembers);
61-
const updatedPhase = _.cloneDeep(phase);
62-
const membersToAdd = _.difference(newPhaseMembers, existentPhaseMembers);
63-
const membersToRemove = _.differenceBy(existentPhaseMembers, newPhaseMembers);
64-
if (membersToRemove.length > 0) {
65-
await models.ProjectPhaseMember.destroy({ where: { phaseId, userId: membersToRemove }, transaction });
66-
updatedPhaseMembers = _.filter(updatedPhaseMembers, row => !_.includes(membersToRemove, row.userId));
67-
}
68-
if (membersToAdd.length > 0) {
69-
const createData = _.map(membersToAdd, userId => ({ phaseId, userId, createdBy, updatedBy }));
70-
const result = await models.ProjectPhaseMember.bulkCreate(createData, { transaction });
71-
updatedPhaseMembers.push(..._.map(result, item => item.toJSON()));
72-
}
50+
const updatedPhaseMembers = await updateService(req.authUser, projectId, phaseId, newPhaseMembers);
7351
req.log.debug('updated phase members', JSON.stringify(newPhaseMembers, null, 2));
52+
const updatedPhase = _.cloneDeep(phase);
7453
// emit event
75-
if (membersToRemove.length > 0 || membersToAdd.length > 0) {
54+
if (_.intersectionBy(phaseMembers, updatedPhaseMembers, 'id').length !== updatedPhaseMembers.length) {
7655
util.sendResourceToKafkaBus(
7756
req,
7857
EVENT.ROUTING_KEY.PROJECT_PHASE_UPDATED,
@@ -81,10 +60,8 @@ module.exports = [
8160
_.assign(phase, { members: phaseMembers }),
8261
ROUTES.PHASES.UPDATE);
8362
}
84-
await transaction.commit();
8563
res.json(updatedPhaseMembers);
8664
} catch (err) {
87-
await transaction.rollback();
8865
next(err);
8966
}
9067
},

0 commit comments

Comments
 (0)