Skip to content

Commit f2d3bc7

Browse files
authored
Dev to Main Sync (#2513)
* chore: remove lastOooUntil migration artifacts and code (#2507) - drop updateLastOooUntil controller now that prod backfill is done - delete runLastOooUntilMigration helpers from userStatus.js - remove POST /users/migration/update-last-ooo-until route - strip integration tests that exercised the migration endpoint * fix(missed-updates): compute gap window using working days (#2511) * fix(missed-updates): compute gap window using working days - import convertTimestampToUTCStartOrEndOfDay so the window uses UTC day bounds - respect caller-provided exclusions instead of hard-coding Sunday into the set - step backwards through calendar days, skipping excluded ones, and bail when none remain - add regression coverage (plus fixture) for Sunday-only gaps and the Sunday-working-day case * fix(missed-updates): align working-day window counting and tests - compute the missed-updates window from consecutive working days without overshooting - adjust the Sunday-gap fixture to actual Saturday/Sunday/Monday dates - ensure regression tests cover both excluded Sunday and Sunday-as-working-day scenarios * fix: failing tests
2 parents caeb2fe + 6b81a17 commit f2d3bc7

File tree

3 files changed

+160
-12
lines changed

3 files changed

+160
-12
lines changed

models/discordactions.js

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ const discordMissedUpdatesRoleId = config.get("discordMissedUpdatesRoleId");
2222
const userStatusModel = firestore.collection("usersStatus");
2323
const usersUtils = require("../utils/users");
2424
const { getUsersBasedOnFilter, fetchUser } = require("./users");
25-
const { convertDaysToMilliseconds, convertMillisToSeconds } = require("../utils/time");
25+
const {
26+
convertDaysToMilliseconds,
27+
convertMillisToSeconds,
28+
convertTimestampToUTCStartOrEndOfDay,
29+
} = require("../utils/time");
2630
const { chunks } = require("../utils/array");
2731
const tasksModel = firestore.collection("tasks");
2832
const { FIRESTORE_IN_CLAUSE_SIZE } = require("../constants/users");
@@ -965,23 +969,71 @@ const getMissedProgressUpdatesUsers = async (options = {}) => {
965969
const discordUsersPromise = discordService.getDiscordMembers();
966970
const missedUpdatesRoleId = discordMissedUpdatesRoleId;
967971

968-
let gapWindowStart = Date.now() - convertDaysToMilliseconds(dateGap);
969-
const gapWindowEnd = Date.now();
970-
excludedDates.forEach((timestamp) => {
971-
if (timestamp > gapWindowStart && timestamp < gapWindowEnd) {
972-
gapWindowStart -= convertDaysToMilliseconds(1);
972+
const normalizedExcludedWeekdays = new Set(
973+
excludedDays.map((day) => Number(day)).filter((day) => Number.isInteger(day) && day >= 0 && day <= 6)
974+
);
975+
976+
if (normalizedExcludedWeekdays.size === 7) {
977+
return { usersToAddRole: [], ...stats };
978+
}
979+
980+
const normalizedExcludedDates = excludedDates.reduce((set, timestamp) => {
981+
const normalizedTimestamp = convertTimestampToUTCStartOrEndOfDay(Number(timestamp));
982+
if (normalizedTimestamp !== null) {
983+
set.add(normalizedTimestamp);
973984
}
974-
});
985+
return set;
986+
}, new Set());
987+
988+
const currentTimestamp = Date.now();
989+
const gapWindowEnd = convertTimestampToUTCStartOrEndOfDay(currentTimestamp, true);
990+
let gapWindowStart = convertTimestampToUTCStartOrEndOfDay(currentTimestamp, false);
991+
let remainingWorkingDays = Number.isInteger(dateGap) && dateGap > 0 ? dateGap : 0;
992+
993+
while (remainingWorkingDays > 0 && gapWindowStart !== null) {
994+
const dayOfWeek = new Date(gapWindowStart).getUTCDay();
995+
const isExcludedDay = normalizedExcludedWeekdays.has(dayOfWeek);
996+
const isExcludedDate = normalizedExcludedDates.has(gapWindowStart);
997+
998+
if (!isExcludedDay && !isExcludedDate) {
999+
remainingWorkingDays--;
1000+
}
1001+
1002+
const previousDayTimestamp = gapWindowStart - convertDaysToMilliseconds(1);
1003+
const previousDayStart = convertTimestampToUTCStartOrEndOfDay(previousDayTimestamp, false);
9751004

976-
if (excludedDays.length === 7) {
1005+
if (remainingWorkingDays === 0) {
1006+
gapWindowStart = previousDayStart ?? gapWindowStart;
1007+
break;
1008+
}
1009+
1010+
if (previousDayStart === null) {
1011+
gapWindowStart = null;
1012+
break;
1013+
}
1014+
1015+
gapWindowStart = previousDayStart;
1016+
}
1017+
1018+
if (gapWindowStart === null || remainingWorkingDays > 0) {
9771019
return { usersToAddRole: [], ...stats };
9781020
}
9791021

980-
for (let i = gapWindowEnd; i >= gapWindowStart; i -= convertDaysToMilliseconds(1)) {
981-
const day = new Date(i).getDay();
982-
if (excludedDays.includes(day)) {
983-
gapWindowStart -= convertDaysToMilliseconds(1);
1022+
while (gapWindowStart !== null) {
1023+
const dayOfWeek = new Date(gapWindowStart).getUTCDay();
1024+
const isExcludedDay = normalizedExcludedWeekdays.has(dayOfWeek);
1025+
const isExcludedDate = normalizedExcludedDates.has(gapWindowStart);
1026+
1027+
if (!isExcludedDay && !isExcludedDate) {
1028+
break;
9841029
}
1030+
1031+
const previousDayTimestamp = gapWindowStart - convertDaysToMilliseconds(1);
1032+
gapWindowStart = convertTimestampToUTCStartOrEndOfDay(previousDayTimestamp, false);
1033+
}
1034+
1035+
if (gapWindowStart === null) {
1036+
return { usersToAddRole: [], ...stats };
9851037
}
9861038

9871039
let taskQuery = buildTasksQueryForMissedUpdates(size);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const userData = require("../user/user")();
2+
const tasksData = require("../tasks/tasks")();
3+
const { TASK_STATUS } = require("../../../constants/tasks");
4+
5+
module.exports = () => {
6+
const developer = {
7+
...userData[0],
8+
username: "sunday-gap-user",
9+
discordId: "sunday-gap-discord",
10+
roles: { ...(userData[0].roles || {}), archived: false, in_discord: true },
11+
};
12+
13+
const task = {
14+
...tasksData[0],
15+
status: TASK_STATUS.IN_PROGRESS,
16+
startedOn: Math.floor(Date.UTC(2025, 2, 31) / 1000),
17+
endsOn: Math.floor(Date.UTC(2025, 4, 30) / 1000),
18+
};
19+
20+
return {
21+
evaluationTime: Date.UTC(2025, 3, 14, 9, 0, 0, 0),
22+
saturdayProgressTimestamp: Date.UTC(2025, 3, 12, 0, 0, 0, 0),
23+
developer,
24+
task,
25+
};
26+
};

test/unit/models/discordactions.test.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const chai = require("chai");
22
const expect = chai.expect;
33
const sinon = require("sinon");
4+
const config = require("config");
45
const firestore = require("../../../utils/firestore");
56
const photoVerificationModel = firestore.collection("photo-verification");
67
const discordRoleModel = firestore.collection("discord-roles");
@@ -66,6 +67,7 @@ const { generateUserStatusData } = require("../../fixtures/userStatus/userStatus
6667
const { userState } = require("../../../constants/userStatus");
6768
const { REQUEST_TYPE, REQUEST_STATE } = require("../../../constants/requests");
6869
const { createRequest } = require("../../../models/requests");
70+
const getSundayGapFixture = require("../../fixtures/missedUpdates/sundayGap");
6971

7072
chai.should();
7173

@@ -1441,4 +1443,72 @@ describe("discordactions", function () {
14411443
expect(users[1].id).to.equal(userId1);
14421444
});
14431445
});
1446+
1447+
describe("getMissedProgressUpdatesUsers working days window", function () {
1448+
let dateNowStub;
1449+
let developerDiscordId;
1450+
let developerUserId;
1451+
let developerTaskId;
1452+
let sundayFixture;
1453+
1454+
beforeEach(async function () {
1455+
await cleanDb();
1456+
1457+
sundayFixture = getSundayGapFixture();
1458+
dateNowStub = sinon.stub(Date, "now").returns(sundayFixture.evaluationTime);
1459+
1460+
developerDiscordId = sundayFixture.developer.discordId;
1461+
developerUserId = await addUser(sundayFixture.developer);
1462+
1463+
await userStatusModel.updateUserStatus(developerUserId, userStatusData.activeStatus);
1464+
1465+
const taskPayload = {
1466+
...sundayFixture.task,
1467+
assignee: developerUserId,
1468+
};
1469+
1470+
const taskDocRef = await tasksModel.add(taskPayload);
1471+
developerTaskId = taskDocRef.id;
1472+
1473+
const saturdayProgress = stubbedModelTaskProgressData(
1474+
developerUserId,
1475+
developerTaskId,
1476+
sundayFixture.saturdayProgressTimestamp,
1477+
sundayFixture.saturdayProgressTimestamp
1478+
);
1479+
1480+
await firestore.collection("progresses").add(saturdayProgress);
1481+
1482+
sinon.stub(discordService, "getDiscordMembers").resolves([
1483+
{
1484+
user: { id: developerDiscordId },
1485+
roles: [config.get("discordDeveloperRoleId")],
1486+
},
1487+
]);
1488+
});
1489+
1490+
afterEach(async function () {
1491+
if (dateNowStub) {
1492+
dateNowStub.restore();
1493+
}
1494+
sinon.restore();
1495+
await cleanDb();
1496+
});
1497+
1498+
it("should not flag users when the only gap falls on Sunday", async function () {
1499+
const result = await getMissedProgressUpdatesUsers();
1500+
1501+
expect(result).to.be.an("object");
1502+
expect(result.usersToAddRole).to.not.include(developerDiscordId);
1503+
expect(result.missedUpdatesTasks).to.equal(0);
1504+
});
1505+
1506+
it("should flag users when Sunday is treated as a working day", async function () {
1507+
const result = await getMissedProgressUpdatesUsers({ excludedDays: [], dateGap: 1 });
1508+
1509+
expect(result).to.be.an("object");
1510+
expect(result.usersToAddRole).to.include(developerDiscordId);
1511+
expect(result.missedUpdatesTasks).to.equal(1);
1512+
});
1513+
});
14441514
});

0 commit comments

Comments
 (0)