Skip to content

Commit 61433fc

Browse files
Adds route and controller for missed update user list api (#1766)
* feat: adds route and controller functions for missed update * feat: adds tests for controller and validator of missed update script * fix: correct sinon import * fix: update validator test for query params * fix: failing test due modification of shallow copy of test data * chore: remove unnecessary array spreading * chore: fix typo in discord member mock data index * chore: fixes lint issues
1 parent 3aa1f18 commit 61433fc

File tree

7 files changed

+333
-7
lines changed

7 files changed

+333
-7
lines changed

constants/constants.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
const DOCUMENT_WRITE_SIZE = 500;
2+
const daysOfWeek = {
3+
sun: 0,
4+
mon: 1,
5+
tue: 2,
6+
wed: 3,
7+
thu: 4,
8+
fri: 5,
9+
sat: 6,
10+
};
211

312
module.exports = {
4-
DOCUMENT_WRITE_SIZE,
13+
DOCUMENT_WRITE_SIZE,
14+
daysOfWeek,
515
};

constants/tasks.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ const COMPLETED_TASK_STATUS = {
4444
};
4545
const TASK_SIZE = 5;
4646

47+
const tasksUsersStatus = {
48+
MISSED_UPDATES: "missed-updates",
49+
};
50+
4751
module.exports = {
4852
TASK_TYPE,
4953
TASK_STATUS,
@@ -52,4 +56,5 @@ module.exports = {
5256
TASK_SIZE,
5357
DEFAULT_TASK_PRIORITY,
5458
COMPLETED_TASK_STATUS,
59+
tasksUsersStatus,
5560
};

controllers/tasks.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const tasks = require("../models/tasks");
2-
const { TASK_STATUS, TASK_STATUS_OLD } = require("../constants/tasks");
2+
const { TASK_STATUS, TASK_STATUS_OLD, tasksUsersStatus } = require("../constants/tasks");
33
const { addLog } = require("../models/logs");
44
const { USER_STATUS } = require("../constants/users");
55
const { addOrUpdate, getRdsUserInfoByGitHubUsername } = require("../models/users");
@@ -13,6 +13,9 @@ const { updateUserStatusOnTaskUpdate, updateStatusOnTaskCompletion } = require("
1313
const dataAccess = require("../services/dataAccessLayer");
1414
const { parseSearchQuery } = require("../utils/tasks");
1515
const { addTaskCreatedAtAndUpdatedAtFields } = require("../services/tasks");
16+
const { RQLQueryParser } = require("../utils/RQLParser");
17+
const { getMissedProgressUpdatesUsers } = require("../models/discordactions");
18+
const { daysOfWeek } = require("../constants/constants");
1619
/**
1720
* Creates new task
1821
*
@@ -475,6 +478,32 @@ const updateStatus = async (req, res) => {
475478
}
476479
};
477480

481+
const getUsersHandler = async (req, res) => {
482+
try {
483+
const { size, cursor, q: queryString } = req.query;
484+
const rqlParser = new RQLQueryParser(queryString);
485+
const { "days-count": daysGap, weekday, date, status } = rqlParser.getFilterQueries();
486+
if (!!status && status.length === 1 && status[0].value === tasksUsersStatus.MISSED_UPDATES) {
487+
if (daysGap && daysGap > 1) {
488+
return res.boom.badRequest("number of days gap provided cannot be greater than 1");
489+
}
490+
const response = await getMissedProgressUpdatesUsers({
491+
cursor: cursor,
492+
size: size && Number.parseInt(size),
493+
excludedDates: date?.map((date) => Number.parseInt(date.value)),
494+
excludedDays: weekday?.map((day) => daysOfWeek[day.value]),
495+
dateGap: !!daysGap && daysGap.length === 1 && Number.parseInt(daysGap[0].value),
496+
});
497+
return res.status(200).json(response);
498+
} else {
499+
return res.boom.badRequest("Unknown type and query");
500+
}
501+
} catch (error) {
502+
logger.error("Error in fetching users details of tasks", error);
503+
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
504+
}
505+
};
506+
478507
module.exports = {
479508
addNewTask,
480509
fetchTasks,
@@ -486,4 +515,5 @@ module.exports = {
486515
overdueTasks,
487516
assignTask,
488517
updateStatus,
518+
getUsersHandler,
489519
};

middlewares/validators/tasks.js

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
const joi = require("joi");
22
const { DINERO, NEELAM } = require("../../constants/wallets");
3-
const { TASK_STATUS, TASK_STATUS_OLD, MAPPED_TASK_STATUS } = require("../../constants/tasks");
4-
3+
const { TASK_STATUS, TASK_STATUS_OLD, MAPPED_TASK_STATUS, tasksUsersStatus } = require("../../constants/tasks");
4+
const { RQLQueryParser } = require("../../utils/RQLParser");
5+
const { Operators } = require("../../typeDefinitions/rqlParser");
6+
const { daysOfWeek } = require("../../constants/constants");
57
const TASK_STATUS_ENUM = Object.values(TASK_STATUS);
68
const MAPPED_TASK_STATUS_ENUM = Object.keys(MAPPED_TASK_STATUS);
79

@@ -190,10 +192,68 @@ const getTasksValidator = async (req, res, next) => {
190192
res.boom.badRequest(error.details[0].message);
191193
}
192194
};
195+
const getUsersValidator = async (req, res, next) => {
196+
const queryParamsSchema = joi.object().keys({
197+
cursor: joi.string().optional(),
198+
q: joi.string().optional(),
199+
size: joi.number().integer().min(1).max(2013),
200+
});
201+
const filtersSchema = joi.object().keys({
202+
status: joi
203+
.array()
204+
.items(
205+
joi.object().keys({
206+
value: joi.string().valid(...Object.values(tasksUsersStatus)),
207+
operator: joi.string().valid(Operators.INCLUDE),
208+
})
209+
)
210+
.required(),
211+
"days-count": joi
212+
.array()
213+
.items(
214+
joi.object().keys({
215+
value: joi.number().integer().min(1).max(10),
216+
operator: joi.string().valid(Operators.EXCLUDE),
217+
})
218+
)
219+
.optional(),
220+
weekday: joi
221+
.array()
222+
.items(
223+
joi.object().keys({
224+
value: joi.string().valid(...Object.keys(daysOfWeek)),
225+
operator: joi.string().valid(Operators.EXCLUDE),
226+
})
227+
)
228+
.optional(),
229+
date: joi
230+
.array()
231+
.items(
232+
joi.object().keys({
233+
value: joi.date().timestamp(),
234+
operator: joi.string().valid(Operators.EXCLUDE),
235+
})
236+
)
237+
.optional(),
238+
});
193239

240+
try {
241+
const { q: queryString } = req.query;
242+
const rqlQueryParser = new RQLQueryParser(queryString);
243+
await Promise.all([
244+
queryParamsSchema.validateAsync(req.query),
245+
filtersSchema.validateAsync(rqlQueryParser.getFilterQueries()),
246+
]);
247+
next();
248+
} catch (error) {
249+
logger.error(`Error validating get tasks for users query : ${error}`);
250+
res.boom.badRequest(error.details[0].message);
251+
}
252+
};
194253
module.exports = {
195254
createTask,
196255
updateTask,
197256
updateSelfTask,
198257
getTasksValidator,
258+
getUsersValidator,
199259
};

routes/tasks.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@ const express = require("express");
22
const router = express.Router();
33
const authenticate = require("../middlewares/authenticate");
44
const tasks = require("../controllers/tasks");
5-
const { createTask, updateTask, updateSelfTask, getTasksValidator } = require("../middlewares/validators/tasks");
5+
const {
6+
createTask,
7+
updateTask,
8+
updateSelfTask,
9+
getTasksValidator,
10+
getUsersValidator,
11+
} = require("../middlewares/validators/tasks");
612
const authorizeRoles = require("../middlewares/authorizeRoles");
713
const { APPOWNER, SUPERUSER } = require("../constants/roles");
814
const assignTask = require("../middlewares/assignTask");
915
const { cacheResponse, invalidateCache } = require("../utils/cache");
1016
const { ALL_TASKS } = require("../constants/cacheKeys");
17+
const { verifyCronJob } = require("../middlewares/authorizeBot");
1118

1219
router.get("/", getTasksValidator, cacheResponse({ invalidationKey: ALL_TASKS, expiry: 10 }), tasks.fetchTasks);
1320
router.get("/self", authenticate, tasks.getSelfTasks);
@@ -40,6 +47,8 @@ router.patch(
4047
);
4148
router.patch("/assign/self", authenticate, invalidateCache({ invalidationKeys: [ALL_TASKS] }), tasks.assignTask);
4249

50+
router.get("/users/discord", verifyCronJob, getUsersValidator, tasks.getUsersHandler);
51+
4352
router.post("/migration", authenticate, authorizeRoles([SUPERUSER]), tasks.updateStatus);
4453

4554
module.exports = router;

test/integration/tasks.test.js

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,30 @@ const tasks = require("../../models/tasks");
99
const authService = require("../../services/authService");
1010
const addUser = require("../utils/addUser");
1111
const userModel = require("../../models/users");
12+
const userStatusModel = require("../../models/userStatus");
1213
const config = require("config");
1314
const cookieName = config.get("userToken.cookieName");
1415
const userData = require("../fixtures/user/user")();
1516
const tasksData = require("../fixtures/tasks/tasks")();
1617
const { DINERO, NEELAM } = require("../../constants/wallets");
1718
const cleanDb = require("../utils/cleanDb");
18-
const { TASK_STATUS } = require("../../constants/tasks");
19+
const { TASK_STATUS, tasksUsersStatus } = require("../../constants/tasks");
1920
const updateTaskStatus = require("../fixtures/tasks/tasks1")();
21+
const userStatusData = require("../fixtures/userStatus/userStatus");
22+
const tasksModel = firestore.collection("tasks");
23+
const discordService = require("../../services/discordService");
24+
const { CRON_JOB_HANDLER } = require("../../constants/bot");
2025
chai.use(chaiHttp);
2126

2227
const appOwner = userData[3];
2328
const superUser = userData[4];
2429

2530
let jwt, superUserJwt;
31+
const { createProgressDocument } = require("../../models/progresses");
32+
const { stubbedModelTaskProgressData } = require("../fixtures/progress/progresses");
33+
const { convertDaysToMilliseconds } = require("../../utils/time");
34+
const { getDiscordMembers } = require("../fixtures/discordResponse/discord-response");
35+
const { generateCronJobToken } = require("../utils/generateBotToken");
2636

2737
const taskData = [
2838
{
@@ -1293,6 +1303,117 @@ describe("Tasks", function () {
12931303
});
12941304
});
12951305

1306+
describe("GET /tasks/users", function () {
1307+
let activeUserWithProgressUpdates;
1308+
let idleUser;
1309+
let userNotInDiscord;
1310+
let jwtToken;
1311+
beforeEach(async function () {
1312+
await cleanDb();
1313+
idleUser = { ...userData[9], discordId: getDiscordMembers[0].user.id };
1314+
activeUserWithProgressUpdates = { ...userData[10], discordId: getDiscordMembers[1].user.id };
1315+
const activeUserWithNoUpdates = { ...userData[0], discordId: getDiscordMembers[2].user.id };
1316+
userNotInDiscord = { ...userData[4], discordId: "Not in discord" };
1317+
const {
1318+
idleStatus: idleUserStatus,
1319+
activeStatus: activeUserStatus,
1320+
userStatusDataForOooState: oooUserStatus,
1321+
} = userStatusData;
1322+
const userIdList = await Promise.all([
1323+
await addUser(idleUser), // idle user with no task progress updates
1324+
await addUser(activeUserWithProgressUpdates), // active user with task progress updates
1325+
await addUser(activeUserWithNoUpdates), // active user with no task progress updates
1326+
await addUser(userNotInDiscord), // OOO user with
1327+
]);
1328+
await Promise.all([
1329+
await userStatusModel.updateUserStatus(userIdList[0], idleUserStatus),
1330+
await userStatusModel.updateUserStatus(userIdList[1], activeUserStatus),
1331+
await userStatusModel.updateUserStatus(userIdList[2], activeUserStatus),
1332+
await userStatusModel.updateUserStatus(userIdList[3], oooUserStatus),
1333+
]);
1334+
1335+
const tasksPromise = [];
1336+
1337+
for (let index = 0; index < 4; index++) {
1338+
const task = tasksData[index];
1339+
const validTask = {
1340+
...task,
1341+
assignee: userIdList[index],
1342+
startedOn: (new Date().getTime() - convertDaysToMilliseconds(7)) / 1000,
1343+
endsOn: (new Date().getTime() + convertDaysToMilliseconds(4)) / 1000,
1344+
status: TASK_STATUS.IN_PROGRESS,
1345+
};
1346+
1347+
tasksPromise.push(tasksModel.add(validTask));
1348+
}
1349+
const taskIdList = (await Promise.all(tasksPromise)).map((tasksDoc) => tasksDoc.id);
1350+
const progressDataList = [];
1351+
1352+
const date = new Date();
1353+
date.setDate(date.getDate() - 1);
1354+
const progressData = stubbedModelTaskProgressData(null, taskIdList[2], date.getTime(), date.valueOf());
1355+
progressDataList.push(progressData);
1356+
1357+
await Promise.all(progressDataList.map(async (progress) => await createProgressDocument(progress)));
1358+
const discordMembers = [...getDiscordMembers].map((user) => {
1359+
return { ...user };
1360+
});
1361+
const roles1 = [...discordMembers[0].roles, "9876543210"];
1362+
const roles2 = [...discordMembers[1].roles, "9876543210"];
1363+
discordMembers[0].roles = roles1;
1364+
discordMembers[1].roles = roles2;
1365+
sinon.stub(discordService, "getDiscordMembers").returns(discordMembers);
1366+
jwtToken = generateCronJobToken({ name: CRON_JOB_HANDLER });
1367+
});
1368+
afterEach(async function () {
1369+
sinon.restore();
1370+
await cleanDb();
1371+
});
1372+
it("should return successful response with user id list", async function () {
1373+
const response = await chai
1374+
.request(app)
1375+
.get("/tasks/users/discord")
1376+
.query({ q: `status:${tasksUsersStatus.MISSED_UPDATES}` })
1377+
.set("Authorization", `Bearer ${jwtToken}`);
1378+
expect(response.body).to.be.deep.equal({
1379+
usersToAddRole: [activeUserWithProgressUpdates.discordId],
1380+
tasks: 4,
1381+
missedUpdatesTasks: 3,
1382+
});
1383+
expect(response.status).to.be.equal(200);
1384+
});
1385+
it("should return successful response with user id when all params are passed", async function () {
1386+
const response = await chai
1387+
.request(app)
1388+
.get("/tasks/users/discord")
1389+
.query({
1390+
size: 3,
1391+
q: `status:${tasksUsersStatus.MISSED_UPDATES} -weekday:sun -weekday:mon -weekday:tue -weekday:wed -weekday:thu -weekday:fri -date:231423432 -days-count:4`,
1392+
})
1393+
.set("Authorization", `Bearer ${jwtToken}`);
1394+
expect(response.body).to.be.deep.equal({
1395+
usersToAddRole: [],
1396+
tasks: 0,
1397+
missedUpdatesTasks: 0,
1398+
});
1399+
expect(response.status).to.be.equal(200);
1400+
});
1401+
1402+
it("should return bad request error when status is not passed", async function () {
1403+
const response = await chai
1404+
.request(app)
1405+
.get("/tasks/users/discord")
1406+
.query({})
1407+
.set("Authorization", `Bearer ${jwtToken}`);
1408+
expect(response.body).to.be.deep.equal({
1409+
error: "Bad Request",
1410+
message: '"status" is required',
1411+
statusCode: 400,
1412+
});
1413+
expect(response.status).to.be.equal(400);
1414+
});
1415+
});
1416+
12961417
describe("PATCH /tasks/:id should update the tasks by SuperUser", function () {
12971418
beforeEach(async function () {
12981419
const superUserId = await addUser(superUser);

0 commit comments

Comments
 (0)