Skip to content
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
12 changes: 11 additions & 1 deletion constants/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
const DOCUMENT_WRITE_SIZE = 500;
const daysOfWeek = {
sun: 0,
mon: 1,
tue: 2,
wed: 3,
thu: 4,
fri: 5,
sat: 6,
};

module.exports = {
DOCUMENT_WRITE_SIZE,
DOCUMENT_WRITE_SIZE,
daysOfWeek,
};
5 changes: 5 additions & 0 deletions constants/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ const COMPLETED_TASK_STATUS = {
};
const TASK_SIZE = 5;

const tasksUsersStatus = {
MISSED_UPDATES: "missed-updates",
};

module.exports = {
TASK_TYPE,
TASK_STATUS,
Expand All @@ -52,4 +56,5 @@ module.exports = {
TASK_SIZE,
DEFAULT_TASK_PRIORITY,
COMPLETED_TASK_STATUS,
tasksUsersStatus,
};
32 changes: 31 additions & 1 deletion controllers/tasks.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const tasks = require("../models/tasks");
const { TASK_STATUS, TASK_STATUS_OLD } = require("../constants/tasks");
const { TASK_STATUS, TASK_STATUS_OLD, tasksUsersStatus } = require("../constants/tasks");
const { addLog } = require("../models/logs");
const { USER_STATUS } = require("../constants/users");
const { addOrUpdate, getRdsUserInfoByGitHubUsername } = require("../models/users");
Expand All @@ -13,6 +13,9 @@ const { updateUserStatusOnTaskUpdate, updateStatusOnTaskCompletion } = require("
const dataAccess = require("../services/dataAccessLayer");
const { parseSearchQuery } = require("../utils/tasks");
const { addTaskCreatedAtAndUpdatedAtFields } = require("../services/tasks");
const { RQLQueryParser } = require("../utils/RQLParser");
const { getMissedProgressUpdatesUsers } = require("../models/discordactions");
const { daysOfWeek } = require("../constants/constants");
/**
* Creates new task
*
Expand Down Expand Up @@ -475,6 +478,32 @@ const updateStatus = async (req, res) => {
}
};

const getUsersHandler = async (req, res) => {
try {
const { size, cursor, q: queryString } = req.query;
const rqlParser = new RQLQueryParser(queryString);
const { "days-count": daysGap, weekday, date, status } = rqlParser.getFilterQueries();
if (!!status && status.length === 1 && status[0].value === tasksUsersStatus.MISSED_UPDATES) {
if (daysGap && daysGap > 1) {
return res.boom.badRequest("number of days gap provided cannot be greater than 1");
}
const response = await getMissedProgressUpdatesUsers({
cursor: cursor,
size: size && Number.parseInt(size),
excludedDates: date?.map((date) => Number.parseInt(date.value)),
excludedDays: weekday?.map((day) => daysOfWeek[day.value]),
dateGap: !!daysGap && daysGap.length === 1 && Number.parseInt(daysGap[0].value),
});
return res.status(200).json(response);
} else {
return res.boom.badRequest("Unknown type and query");
}
} catch (error) {
logger.error("Error in fetching users details of tasks", error);
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
}
};

module.exports = {
addNewTask,
fetchTasks,
Expand All @@ -486,4 +515,5 @@ module.exports = {
overdueTasks,
assignTask,
updateStatus,
getUsersHandler,
};
64 changes: 62 additions & 2 deletions middlewares/validators/tasks.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const joi = require("joi");
const { DINERO, NEELAM } = require("../../constants/wallets");
const { TASK_STATUS, TASK_STATUS_OLD, MAPPED_TASK_STATUS } = require("../../constants/tasks");

const { TASK_STATUS, TASK_STATUS_OLD, MAPPED_TASK_STATUS, tasksUsersStatus } = require("../../constants/tasks");
const { RQLQueryParser } = require("../../utils/RQLParser");
const { Operators } = require("../../typeDefinitions/rqlParser");
const { daysOfWeek } = require("../../constants/constants");
const TASK_STATUS_ENUM = Object.values(TASK_STATUS);
const MAPPED_TASK_STATUS_ENUM = Object.keys(MAPPED_TASK_STATUS);

Expand Down Expand Up @@ -190,10 +192,68 @@ const getTasksValidator = async (req, res, next) => {
res.boom.badRequest(error.details[0].message);
}
};
const getUsersValidator = async (req, res, next) => {
const queryParamsSchema = joi.object().keys({
cursor: joi.string().optional(),
q: joi.string().optional(),
size: joi.number().integer().min(1).max(2013),
});
const filtersSchema = joi.object().keys({
status: joi
.array()
.items(
joi.object().keys({
value: joi.string().valid(...Object.values(tasksUsersStatus)),
operator: joi.string().valid(Operators.INCLUDE),
})
)
.required(),
"days-count": joi
.array()
.items(
joi.object().keys({
value: joi.number().integer().min(1).max(10),
operator: joi.string().valid(Operators.EXCLUDE),
})
)
.optional(),
weekday: joi
.array()
.items(
joi.object().keys({
value: joi.string().valid(...Object.keys(daysOfWeek)),
operator: joi.string().valid(Operators.EXCLUDE),
})
)
.optional(),
date: joi
.array()
.items(
joi.object().keys({
value: joi.date().timestamp(),
operator: joi.string().valid(Operators.EXCLUDE),
})
)
.optional(),
});

try {
const { q: queryString } = req.query;
const rqlQueryParser = new RQLQueryParser(queryString);
await Promise.all([
queryParamsSchema.validateAsync(req.query),
filtersSchema.validateAsync(rqlQueryParser.getFilterQueries()),
]);
next();
} catch (error) {
logger.error(`Error validating get tasks for users query : ${error}`);
res.boom.badRequest(error.details[0].message);
}
};
module.exports = {
createTask,
updateTask,
updateSelfTask,
getTasksValidator,
getUsersValidator,
};
11 changes: 10 additions & 1 deletion routes/tasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ const express = require("express");
const router = express.Router();
const authenticate = require("../middlewares/authenticate");
const tasks = require("../controllers/tasks");
const { createTask, updateTask, updateSelfTask, getTasksValidator } = require("../middlewares/validators/tasks");
const {
createTask,
updateTask,
updateSelfTask,
getTasksValidator,
getUsersValidator,
} = require("../middlewares/validators/tasks");
const authorizeRoles = require("../middlewares/authorizeRoles");
const { APPOWNER, SUPERUSER } = require("../constants/roles");
const assignTask = require("../middlewares/assignTask");
const { cacheResponse, invalidateCache } = require("../utils/cache");
const { ALL_TASKS } = require("../constants/cacheKeys");
const { verifyCronJob } = require("../middlewares/authorizeBot");

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

router.get("/users/discord", verifyCronJob, getUsersValidator, tasks.getUsersHandler);

Check failure

Code scanning / CodeQL

Missing rate limiting

This route handler performs [authorization](1), but is not rate-limited.

router.post("/migration", authenticate, authorizeRoles([SUPERUSER]), tasks.updateStatus);

module.exports = router;
123 changes: 122 additions & 1 deletion test/integration/tasks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,30 @@ const tasks = require("../../models/tasks");
const authService = require("../../services/authService");
const addUser = require("../utils/addUser");
const userModel = require("../../models/users");
const userStatusModel = require("../../models/userStatus");
const config = require("config");
const cookieName = config.get("userToken.cookieName");
const userData = require("../fixtures/user/user")();
const tasksData = require("../fixtures/tasks/tasks")();
const { DINERO, NEELAM } = require("../../constants/wallets");
const cleanDb = require("../utils/cleanDb");
const { TASK_STATUS } = require("../../constants/tasks");
const { TASK_STATUS, tasksUsersStatus } = require("../../constants/tasks");
const updateTaskStatus = require("../fixtures/tasks/tasks1")();
const userStatusData = require("../fixtures/userStatus/userStatus");
const tasksModel = firestore.collection("tasks");
const discordService = require("../../services/discordService");
const { CRON_JOB_HANDLER } = require("../../constants/bot");
chai.use(chaiHttp);

const appOwner = userData[3];
const superUser = userData[4];

let jwt, superUserJwt;
const { createProgressDocument } = require("../../models/progresses");
const { stubbedModelTaskProgressData } = require("../fixtures/progress/progresses");
const { convertDaysToMilliseconds } = require("../../utils/time");
const { getDiscordMembers } = require("../fixtures/discordResponse/discord-response");
const { generateCronJobToken } = require("../utils/generateBotToken");

const taskData = [
{
Expand Down Expand Up @@ -1293,6 +1303,117 @@ describe("Tasks", function () {
});
});

describe("GET /tasks/users", function () {
let activeUserWithProgressUpdates;
let idleUser;
let userNotInDiscord;
let jwtToken;
beforeEach(async function () {
await cleanDb();
idleUser = { ...userData[9], discordId: getDiscordMembers[0].user.id };
activeUserWithProgressUpdates = { ...userData[10], discordId: getDiscordMembers[1].user.id };
const activeUserWithNoUpdates = { ...userData[0], discordId: getDiscordMembers[2].user.id };
userNotInDiscord = { ...userData[4], discordId: "Not in discord" };
const {
idleStatus: idleUserStatus,
activeStatus: activeUserStatus,
userStatusDataForOooState: oooUserStatus,
} = userStatusData;
const userIdList = await Promise.all([
await addUser(idleUser), // idle user with no task progress updates
await addUser(activeUserWithProgressUpdates), // active user with task progress updates
await addUser(activeUserWithNoUpdates), // active user with no task progress updates
await addUser(userNotInDiscord), // OOO user with
]);
await Promise.all([
await userStatusModel.updateUserStatus(userIdList[0], idleUserStatus),
await userStatusModel.updateUserStatus(userIdList[1], activeUserStatus),
await userStatusModel.updateUserStatus(userIdList[2], activeUserStatus),
await userStatusModel.updateUserStatus(userIdList[3], oooUserStatus),
]);

const tasksPromise = [];

for (let index = 0; index < 4; index++) {
const task = tasksData[index];
const validTask = {
...task,
assignee: userIdList[index],
startedOn: (new Date().getTime() - convertDaysToMilliseconds(7)) / 1000,
endsOn: (new Date().getTime() + convertDaysToMilliseconds(4)) / 1000,
status: TASK_STATUS.IN_PROGRESS,
};

tasksPromise.push(tasksModel.add(validTask));
}
const taskIdList = (await Promise.all(tasksPromise)).map((tasksDoc) => tasksDoc.id);
const progressDataList = [];

const date = new Date();
date.setDate(date.getDate() - 1);
const progressData = stubbedModelTaskProgressData(null, taskIdList[2], date.getTime(), date.valueOf());
progressDataList.push(progressData);

await Promise.all(progressDataList.map(async (progress) => await createProgressDocument(progress)));
const discordMembers = [...getDiscordMembers].map((user) => {
return { ...user };
});
const roles1 = [...discordMembers[0].roles, "9876543210"];
const roles2 = [...discordMembers[1].roles, "9876543210"];
discordMembers[0].roles = roles1;
discordMembers[1].roles = roles2;
sinon.stub(discordService, "getDiscordMembers").returns(discordMembers);
jwtToken = generateCronJobToken({ name: CRON_JOB_HANDLER });
});
afterEach(async function () {
sinon.restore();
await cleanDb();
});
it("should return successful response with user id list", async function () {
const response = await chai
.request(app)
.get("/tasks/users/discord")
.query({ q: `status:${tasksUsersStatus.MISSED_UPDATES}` })
.set("Authorization", `Bearer ${jwtToken}`);
expect(response.body).to.be.deep.equal({
usersToAddRole: [activeUserWithProgressUpdates.discordId],
tasks: 4,
missedUpdatesTasks: 3,
});
expect(response.status).to.be.equal(200);
});
it("should return successful response with user id when all params are passed", async function () {
const response = await chai
.request(app)
.get("/tasks/users/discord")
.query({
size: 3,
q: `status:${tasksUsersStatus.MISSED_UPDATES} -weekday:sun -weekday:mon -weekday:tue -weekday:wed -weekday:thu -weekday:fri -date:231423432 -days-count:4`,
})
.set("Authorization", `Bearer ${jwtToken}`);
expect(response.body).to.be.deep.equal({
usersToAddRole: [],
tasks: 0,
missedUpdatesTasks: 0,
});
expect(response.status).to.be.equal(200);
});

it("should return bad request error when status is not passed", async function () {
const response = await chai
.request(app)
.get("/tasks/users/discord")
.query({})
.set("Authorization", `Bearer ${jwtToken}`);
expect(response.body).to.be.deep.equal({
error: "Bad Request",
message: '"status" is required',
statusCode: 400,
});
expect(response.status).to.be.equal(400);
});
});

describe("PATCH /tasks/:id should update the tasks by SuperUser", function () {
beforeEach(async function () {
const superUserId = await addUser(superUser);
Expand Down
Loading