Skip to content

Commit 65cd50b

Browse files
committed
Merge branch 'develop' of https://github.com/Real-Dev-Squad/website-backend into feat/cpllapsed-task
2 parents 7562980 + eb0a1b1 commit 65cd50b

File tree

24 files changed

+1959
-1532
lines changed

24 files changed

+1959
-1532
lines changed

controllers/issues.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
const issuesService = require("../services/issuesService");
2+
const tasks = require("../models/tasks");
3+
const { SOMETHING_WENT_WRONG } = require("../constants/errorMessages");
4+
5+
/**
6+
* Get the issues of the repo
7+
* @param {Object} req - Express request object
8+
* @param {Object} res - Express response object
9+
*/
10+
11+
const getIssues = async (req, res) => {
12+
try {
13+
const { q: queryString } = req.query;
14+
let issues = {};
15+
if (queryString) {
16+
const searchedIssues = await issuesService.searchOrgIssues(queryString);
17+
issues.data = searchedIssues?.data?.items ?? [];
18+
} else {
19+
issues = await issuesService.getOrgIssues();
20+
}
21+
22+
let issuesData = issues.data.length > 0 ? issues.data : [];
23+
issuesData = issuesData.filter((issue) => !Object.keys(issue).includes("pull_request"));
24+
issuesData = issuesData.map(async (issue) => {
25+
const taskData = await tasks.fetchTaskByIssueId(issue.id);
26+
if (taskData) {
27+
issue.taskExists = true;
28+
}
29+
30+
return issue;
31+
});
32+
const updatedIsuees = await Promise.all(issuesData);
33+
return res.json({
34+
message: "Issues returned successfully!",
35+
issues: updatedIsuees,
36+
});
37+
} catch (err) {
38+
logger.error(`Error while retriving issues ${err}`);
39+
return res.boom.badImplementation(SOMETHING_WENT_WRONG);
40+
}
41+
};
42+
43+
/**
44+
* Receive updated issue information from webhook
45+
* @param {Object} req - Express request object
46+
* @param {Object} res - Express response object
47+
*/
48+
const issueUpdates = async (req, res) => {
49+
try {
50+
const response = req.body;
51+
if ("issue" in response) {
52+
const { issue } = response;
53+
const taskData = await tasks.fetchTaskByIssueId(issue.id);
54+
if (taskData) {
55+
// filtering properties with undefined or null values
56+
const updatedTaskData = Object.fromEntries(Object.entries(taskData).filter(([_, value]) => value ?? false));
57+
58+
updatedTaskData.title = issue.title;
59+
updatedTaskData.github = {
60+
issue: {
61+
...updatedTaskData.github.issue,
62+
status: issue.state,
63+
},
64+
};
65+
66+
// If the issue has any updates with the assignee
67+
if (issue.assignee) {
68+
// If there are no previous assignees or the task was not assigned before
69+
if (!updatedTaskData.github.issue.assignee || !updatedTaskData.assignee) {
70+
updatedTaskData.github.issue.assignee = issue.assignee.login;
71+
}
72+
}
73+
// If the issue assignee was removed and task was not assigned
74+
else if (updatedTaskData.github.issue.assignee && !updatedTaskData.assignee) {
75+
delete updatedTaskData.github.issue.assignee;
76+
}
77+
78+
if (issue.state === "closed") {
79+
updatedTaskData.github.issue.closedAt = issue.closed_at;
80+
}
81+
82+
await tasks.updateTask(updatedTaskData, taskData.id);
83+
return res.json({
84+
message: "Task updated successfully",
85+
});
86+
} else {
87+
return res.json({
88+
message: "No task was found for the updated issue",
89+
});
90+
}
91+
}
92+
return res.json({
93+
message: "No issue was updated",
94+
});
95+
} catch (err) {
96+
logger.error(`Error while retriving issues ${err}`);
97+
return res.boom.badImplementation(SOMETHING_WENT_WRONG);
98+
}
99+
};
100+
101+
module.exports = {
102+
getIssues,
103+
issueUpdates,
104+
};

controllers/tasks.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const tasks = require("../models/tasks");
22
const { TASK_STATUS, TASK_STATUS_OLD } = require("../constants/tasks");
33
const { addLog } = require("../models/logs");
44
const { USER_STATUS } = require("../constants/users");
5-
const { addOrUpdate } = require("../models/users");
5+
const { addOrUpdate, getRdsUserInfoByGitHubUsername } = require("../models/users");
66
const { OLD_ACTIVE, OLD_BLOCKED, OLD_PENDING } = TASK_STATUS_OLD;
77
const { IN_PROGRESS, BLOCKED, SMOKE_TESTING, ASSIGNED } = TASK_STATUS;
88
const { INTERNAL_SERVER_ERROR, SOMETHING_WENT_WRONG } = require("../constants/errorMessages");
@@ -20,6 +20,7 @@ const addNewTask = async (req, res) => {
2020
...req.body,
2121
createdBy,
2222
};
23+
2324
const task = await tasks.updateTask(body);
2425

