Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
13 changes: 12 additions & 1 deletion controllers/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const {
const { addLog } = require("../models/logs");
const { getUserStatus } = require("../models/userStatus");
const config = require("config");
const { generateUniqueUsername } = require("../services/users");
const { generateUniqueUsername, getUsersWithIncompleteTasks } = require("../services/users");
const discordDeveloperRoleId = config.get("discordDeveloperRoleId");

const verifyUser = async (req, res) => {
Expand Down Expand Up @@ -1029,6 +1029,16 @@ const updateUsernames = async (req, res) => {
}
};

const getUsersWithAbandonedTasks = async (req, res) => {
try {
const data = await getUsersWithIncompleteTasks();
return res.status(200).json({ message: "Users with abandoned tasks fetched successfully", data });
} catch (error) {
logger.error("Error in getting user who abandoned tasks:", error);
return res.boom.badImplementation(INTERNAL_SERVER_ERROR);
}
};

module.exports = {
verifyUser,
generateChaincode,
Expand Down Expand Up @@ -1061,4 +1071,5 @@ module.exports = {
isDeveloper,
getIdentityStats,
updateUsernames,
getUsersWithAbandonedTasks,
};
15 changes: 15 additions & 0 deletions models/tasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,20 @@ const markUnDoneTasksOfArchivedUsersBacklog = async (users) => {
}
};

const fetchIncompleteTaskForUser = async (user) => {
const COMPLETED_STATUSES = [DONE, COMPLETED];
try {
const incompleteTaskForUser = await tasksModel
.where("assigneeId", "==", user.id)
.where("status", "not-in", COMPLETED_STATUSES)
.get();
return incompleteTaskForUser;
} catch (error) {
logger.error("Error when fetching incomplete tasks:", error);
throw error;
}
};

module.exports = {
updateTask,
fetchTasks,
Expand All @@ -720,4 +734,5 @@ module.exports = {
updateTaskStatus,
updateOrphanTasksStatus,
markUnDoneTasksOfArchivedUsersBacklog,
fetchIncompleteTaskForUser,
};
63 changes: 61 additions & 2 deletions models/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ const firestore = require("../utils/firestore");
const { fetchWallet, createWallet } = require("../models/wallets");
const { updateUserStatus } = require("../models/userStatus");
const { arraysHaveCommonItem, chunks } = require("../utils/array");
const { archiveUsers } = require("../services/users");
const { ALLOWED_FILTER_PARAMS, FIRESTORE_IN_CLAUSE_SIZE } = require("../constants/users");
const {
ALLOWED_FILTER_PARAMS,
FIRESTORE_IN_CLAUSE_SIZE,
USERS_PATCH_HANDLER_SUCCESS_MESSAGES,
USERS_PATCH_HANDLER_ERROR_MESSAGES,
} = require("../constants/users");
const { DOCUMENT_WRITE_SIZE } = require("../constants/constants");
const { userState } = require("../constants/userStatus");
const { BATCH_SIZE_IN_CLAUSE } = require("../constants/firebase");
Expand All @@ -27,6 +31,46 @@ const { formatUsername } = require("../utils/username");
const { logType } = require("../constants/logs");
const { addLog } = require("../services/logService");

const archiveUsers = async (usersData) => {
const batch = firestore.batch();
const usersBatch = [];
const summary = {
totalUsersArchived: 0,
totalOperationsFailed: 0,
updatedUserDetails: [],
failedUserDetails: [],
};

usersData.forEach((user) => {
const { id, first_name: firstName, last_name: lastName } = user;
const updatedUserData = {
...user,
roles: {
...user.roles,
archived: true,
},
updated_at: Date.now(),
};
batch.update(userModel.doc(id), updatedUserData);
usersBatch.push({ id, firstName, lastName });
});

try {
await batch.commit();
summary.totalUsersArchived += usersData.length;
summary.updatedUserDetails = [...usersBatch];
return {
message: USERS_PATCH_HANDLER_SUCCESS_MESSAGES.ARCHIVE_USERS.SUCCESSFULLY_COMPLETED_BATCH_UPDATES,
...summary,
};
} catch (err) {
logger.error("Firebase batch Operation Failed!");
summary.totalOperationsFailed += usersData.length;
summary.failedUserDetails = [...usersBatch];
return { message: USERS_PATCH_HANDLER_ERROR_MESSAGES.ARCHIVE_USERS.BATCH_DATA_UPDATED_FAILED, ...summary };
}
};

/**
* Adds or updates the user data
*
Expand Down Expand Up @@ -1030,7 +1074,21 @@ const updateUsersWithNewUsernames = async () => {
}
};

const fetchUsersNotInDiscordServer = async () => {
try {
const usersNotInDiscordServer = await userModel
.where("roles.archived", "==", true)
.where("roles.in_discord", "==", false)
.get();
return usersNotInDiscordServer;
} catch (error) {
logger.error(`Error in getting users who are not in discord server: ${error}`);
throw error;
}
};

module.exports = {
archiveUsers,
addOrUpdate,
fetchPaginatedUsers,
fetchUser,
Expand Down Expand Up @@ -1059,4 +1117,5 @@ module.exports = {
fetchUserForKeyValue,
getNonNickNameSyncedUsers,
updateUsersWithNewUsernames,
fetchUsersNotInDiscordServer,
};
2 changes: 2 additions & 0 deletions routes/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const ROLES = require("../constants/roles");
const { Services } = require("../constants/bot");
const authenticateProfile = require("../middlewares/authenticateProfile");

router.get("/departed-users", users.getUsersWithAbandonedTasks);
router.post("/", authorizeAndAuthenticate([ROLES.SUPERUSER], [Services.CRON_JOB_HANDLER]), users.markUnverified);
router.post("/update-in-discord", authenticate, authorizeRoles([SUPERUSER]), users.setInDiscordScript);
router.post("/verify", authenticate, users.verifyUser);
Expand Down Expand Up @@ -67,6 +68,7 @@ router.patch("/profileURL", authenticate, userValidator.updateProfileURL, users.
router.patch("/rejectDiff", authenticate, authorizeRoles([SUPERUSER]), users.rejectProfileDiff);
router.patch("/:userId", authenticate, authorizeRoles([SUPERUSER]), users.updateUser);
router.get("/suggestedUsers/:skillId", authenticate, authorizeRoles([SUPERUSER]), users.getSuggestedUsers);

module.exports = router;
router.post("/batch-username-update", authenticate, authorizeRoles([SUPERUSER]), users.updateUsernames);
module.exports = router;
59 changes: 22 additions & 37 deletions services/users.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,29 @@
const { USERS_PATCH_HANDLER_SUCCESS_MESSAGES, USERS_PATCH_HANDLER_ERROR_MESSAGES } = require("../constants/users");
const firestore = require("../utils/firestore");
const { formatUsername } = require("../utils/username");
const userModel = firestore.collection("users");
const archiveUsers = async (usersData) => {
const batch = firestore.batch();
const usersBatch = [];
const summary = {
totalUsersArchived: 0,
totalOperationsFailed: 0,
updatedUserDetails: [],
failedUserDetails: [],
};

usersData.forEach((user) => {
const { id, first_name: firstName, last_name: lastName } = user;
const updatedUserData = {
...user,
roles: {
...user.roles,
archived: true,
},
updated_at: Date.now(),
};
batch.update(userModel.doc(id), updatedUserData);
usersBatch.push({ id, firstName, lastName });
});
const { fetchUsersNotInDiscordServer } = require("../models/users");
const { fetchIncompleteTaskForUser } = require("../models/tasks");

const getUsersWithIncompleteTasks = async () => {
try {
await batch.commit();
summary.totalUsersArchived += usersData.length;
summary.updatedUserDetails = [...usersBatch];
return {
message: USERS_PATCH_HANDLER_SUCCESS_MESSAGES.ARCHIVE_USERS.SUCCESSFULLY_COMPLETED_BATCH_UPDATES,
...summary,
};
} catch (err) {
logger.error("Firebase batch Operation Failed!");
summary.totalOperationsFailed += usersData.length;
summary.failedUserDetails = [...usersBatch];
return { message: USERS_PATCH_HANDLER_ERROR_MESSAGES.ARCHIVE_USERS.BATCH_DATA_UPDATED_FAILED, ...summary };
const eligibleUsersWithTasks = [];

const userSnapshot = await fetchUsersNotInDiscordServer();

for (const userDoc of userSnapshot.docs) {
const user = userDoc.data();

// Check if the user has any tasks with status not in [Done, Complete]
const abandonedTasksQuerySnapshot = await fetchIncompleteTaskForUser(user);

if (!abandonedTasksQuerySnapshot.empty) {
eligibleUsersWithTasks.push(user);
}
}
return eligibleUsersWithTasks;
} catch (error) {
logger.error(`Error in getting users who abandoned tasks: ${error}`);
throw error;
}
};

Expand All @@ -63,6 +48,6 @@ const generateUniqueUsername = async (firstName, lastName) => {
};

module.exports = {
archiveUsers,
generateUniqueUsername,
getUsersWithIncompleteTasks,
};
154 changes: 154 additions & 0 deletions test/fixtures/abandoned-tasks/departed-users.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
const usersData = [
{
id: "user1_id",
discordId: "123456789",
github_id: "github_user1",
username: "archived_user1",
first_name: "Archived",
last_name: "User One",
linkedin_id: "archived_user1",
github_display_name: "archived-user-1",
phone: "1234567890",
email: "[email protected]",
roles: {
archived: true,
in_discord: false,
},
discordJoinedAt: "2024-01-01T00:00:00.000Z",
picture: {
publicId: "profile/user1",
url: "https://example.com/user1.jpg",
},
},
{
id: "user2_id",
discordId: "987654321",
github_id: "github_user2",
username: "archived_user2",
first_name: "Archived",
last_name: "User Two",
linkedin_id: "archived_user2",
github_display_name: "archived-user-2",
phone: "0987654321",
email: "[email protected]",
roles: {
archived: true,
in_discord: false,
},
discordJoinedAt: "2024-01-02T00:00:00.000Z",
picture: {
publicId: "profile/user2",
url: "https://example.com/user2.jpg",
},
},
{
id: "user3_id",
discordId: "555555555",
github_id: "github_user3",
username: "active_user",
first_name: "Active",
last_name: "User",
linkedin_id: "active_user",
github_display_name: "active-user",
phone: "5555555555",
email: "[email protected]",
roles: {
archived: false,
in_discord: true,
},
discordJoinedAt: "2024-01-03T00:00:00.000Z",
picture: {
publicId: "profile/user3",
url: "https://example.com/user3.jpg",
},
},
];

const tasksData = [
{
id: "task1_id",
title: "Abandoned Task 1",
type: "feature",
status: "IN_PROGRESS",
priority: "HIGH",
percentCompleted: 50,
createdAt: 1727027666,
updatedAt: 1727027999,
startedOn: 1727027777,
endsOn: 1731542400,
assignee: "archived_user1",
assigneeId: "user1_id",
github: {
issue: {
html_url: "https://github.com/org/repo/issues/1",
url: "https://api.github.com/repos/org/repo/issues/1",
},
},
dependsOn: [],
},
{
id: "task2_id",
title: "Abandoned Task 2",
type: "bug",
status: "BLOCKED",
priority: "MEDIUM",
percentCompleted: 30,
createdAt: 1727027666,
updatedAt: 1727027999,
startedOn: 1727027777,
endsOn: 1731542400,
assignee: "archived_user2",
assigneeId: "user2_id",
github: {
issue: {
html_url: "https://github.com/org/repo/issues/2",
url: "https://api.github.com/repos/org/repo/issues/2",
},
},
dependsOn: [],
},
{
id: "task3_id",
title: "Completed Archived Task",
type: "feature",
status: "DONE",
priority: "LOW",
percentCompleted: 100,
createdAt: 1727027666,
updatedAt: 1727027999,
startedOn: 1727027777,
endsOn: 1731542400,
assignee: "archived_user1",
assigneeId: "user1_id",
github: {
issue: {
html_url: "https://github.com/org/repo/issues/3",
url: "https://api.github.com/repos/org/repo/issues/3",
},
},
dependsOn: [],
},
{
id: "task4_id",
title: "Active User Task",
type: "feature",
status: "IN_PROGRESS",
priority: "HIGH",
percentCompleted: 75,
createdAt: 1727027666,
updatedAt: 1727027999,
startedOn: 1727027777,
endsOn: 1731542400,
assignee: "active_user",
assigneeId: "user3_id",
github: {
issue: {
html_url: "https://github.com/org/repo/issues/4",
url: "https://api.github.com/repos/org/repo/issues/4",
},
},
dependsOn: [],
},
];

module.exports = { usersData, tasksData };
Loading