perf: build case-issue map once in stale-case-reminder; add idempotency TODO
- Hoist issues.list() loop outside stale-cases loop — reduces RPCs from O(cases × companies × issues) to O(companies × issues) - Add TODO comment for requestId-based idempotency guard in onWebhook Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -722,36 +722,37 @@ const plugin = definePlugin({
|
|||||||
total: number;
|
total: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
let reminded = 0;
|
// Build case→issue map once (O(companies × issues)) to avoid N×M RPCs per stale case
|
||||||
const companies = await ctx.companies.list();
|
const companies = await ctx.companies.list();
|
||||||
for (const staleCase of data.cases) {
|
const caseIssueMap = new Map<string, { issueId: string; companyId: string }>();
|
||||||
for (const company of companies) {
|
for (const company of companies) {
|
||||||
const issues = await ctx.issues.list({ companyId: company.id });
|
const issues = await ctx.issues.list({ companyId: company.id });
|
||||||
let linkedIssueId: string | null = null;
|
for (const issue of issues) {
|
||||||
for (const issue of issues) {
|
const linkedCase = await ctx.state.get({
|
||||||
const linkedCase = await ctx.state.get({
|
scopeKind: "issue",
|
||||||
scopeKind: "issue",
|
scopeId: issue.id,
|
||||||
scopeId: issue.id,
|
stateKey: "legal-case-number",
|
||||||
stateKey: "legal-case-number",
|
});
|
||||||
});
|
if (linkedCase && typeof linkedCase === "string") {
|
||||||
if (linkedCase === staleCase.case_number) {
|
caseIssueMap.set(linkedCase, { issueId: issue.id, companyId: company.id });
|
||||||
linkedIssueId = issue.id;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!linkedIssueId) continue;
|
|
||||||
|
|
||||||
await ctx.issues.createComment(
|
|
||||||
linkedIssueId,
|
|
||||||
`⚠️ **תיק תקוע ${staleCase.case_number}** — ${staleCase.days_stale} ימים ללא עדכון (סטטוס: ${staleCase.status}). האם נדרשת פעולה?`,
|
|
||||||
company.id,
|
|
||||||
);
|
|
||||||
reminded++;
|
|
||||||
ctx.logger.info(`stale-case-reminder: reminded case ${staleCase.case_number} (${staleCase.days_stale}d)`);
|
|
||||||
break; // Found the company, move to next case
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let reminded = 0;
|
||||||
|
for (const staleCase of data.cases) {
|
||||||
|
const linked = caseIssueMap.get(staleCase.case_number);
|
||||||
|
if (!linked) continue;
|
||||||
|
|
||||||
|
await ctx.issues.createComment(
|
||||||
|
linked.issueId,
|
||||||
|
`⚠️ **תיק תקוע ${staleCase.case_number}** — ${staleCase.days_stale} ימים ללא עדכון (סטטוס: ${staleCase.status}). האם נדרשת פעולה?`,
|
||||||
|
linked.companyId,
|
||||||
|
);
|
||||||
|
reminded++;
|
||||||
|
ctx.logger.info(`stale-case-reminder: reminded case ${staleCase.case_number} (${staleCase.days_stale}d)`);
|
||||||
|
}
|
||||||
|
|
||||||
ctx.logger.info(`stale-case-reminder: done. ${reminded}/${data.total} cases reminded`);
|
ctx.logger.info(`stale-case-reminder: done. ${reminded}/${data.total} cases reminded`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -801,6 +802,9 @@ const plugin = definePlugin({
|
|||||||
async onWebhook(input: PluginWebhookInput): Promise<void> {
|
async onWebhook(input: PluginWebhookInput): Promise<void> {
|
||||||
if (!pluginCtx) return; // not yet initialized
|
if (!pluginCtx) return; // not yet initialized
|
||||||
|
|
||||||
|
// TODO: add idempotency guard using input.requestId to prevent duplicate
|
||||||
|
// comments on rapid retries (store requestId in plugin state with ~60s TTL)
|
||||||
|
|
||||||
const { endpointKey, parsedBody } = input;
|
const { endpointKey, parsedBody } = input;
|
||||||
|
|
||||||
if (endpointKey !== "case-status") return;
|
if (endpointKey !== "case-status") return;
|
||||||
|
|||||||
Reference in New Issue
Block a user