2526
return res.json({
@@ -41,9 +42,32 @@ const addNewTask = async (req, res) => {
4142
const fetchTasks = async (req, res) => {
4243
try {
4344
const allTasks = await tasks.fetchTasks();
45+
const fetchTasksWithRdsAssigneeInfo = allTasks.map(async (task) => {
46+
/*
47+
If the issue has a "github.issue" inner object and a property "assignee",
48+
then fetch the RDS user information with GitHub username in "assignee"
49+
*/
50+
if (Object.keys(task).includes("github")) {
51+
if (Object.keys(task.github.issue).includes("assignee")) {
52+
return {
53+
...task,
54+
github: {
55+
...task.github,
56+
issue: {
57+
...task.github.issue,
58+
assigneeRdsInfo: await getRdsUserInfoByGitHubUsername(task.github.issue.assignee),
59+
},
60+
},
61+
};
62+
}
63+
}
64+
return task;
65+
});
66+
67+
const tasksWithRdsAssigneeInfo = await Promise.all(fetchTasksWithRdsAssigneeInfo);
4468
return res.json({
4569
message: "Tasks returned successfully!",
46-
tasks: allTasks.length > 0 ? allTasks : [],
70+
tasks: tasksWithRdsAssigneeInfo.length > 0 ? tasksWithRdsAssigneeInfo : [],
4771
});
4872
} catch (err) {
4973
logger.error(`Error while fetching tasks ${err}`);

controllers/users.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -449,12 +449,12 @@ const filterUsers = async (req, res) => {
449449
if (!Object.keys(req.query).length) {
450450
return res.boom.badRequest("filter for item not provided");
451451
}
452-
const allUsers = await userQuery.getUsersBasedOnFilter(req.query);
452+
const users = await userQuery.getUsersBasedOnFilter(req.query);
453453

454454
return res.json({
455-
message: "Users found successfully!",
456-
users: allUsers,
457-
count: allUsers.length,
455+
message: users.length ? "Users found successfully!" : "No users found",
456+
users,
457+
count: users.length,
458458
});
459459
} catch (error) {
460460
logger.error(`Error while fetching all users: ${error}`);

middlewares/validators/tasks.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,17 @@ const createTask = async (req, res, next) => {
4343
.optional(),
4444
isNoteworthy: joi.bool().optional(),
4545
isCollapsed: joi.bool().optional(),
46+
github: joi
47+
.object()
48+
.keys({
49+
issue: joi.object().keys({
50+
status: joi.string().optional(),
51+
assignee: joi.string().optional(),
52+
id: joi.number().optional(),
53+
closedAt: joi.string().optional(),
54+
}),
55+
})
56+
.optional(),
4657
});
4758

4859
try {

middlewares/validators/user.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,17 @@ async function validateUserQueryParams(req, res, next) {
172172
joi.array().items(joi.string().valid("IDLE", "OOO", "ACTIVE"))
173173
)
174174
.optional(),
175+
})
176+
.messages({
177+
"object.min": "Please provide at least one filter criteria",
175178
});
176179

177180
try {
178181
await schema.validateAsync(req.query);
179182
next();
180183
} catch (error) {
181184
logger.error(`Error validating query params : ${error}`);
182-
res.boom.badRequest(error.details[0].message);
185+
res.boom.badRequest(error);
183186
}
184187
}
185188

models/tasks.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,32 @@ const fetchTask = async (taskId) => {
105105
}
106106
};
107107

108+
/**
109+
* Fetch a task against the IssueId
110+
* @param issueId { number }: issueId which will be used to fetch the task
111+
* @return {Promise<taskData|Object>}
112+
*/
113+
const fetchTaskByIssueId = async (issueId) => {
114+
try {
115+
const task = await tasksModel.where("github.issue.id", "==", issueId).get();
116+
const [taskDoc] = task.docs;
117+
let updatedTaskData;
118+
if (taskDoc) {
119+
updatedTaskData = { id: taskDoc.id, ...taskDoc.data() };
120+
}
121+
const taskData = await fromFirestoreData(updatedTaskData);
122+
123+
if (taskData?.status) {
124+
taskData.status = TASK_STATUS[taskData.status.toUpperCase()];
125+
}
126+
127+
return taskData;
128+
} catch (err) {
129+
logger.error("Error retrieving task data from issue Id", err);
130+
throw err;
131+
}
132+
};
133+
108134
/**
109135
* Fetch assigned self task
110136
* @param taskId { string }: taskId which will be used to fetch the task
@@ -326,4 +352,5 @@ module.exports = {
326352
fetchSelfTask,
327353
fetchSkillLevelTask,
328354
overdueTasks,
355+
fetchTaskByIssueId,
329356
};

models/users.js

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ const addOrUpdate = async (userData, userId = null) => {
5959
userData.roles = { archived: false };
6060
userData.incompleteUserDetails = true;
6161
const userInfo = await userModel.add(userData);
62-
return { isNewUser: true, userId: userInfo.id };
62+
return { isNewUser: true, userId: userInfo.id, incompleteUserDetails: true };
6363
} catch (err) {
6464
logger.error("Error in adding or updating user", err);
6565
throw err;
@@ -224,7 +224,7 @@ const fetchUsers = async (usernames = []) => {
224224
* @param { Object }: Object with username and userId, any of the two can be used
225225
* @return {Promise<{userExists: boolean, user: <userModel>}|{userExists: boolean, user: <userModel>}>}
226226
*/
227-
const fetchUser = async ({ userId = null, username = null }) => {
227+
const fetchUser = async ({ userId = null, username = null, githubUsername = null }) => {
228228
try {
229229
let userData, id;
230230
if (username) {
@@ -238,6 +238,12 @@ const fetchUser = async ({ userId = null, username = null }) => {
238238
const user = await userModel.doc(userId).get();
239239
id = userId;
240240
userData = user.data();
241+
} else if (githubUsername) {
242+
const user = await userModel.where("github_id", "==", githubUsername).limit(1).get();
243+
user.forEach((doc) => {
244+
id = doc.id;
245+
userData = doc.data();
246+
});
241247
}
242248
return {
243249
userExists: !!userData,
@@ -335,6 +341,16 @@ const fetchUserSkills = async (id) => {
335341
}
336342
};
337343

344+
const getRdsUserInfoByGitHubUsername = async (githubUsername) => {
345+
const { user } = await fetchUser({ githubUsername });
346+
347+
return {
348+
firstName: user.first_name ?? "",
349+
lastName: user.last_name ?? "",
350+
username: user.username ?? "",
351+
};
352+
};
353+
338354
/**
339355
* Fetches user data based on the filter query
340356
*
@@ -379,18 +395,24 @@ const getUsersBasedOnFilter = async (query) => {
379395
let finalItems = [];
380396

381397
if (doesTagQueryExist && doesStateQueryExist) {
382-
const stateItemIds = new Set(stateItems.map((item) => item.userId));
383-
finalItems = tagItems.filter((item) => stateItemIds.has(item.itemId)).map((item) => item.itemId);
398+
if (stateItems.length && tagItems.length) {
399+
const stateItemIds = new Set(stateItems.map((item) => item.userId));
400+
finalItems = tagItems.filter((item) => stateItemIds.has(item.itemId)).map((item) => item.itemId);
401+
}
384402
} else if (doesStateQueryExist) {
385403
finalItems = stateItems.map((item) => item.userId);
386404
} else if (doesTagQueryExist) {
387405
finalItems = tagItems.map((item) => item.itemId);
388406
}
389407

390-
finalItems = [...new Set(finalItems)];
391-
const userRefs = finalItems.map((itemId) => userModel.doc(itemId));
392-
const userDocs = (await firestore.getAll(...userRefs)).map((doc) => ({ id: doc.id, ...doc.data() }));
393-
return userDocs;
408+
if (finalItems.length) {
409+
finalItems = [...new Set(finalItems)];
410+
const userRefs = finalItems.map((itemId) => userModel.doc(itemId));
411+
const userDocs = (await firestore.getAll(...userRefs)).map((doc) => ({ id: doc.id, ...doc.data() }));
412+
const filteredUserDocs = userDocs.filter((doc) => !doc.roles?.archived);
413+
return filteredUserDocs;
414+
}
415+
return [];
394416
};
395417

396418
module.exports = {
@@ -405,6 +427,7 @@ module.exports = {
405427
getJoinData,
406428
getSuggestedUsers,
407429
fetchUserSkills,
430+
getRdsUserInfoByGitHubUsername,
408431
fetchUsers,
409432
getUsersBasedOnFilter,
410433
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"eslint-plugin-promise": "^6.0.0",
5151
"eslint-plugin-security": "^1.4.0",
5252
"eslint-plugin-standard": "^4.1.0",
53-
"firebase-tools": "^11.0.0",
53+
"firebase-tools": "^11.28.0",
5454
"mocha": "^10.0.0",
5555
"nock": "^13.0.11",
5656
"nodemon": "^2.0.7",

routes/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ app.use("/levels", require("./levels.js"));
2424
app.use("/items", require("./items.js"));
2525
app.use("/cache", require("./cloudflareCache.js"));
2626
app.use("/external-accounts", require("./external-accounts.js"));
27+
app.use("/issues", require("./issues.js"));
2728

2829
module.exports = app;

routes/issues.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const express = require("express");
2+
const issues = require("../controllers/issues");
3+
const router = express.Router();
4+
5+
router.get("/", issues.getIssues);
6+
router.post("/updates", issues.issueUpdates);
7+
8+
module.exports = router;

0 commit comments

Comments
 (0)