Skip to content

Commit 0c62d57

Browse files
Script to Migrate status COMPLETED to DONE in tasks model (#1692)
* written test for service file * implement batch operation for task status from COMPLETED to DONE * added code for updating task status COMPLETED to DONE * written test for changes * revert changes * written integration test * revert one change * fix overlap problem * added model test * added chunk into promise.all
1 parent cba5fa7 commit 0c62d57

File tree

5 files changed

+165
-1
lines changed

5 files changed

+165
-1
lines changed

controllers/tasks.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,16 @@ const assignTask = async (req, res) => {
443443
}
444444
};
445445

446+
const updateStatus = async (req, res) => {
447+
try {
448+
const response = await tasks.updateTaskStatus();
449+
return res.status(200).json(response);
450+
} catch (error) {
451+
logger.error("Error in migration scripts", error);
452+
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
453+
}
454+
};
455+
446456
module.exports = {
447457
addNewTask,
448458
fetchTasks,
@@ -453,4 +463,5 @@ module.exports = {
453463
updateTaskStatus,
454464
overdueTasks,
455465
assignTask,
466+
updateStatus,
456467
};

models/tasks.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ const tasksModel = firestore.collection("tasks");
33
const ItemModel = firestore.collection("itemTags");
44
const dependencyModel = firestore.collection("taskDependencies");
55
const userUtils = require("../utils/users");
6+
const { updateTaskStatusToDone } = require("../services/tasks");
7+
const { chunks } = require("../utils/array");
8+
const { DOCUMENT_WRITE_SIZE } = require("../constants/constants");
69
const { fromFirestoreData, toFirestoreData, buildTasks } = require("../utils/tasks");
710
const { TASK_TYPE, TASK_STATUS, TASK_STATUS_OLD, TASK_SIZE } = require("../constants/tasks");
811
const { IN_PROGRESS, NEEDS_REVIEW, IN_REVIEW, ASSIGNED, BLOCKED, SMOKE_TESTING, COMPLETED, SANITY_CHECK } = TASK_STATUS;
912
const { OLD_ACTIVE, OLD_BLOCKED, OLD_PENDING, OLD_COMPLETED } = TASK_STATUS_OLD;
13+
const { INTERNAL_SERVER_ERROR } = require("../constants/errorMessages");
1014

1115
/**
1216
* Adds and Updates tasks
@@ -575,6 +579,62 @@ const getOverdueTasks = async (days = 0) => {
575579
}
576580
};
577581

582+
const updateTaskStatus = async () => {
583+
try {
584+
const snapshot = await tasksModel.where("status", "==", "COMPLETED").get();
585+
const tasksStatusCompleted = [];
586+
let summary = {
587+
totalTasks: snapshot.size,
588+
totalUpdatedStatus: 0,
589+
totalOperationsFailed: 0,
590+
updatedTaskDetails: [],
591+
failedTaskDetails: [],
592+
};
593+
594+
if (snapshot.size === 0) {
595+
return summary;
596+
}
597+
598+
snapshot.forEach((task) => {
599+
const id = task.id;
600+
const taskData = task.data();
601+
tasksStatusCompleted.push({ ...taskData, id });
602+
});
603+
const taskStatusCompletedChunks = chunks(tasksStatusCompleted, DOCUMENT_WRITE_SIZE);
604+
605+
const updatedTasksPromises = await Promise.all(
606+
taskStatusCompletedChunks.map(async (tasks) => {
607+
const res = await updateTaskStatusToDone(tasks);
608+
return {
609+
totalUpdatedStatus: res.totalUpdatedStatus,
610+
totalOperationsFailed: res.totalOperationsFailed,
611+
updatedTaskDetails: res.updatedTaskDetails,
612+
failedTaskDetails: res.failedTaskDetails,
613+
};
614+
})
615+
);
616+
617+
updatedTasksPromises.forEach((res) => {
618+
summary = {
619+
...summary,
620+
totalUpdatedStatus: (summary.totalUpdatedStatus += res.totalUpdatedStatus),
621+
totalOperationsFailed: (summary.totalOperationsFailed += res.totalOperationsFailed),
622+
updatedTaskDetails: [...summary.updatedTaskDetails, ...res.updatedTaskDetails],
623+
failedTaskDetails: [...summary.failedTaskDetails, ...res.failedTaskDetails],
624+
};
625+
});
626+
627+
if (summary.totalOperationsFailed === summary.totalTasks) {
628+
throw Error(INTERNAL_SERVER_ERROR);
629+
}
630+
631+
return summary;
632+
} catch (error) {
633+
logger.error(`Error in updating task status: ${error}`);
634+
throw error;
635+
}
636+
};
637+
578638
module.exports = {
579639
updateTask,
580640
fetchTasks,
@@ -591,4 +651,5 @@ module.exports = {
591651
fetchPaginatedTasks,
592652
getBuiltTasks,
593653
getOverdueTasks,
654+
updateTaskStatus,
594655
};

routes/tasks.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,6 @@ router.patch(
4040
);
4141
router.patch("/assign/self", authenticate, invalidateCache({ invalidationKeys: [ALL_TASKS] }), tasks.assignTask);
4242

43+
router.post("/migration", authenticate, authorizeRoles([SUPERUSER]), tasks.updateStatus);
44+
4345
module.exports = router;

test/integration/tasks.test.js

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const sinon = require("sinon");
33
const { expect } = chai;
44
const chaiHttp = require("chai-http");
55

6+
const firestore = require("../../utils/firestore");
67
const app = require("../../server");
78
const tasks = require("../../models/tasks");
89
const authService = require("../../services/authService");
@@ -1012,7 +1013,7 @@ describe("Tasks", function () {
10121013
type: "feature",
10131014
endsOn: 1234,
10141015
startedOn: 4567,
1015-
status: "COMPLETED",
1016+
status: "completed",
10161017
percentCompleted: 100,
10171018
participants: [],
10181019
assignee: appOwner.username,
@@ -1070,4 +1071,51 @@ describe("Tasks", function () {
10701071
expect(res.body.message).to.be.equal("No overdue tasks found");
10711072
});
10721073
});
1074+
1075+
describe("POST /tasks/migration", function () {
1076+
it("Should update status COMPLETED to DONE successful", async function () {
1077+
const taskData1 = { status: "COMPLETED" };
1078+
await firestore.collection("tasks").doc("updateTaskStatus1").set(taskData1);
1079+
const res = await chai.request(app).post("/tasks/migration").set("cookie", `${cookieName}=${superUserJwt}`);
1080+
expect(res).to.have.status(200);
1081+
expect(res.body.totalTasks).to.be.equal(1);
1082+
expect(res.body.totalUpdatedStatus).to.be.equal(1);
1083+
expect(res.body.updatedTaskDetails).to.deep.equal(["updateTaskStatus1"]);
1084+
expect(res.body.totalOperationsFailed).to.be.equal(0);
1085+
expect(res.body.failedTaskDetails).to.deep.equal([]);
1086+
});
1087+
1088+
it("Should not update if not found any COMPLETED task status ", async function () {
1089+
const res = await chai.request(app).post("/tasks/migration").set("cookie", `${cookieName}=${superUserJwt}`);
1090+
expect(res).to.have.status(200);
1091+
expect(res.body.totalTasks).to.be.equal(0);
1092+
expect(res.body.totalUpdatedStatus).to.be.equal(0);
1093+
expect(res.body.updatedTaskDetails).to.deep.equal([]);
1094+
expect(res.body.totalOperationsFailed).to.be.equal(0);
1095+
expect(res.body.failedTaskDetails).to.deep.equal([]);
1096+
});
1097+
1098+
it("should throw an error if firestore batch operations fail", async function () {
1099+
const stub = sinon.stub(firestore, "batch");
1100+
stub.returns({
1101+
update: function () {},
1102+
commit: function () {
1103+
throw new Error("Firestore batch commit failed!");
1104+
},
1105+
});
1106+
const taskData1 = { status: "COMPLETED" };
1107+
await firestore.collection("tasks").doc("updateTaskStatus1").set(taskData1);
1108+
const res = await chai.request(app).post("/tasks/migration").set("cookie", `${cookieName}=${superUserJwt}`);
1109+
expect(res.status).to.equal(500);
1110+
const response = res.body;
1111+
expect(response.message).to.be.equal("An internal server error occurred");
1112+
});
1113+
1114+
it("Should return 401 if not super_user", async function () {
1115+
const nonSuperUserId = await addUser(appOwner);
1116+
const nonSuperUserJwt = authService.generateAuthToken({ userId: nonSuperUserId });
1117+
const res = await chai.request(app).post("/tasks/migration").set("cookie", `${cookieName}=${nonSuperUserJwt}`);
1118+
expect(res).to.have.status(401);
1119+
});
1120+
});
10731121
});

test/unit/models/tasks.test.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
/* eslint-disable security/detect-object-injection */
66

77
const chai = require("chai");
8+
const sinon = require("sinon");
89
const { expect } = chai;
910
const cleanDb = require("../../utils/cleanDb");
1011
const tasksData = require("../../fixtures/tasks/tasks")();
@@ -304,4 +305,45 @@ describe("tasks", function () {
304305
expect(usersWithOverdueTasks.length).to.be.equal(4);
305306
});
306307
});
308+
309+
describe("update task status", function () {
310+
beforeEach(async function () {
311+
const addTasksPromises = [];
312+
tasksData.forEach((task) => {
313+
const taskData = {
314+
...task,
315+
status: "COMPLETED",
316+
};
317+
addTasksPromises.push(tasksModel.add(taskData));
318+
});
319+
320+
await Promise.all(addTasksPromises);
321+
});
322+
323+
afterEach(function () {
324+
sinon.restore();
325+
});
326+
327+
it("Should update task status COMPLETED to DONE", async function () {
328+
const res = await tasks.updateTaskStatus();
329+
expect(res.totalTasks).to.be.equal(8);
330+
expect(res.totalUpdatedStatus).to.be.equal(8);
331+
});
332+
333+
it("should throw an error if firebase batch operation fails", async function () {
334+
const stub = sinon.stub(firestore, "batch");
335+
stub.returns({
336+
update: function () {},
337+
commit: function () {
338+
throw new Error("Firestore batch update failed");
339+
},
340+
});
341+
try {
342+
await tasks.updateTaskStatus();
343+
} catch (error) {
344+
expect(error).to.be.an.instanceOf(Error);
345+
expect(error.message).to.equal("An internal server error occurred");
346+
}
347+
});
348+
});
307349
});

0 commit comments

Comments
 (0)