fix(bot): refresh roll-call date changes
This commit is contained in:
parent
055db8d0f0
commit
f6d42399d4
2 changed files with 26 additions and 17 deletions
|
|
@ -66,7 +66,10 @@ export const handleButtonInteraction = async (
|
||||||
await setMissionAttendance(payload, { missionId, userId: user.id, response });
|
await setMissionAttendance(payload, { missionId, userId: user.id, response });
|
||||||
|
|
||||||
const summary = await getMissionAttendance(payload, missionId);
|
const summary = await getMissionAttendance(payload, missionId);
|
||||||
const hash = attendanceHash(await fetchAttendanceDocs(payload, missionId));
|
const hash = attendanceHash(
|
||||||
|
await fetchAttendanceDocs(payload, missionId),
|
||||||
|
mission.classification.startDateTime,
|
||||||
|
);
|
||||||
|
|
||||||
await interaction.message.edit({
|
await interaction.message.edit({
|
||||||
embeds: [renderAttendanceEmbed(mission, summary)],
|
embeds: [renderAttendanceEmbed(mission, summary)],
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,6 @@ const missionTitle = (mission: Mission): string =>
|
||||||
|
|
||||||
const memberName = (user: User): string => user.displayName ?? user.username;
|
const memberName = (user: User): string => user.displayName ?? user.username;
|
||||||
|
|
||||||
const isStillFuture = (mission: Mission): boolean => {
|
|
||||||
const start = new Date(mission.classification.startDateTime).getTime();
|
|
||||||
const end = start + mission.classification.estimatedDuration * 60_000;
|
|
||||||
return end > Date.now();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Roll call is sent once: as soon as the mission is Ready, or three days
|
* Roll call is sent once: as soon as the mission is Ready, or three days
|
||||||
* before a Scheduled mission, whichever happens first.
|
* before a Scheduled mission, whichever happens first.
|
||||||
|
|
@ -59,17 +53,18 @@ const operationTypeLabel = (type: Mission["operationType"]): string => {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deterministic fingerprint of the attendance state so reconcile can skip
|
* Deterministic fingerprint of the attendance state and scheduled start time
|
||||||
* embeds that haven't changed (anti-loop).
|
* so reconcile can skip embeds that haven't changed (anti-loop), while still
|
||||||
|
* detecting mission date changes.
|
||||||
*/
|
*/
|
||||||
export const attendanceHash = (docs: MissionAttendance[]): string => {
|
export const attendanceHash = (docs: MissionAttendance[], startDateTime?: string): string => {
|
||||||
const rows = docs
|
const rows = docs
|
||||||
.map((doc) => {
|
.map((doc) => {
|
||||||
const userId = typeof doc.user === "object" ? doc.user.id : doc.user;
|
const userId = typeof doc.user === "object" ? doc.user.id : doc.user;
|
||||||
return [userId, doc.response] as const;
|
return [userId, doc.response] as const;
|
||||||
})
|
})
|
||||||
.sort((a, b) => a[0] - b[0] || a[1].localeCompare(b[1]));
|
.sort((a, b) => a[0] - b[0] || a[1].localeCompare(b[1]));
|
||||||
return JSON.stringify(rows);
|
return JSON.stringify({ rows, startDateTime: startDateTime ?? null });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchAttendanceDocs = async (
|
export const fetchAttendanceDocs = async (
|
||||||
|
|
@ -97,17 +92,23 @@ export const getEligibleMissions = async (payload: Payload): Promise<Mission[]>
|
||||||
limit: MAX_ELIGIBLE_MISSIONS,
|
limit: MAX_ELIGIBLE_MISSIONS,
|
||||||
where: {
|
where: {
|
||||||
and: [
|
and: [
|
||||||
{ "classification.startDateTime": { greater_than: new Date().toISOString() } },
|
|
||||||
{ "ownershipAndStatus.visibility": { equals: "unit" } },
|
{ "ownershipAndStatus.visibility": { equals: "unit" } },
|
||||||
{
|
{
|
||||||
or: [
|
or: [
|
||||||
{ "ownershipAndStatus.status": { equals: "Ready" } },
|
|
||||||
{
|
{
|
||||||
and: [
|
and: [
|
||||||
|
{ "classification.startDateTime": { greater_than: new Date().toISOString() } },
|
||||||
|
{ "ownershipAndStatus.status": { equals: "Ready" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
and: [
|
||||||
|
{ "classification.startDateTime": { greater_than: new Date().toISOString() } },
|
||||||
{ "ownershipAndStatus.status": { equals: "Scheduled" } },
|
{ "ownershipAndStatus.status": { equals: "Scheduled" } },
|
||||||
{ "classification.startDateTime": { less_than_equal: rollCallCutoff } },
|
{ "classification.startDateTime": { less_than_equal: rollCallCutoff } },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{ discordMessageId: { exists: true } },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -200,7 +201,10 @@ const postEmbed = async (
|
||||||
mission: Mission,
|
mission: Mission,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const summary = await getMissionAttendance(payload, mission.id);
|
const summary = await getMissionAttendance(payload, mission.id);
|
||||||
const hash = attendanceHash(await fetchAttendanceDocs(payload, mission.id));
|
const hash = attendanceHash(
|
||||||
|
await fetchAttendanceDocs(payload, mission.id),
|
||||||
|
mission.classification.startDateTime,
|
||||||
|
);
|
||||||
const channel = await getOpsChannel(client, opsChannelId);
|
const channel = await getOpsChannel(client, opsChannelId);
|
||||||
const message = await channel.send({
|
const message = await channel.send({
|
||||||
embeds: [renderAttendanceEmbed(mission, summary)],
|
embeds: [renderAttendanceEmbed(mission, summary)],
|
||||||
|
|
@ -257,7 +261,10 @@ const manageMission = async (
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hash = attendanceHash(await fetchAttendanceDocs(payload, mission.id));
|
const hash = attendanceHash(
|
||||||
|
await fetchAttendanceDocs(payload, mission.id),
|
||||||
|
mission.classification.startDateTime,
|
||||||
|
);
|
||||||
if (hash === mission.discordAttendanceHash) {
|
if (hash === mission.discordAttendanceHash) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -297,7 +304,6 @@ export const reconcileMission = async (
|
||||||
if (
|
if (
|
||||||
mission.ownershipAndStatus.visibility !== "unit" ||
|
mission.ownershipAndStatus.visibility !== "unit" ||
|
||||||
!["Ready", "Scheduled"].includes(mission.ownershipAndStatus.status) ||
|
!["Ready", "Scheduled"].includes(mission.ownershipAndStatus.status) ||
|
||||||
!isStillFuture(mission) ||
|
|
||||||
(!isRollCallDue(mission) && !mission.discordMessageId)
|
(!isRollCallDue(mission) && !mission.discordMessageId)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -353,7 +359,7 @@ export const reconcile = async (client: Client, payload: Payload): Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
const eligible = (await getEligibleMissions(payload)).filter(
|
const eligible = (await getEligibleMissions(payload)).filter(
|
||||||
(mission) => isStillFuture(mission) && (isRollCallDue(mission) || !!mission.discordMessageId),
|
(mission) => isRollCallDue(mission) || !!mission.discordMessageId,
|
||||||
);
|
);
|
||||||
for (const mission of eligible) {
|
for (const mission of eligible) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue