Debounce dashboard refresh dispatches - #176
Draft
trask wants to merge 2 commits into
Draft
Conversation
The webhook now records each accepted event in a Netlify Blobs queue, and a scheduled function dispatches one workflow run per pull request once that pull request has been quiet for 45 seconds. Fixes open-telemetry#172
Pull request dashboard statusWaiting on the author · refreshed 2026-07-28 20:21 UTC Move out of draft to request review. Status above doesn't look right?
|
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a Netlify Blobs-backed debounce queue to reduce redundant dashboard refresh workflows.
Changes:
- Queues webhook refresh events and flushes them after 45 seconds of inactivity.
- Extracts GitHub authentication and dispatch logic.
- Adds dependency installation, documentation, and queue tests.
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
.github/workflows/pull-request-dashboard-deploy-webhook.yml |
Installs function dependencies before deployment. |
WEBHOOK_SETUP.md |
Documents queueing and scheduled dispatch behavior. |
test_refresh_queue.mjs |
Tests queue keys, debounce, and expiry rules. |
package.json |
Adds Netlify Blobs. |
package-lock.json |
Locks new dependencies. |
netlify/lib/refresh-queue.mjs |
Defines queue helpers and policies. |
netlify/lib/github-dispatch.mjs |
Centralizes GitHub workflow dispatching. |
netlify/functions/github-webhook.mjs |
Queues accepted webhook events. |
netlify/functions/flush-dashboard-refreshes.mjs |
Sweeps and dispatches queued refreshes. |
Files not reviewed (1)
- .github/scripts/pull-request-dashboard/package-lock.json: Generated file
Comments suppressed due to low confidence (2)
.github/scripts/pull-request-dashboard/netlify/functions/flush-dashboard-refreshes.mjs:27
- This deletion is based on a stale read. If a webhook updates an expired record after
getreturns but before thisdelete, the newly queued event is deleted and its dashboard refresh is lost. Delete conditionally using the ETag read with the record, and retry/re-evaluate on a conflict.
const queued = await store.get(key, { type: "json" });
if (isRefreshExpired(queued, now, EXPIRY_MS)) {
await store.delete(key);
.github/scripts/pull-request-dashboard/netlify/functions/flush-dashboard-refreshes.mjs:48
- Writing the stale
queuedsnapshot after dispatch can drop a webhook that arrives during the dispatch. The webhook may first store a newerlastEventAt, then this write restores the older value and setslastDispatchAtlater than it, makingisRefreshDuepermanently consider the new event handled. Make this an ETag-conditional update and retry/re-evaluate conflicts so an event cannot be overwritten.
await dispatch({
repository: target.repository,
pr_number: target.prNumber,
trigger_event: queued.triggerEvent,
});
await store.setJSON(key, { ...queued, lastDispatchAt: Date.now() });
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+93
to
98
| const queued = (await store.get(key, { type: "json" })) || {}; | ||
| await store.setJSON(key, { | ||
| ...queued, | ||
| lastEventAt: Date.now(), | ||
| triggerEvent: eventName, | ||
| }); |
Comment on lines
+7
to
+8
| "@github/copilot": "1.0.75", | ||
| "@netlify/blobs": "^10.7.9" |
| export const config = { schedule: "* * * * *" }; | ||
|
|
||
| export default async () => { | ||
| const store = getStore(REFRESH_QUEUE_STORE); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #172
The webhook dispatches one workflow run per accepted event today, which is far more than the dashboard needs. GitHub Actions creates one check suite per workflow, so a single push to
opentelemetry-collector-contribproduces 17check_suite.completedwebhooks and therefore 17 dispatches for one pull request. The worst burst measured was 53 dispatches in 77 seconds on a single pull request.The webhook now records each accepted event in a
dashboard-refresh-queueNetlify Blobs entry keyed by<repository>/<pr_number>, and a new scheduled function sweeps that queue every minute and dispatches once a pull request has been quiet for 45 seconds. Replaying 1,501 real webhooks from 15 recent collector-contrib pull requests through this policy yields 515 dispatches, a 66% reduction, and collapses the 53-event burst into 1.Two alternatives were simulated and rejected: debouncing only
check_suiteevents (52% reduction) and a leading-edge dispatch with a cooldown (54% reduction, plus more state). Debouncing everything is both simplest and most effective.The cost is 45-105 seconds of added latency before a refresh starts. Routing is unaffected because
ci_failing_sincederives from each check's owncompleted_atrather than from refresh time. A dispatch failure leaveslastDispatchAtuntouched, so the next sweep retries.The GitHub App auth and dispatch code moves to
netlify/lib/github-dispatch.mjs, which mints one installation token per sweep instead of one per webhook. The webhook now needs onlyGITHUB_WEBHOOK_SECRETand makes no GitHub API calls.The deploy workflow gains an
npm cistep because the functions now have a runtime dependency that Netlify has to bundle.