Add Vercel Plugin#66
Conversation
|
Waiting on Vercel's APIs to be back up to share Cost dashboard, as currently failing due to platform issues on their end |
|
@claude review |
|
@claude review once |
Due to the size of the response data, we can not monitor this right now
We can get this by indexing the team, which will be one object
|
@claude review |
There was a problem hiding this comment.
Additional findings (outside current diff — PR may have been updated during review):
-
🔴
plugins/Vercel/v1/dataStreams/scripts/cost.js:1-25— The cost feature was removed from the data streams in commit 79b05d5, but the cleanup is incomplete:plugins/Vercel/v1/dataStreams/scripts/cost.jsstill exists as an orphaned 25-line script (no data stream references it), while user-facing references inmetadata.json:6(description mentions "a cost overview") anddocs/README.md:38, 43, 44continue to promise a feature that no longer ships. Either re-add thecost.jsondata stream +cost.dash.jsondashboard, or deletecost.jsand strip the four cost references frommetadata.jsonandREADME.mdso the documented surface matches what the validator now reports (10 streams, no cost).Extended reasoning...
What the bug is
Commit 79b05d5 ("Remove Vercel cost data stream") removed the cost data stream and its dashboard, but the removal sweep was partial. The repo is now in an inconsistent state where the data surface and the user-facing description disagree.
Dead artifact still in the repo:
plugins/Vercel/v1/dataStreams/scripts/cost.js— a 25-line post-request script that parsed the FOCUS billing JSONL response. It still exists, but nodataStreams/*.jsonfile declarespostRequestScript: "cost.js"anywhere (the only post-request scripts referenced arecurrentUser.jsanddeployments.js). The latest CI validation summary on this PR confirms "Data Streams: 10" — listing activity, currentUser, deployments, domainConfig, domains, firewallEvents, projectInfo, projects, teamMembers, teams. No cost. The file is unreachable dead code.
User-facing references still promise the feature:
metadata.json:6description:"...team activity, a cost overview, and firewall (WAF) event activity."This string appears in the plugin catalog UI when users browse for plugins to install — it advertises a cost overview that does not exist.docs/README.md:38:"Deployments, teams, members, activity events, and cost data are provided as data streams"— references a non-existent stream.docs/README.md:43:"Operational usage is available only as daily billed quantities via the cost stream"— references a non-existent stream.docs/README.md:44:"Team members and cost require a Team and a token with adequate role/plan"— references a non-existent stream.
The README is rendered in-product on the plugin configuration page, so all three are visible to users while they set up the connection.
Why existing validation doesn't catch it
The plugin validator checks structural correctness — schema, required fields, that referenced data streams exist. It has no signal that an orphaned script in
dataStreams/scripts/is unused (the file is only relevant when a data stream references it), and it does not cross-check freeform prose inmetadata.json.descriptionordocs/README.mdagainst the list of data streams. So the PR validation pipeline reports ✅ Passed — exactly the failure mode that lets partial removals slip through to merge.Impact
Two concrete user-visible inconsistencies on a brand-new community plugin:
- Catalog misrepresentation. A user browsing the plugin catalog reads "a cost overview" in the description and installs the plugin expecting cost monitoring. After install, no cost data stream exists, no cost dashboard exists in the manifest (
overview, deployments, activity, project, domainonly — nocost). - Documentation lies. The README they read while configuring the connection promises a cost stream three separate times. They will look for it and not find it.
The orphaned
cost.jsitself causes no runtime errors (it is never invoked, since no data stream'sconfig.postRequestScriptreferences it), but shipping dead code in a brand-new plugin is poor hygiene and confuses future maintainers about whether cost is intended to come back.Step-by-step proof
- Run
ls plugins/Vercel/v1/dataStreams/scripts/→cost.js,currentUser.js,deployments.js— confirms cost.js is present. - Run
ls plugins/Vercel/v1/dataStreams/→ 10.jsonfiles: activity, currentUser, deployments, domainConfig, domains, firewallEvents, projectInfo, projects, teamMembers, teams — nocost.json. So no data stream registers cost.js as apostRequestScript. - Run
ls plugins/Vercel/v1/defaultContent/→ activity.dash.json, deployments.dash.json, domain.dash.json, manifest.json, overview.dash.json, project.dash.json, scopes.json — nocost.dash.json. - Read
plugins/Vercel/v1/defaultContent/manifest.json→ items list contains overview, deployments, activity, project, domain — no cost. - Read
plugins/Vercel/v1/metadata.json:6→ description contains"a cost overview". - Read
plugins/Vercel/v1/docs/README.md→ grep forcostmatches at lines 38, 43, 44 — all three reference the non-existent cost stream. - Latest CI validation comment on this PR confirms 10 data streams (no cost) and reports the plugin as Validated/Deployed despite the documentation inconsistency.
How to fix
Pick one of two paths:
Path A (preferred — matches what the validator reports): Complete the removal.
- Delete
plugins/Vercel/v1/dataStreams/scripts/cost.js. metadata.json:6— remove", a cost overview,"from the description.docs/README.md:38— drop"and cost data"from the list of data streams.docs/README.md:43— drop the "Operational usage is available only as daily billed quantities via the cost stream" sentence (or rewrite to say cost monitoring is not available).docs/README.md:44— drop "and cost" from the Team-members bullet (it now only requires Team for member visibility).- The PR description's Known limitations bullet about cost daily granularity / timeouts should also be removed for the same reason.
Path B: Restore the feature — re-add
cost.json(data stream definition referencingcost.js) andcost.dash.json(dashboard), and re-register the cost dashboard inmanifest.json. This is the more involved option, justified only if the API issues that motivated the removal have been resolved.Path A is one ~5-line change set and clears the inconsistency immediately.
| // rawId a single-element array. Empty/absent => account-wide, no filter. | ||
| const deployments = (data && data.deployments) || []; | ||
|
|
||
| const unwrap = (v) => (Array.isArray(v) ? v[0] : v); |
There was a problem hiding this comment.
Shouldn't be needed if using schemaVersion 2.1 of plugins
There was a problem hiding this comment.
True, have removed
| "_type": "tile/data-stream", | ||
| "description": "", | ||
| "activePluginConfigIds": ["{{configId}}"], | ||
| "title": "Projects By Framework", |
There was a problem hiding this comment.
🟡 Nit: tile title at line 39 reads "Projects By Framework" with capital "By". Per REVIEW.md's title-case rule and the convention used elsewhere in this PR (e.g. "Cost by Day", "Deployments per Day"), short prepositions like "by" stay lowercase. The title-case sweep in commit 8a11ba2 caught the sibling tiles but missed this one. One-character fix: change "By" → "by".
Extended reasoning...
What the bug is
In plugins/Vercel/v1/defaultContent/overview.dash.json at line 39, the donut tile title reads:
"title": "Projects By Framework"The short preposition By is capitalised. Under standard title-case rules — which REVIEW.md's Tile names — Use title case rule implicitly relies on — short prepositions like by, in, on, for, and per stay lowercase. The prior review thread on this PR was explicit about this when it prescribed Cost by Day and Deployments per Day for the sibling tiles (inline-comment 3433475469), and the title-case sweep in commit 8a11ba2 applied that convention across other dashboards.
Why existing code does not prevent it
The plugin validator (visible in the github-actions[bot] PR summary, reporting ✅ Passed) checks structural/schema correctness only — no lint step inspects dashboard title strings for title-case rules. Stylistic slips and over/under-corrections both pass validation unchanged.
Why this tile was missed
Commit 8a11ba2 was a title-case sweep that fixed lowercase tile titles (e.g. Daily activity → Daily Activity, Cost by day → Cost by Day). This tile had the opposite problem — already capitalised, but with a short preposition that should have stayed lowercase — so it didn't match the sweep's lowercase-finding pattern and slipped through. The same class of regression hit deployments.dash.json:180 (Deployments Per Day, separately filed).
Impact
Cosmetic only — the tile renders and binds data correctly. Same severity family as the other tile-title nits already filed for this PR. Nit-level visible inconsistency on the headline Overview dashboard, which is the first dashboard every user sees on install.
How to fix
Change line 39 of plugins/Vercel/v1/defaultContent/overview.dash.json from:
"title": "Projects By Framework"to:
"title": "Projects by Framework"One-character edit (capital B → lowercase b).
Step-by-step proof
- Open
plugins/Vercel/v1/defaultContent/overview.dash.jsonline 39 →"title": "Projects By Framework". The short prepositionByis capitalised. - Compare with the prior review thread (inline-comment 3433475469) that prescribed
Cost by Day(lowercaseby) andDeployments per Day(lowercaseper) for sibling tiles — the same rule should apply here. - Compare with
plugins/Vercel/v1/defaultContent/cost.dash.json(post-sweep) — sibling tiles correctly use lowercaseby. - Even the prior review's own prose refers to this tile by its correct casing — "the neighboring 'Projects by Framework' donut tile" (inline-comment 3429319373) — confirming the intended form.
- Fix is one character; same family as the tile-title nits already filed on this PR.
There was a problem hiding this comment.
Still present at line 39 — "title": "Projects By Framework" is unchanged.
🧩 Plugin PR Summary📦 Modified Plugins
📋 Results
🔍 Validation Details✅
|
| "getArgs": [ | ||
| { "key": "since", "value": "{{timeframe.start}}" }, | ||
| { "key": "until", "value": "{{timeframe.end}}" }, |
There was a problem hiding this comment.
🔴 The activity stream passes {{timeframe.start}} and {{timeframe.end}} (which template to ISO 8601 strings) to Vercel's /v3/events since/until params, but Vercel documents these as JavaScript millisecond timestamps — confirmed by the other two timeframe-aware Vercel streams in this same PR (deployments.json:13 and firewallEvents.json:14,16) which correctly use {{timeframe.unixStart * 1000}} / {{timeframe.unixEnd * 1000}}. As written, Vercel will reject the ISO string as non-numeric or silently ignore it, so the Activity dashboard's timeframe selector is a no-op — every request returns the API's default window regardless of the selected timeframe. Fix: change lines 13-14 to {{timeframe.unixStart * 1000}} / {{timeframe.unixEnd * 1000}} to match the other Vercel streams.
Extended reasoning...
What the bug is
At plugins/Vercel/v1/dataStreams/activity.json:12-14, the activity stream sends:
"getArgs": [
{ "key": "since", "value": "{{timeframe.start}}" },
{ "key": "until", "value": "{{timeframe.end}}" },
{ "key": "limit", "value": "100" }
]Per SquaredUp's build-plugin reference (.claude/skills/build-plugin/references/data-streams.md), {{timeframe.start}} / {{timeframe.end}} template to ISO 8601 strings (e.g. 2026-06-19T00:00:00.000Z), not unix epoch values. This is confirmed by usage in other plugins — SumoLogic's metricsQuery.json maps {{timeframe.start}} to iso8601Time, and SendGrid's stats streams use {{timeframe.start.substring(0,10)}} to extract the date portion.
Vercel's /v3/events endpoint documents since and until as integer Unix timestamps in milliseconds. The strongest evidence is internal to this same PR — three Vercel timeframe-aware endpoints, two correctly using unix ms, one (this one) using ISO strings:
deployments.json:13—"since"={{timeframe.unixStart * 1000}}for/v7/deploymentsfirewallEvents.json:14,16—startTimestamp/endTimestamp={{timeframe.unixStart * 1000}}/{{timeframe.unixEnd * 1000}}for/v1/security/firewall/eventsactivity.json:13-14—since/until={{timeframe.start}}/{{timeframe.end}}(ISO strings) — the outlier
The deployments.js post-request script also explicitly notes the Vercel time convention is unix ms (untilMs = tf.unixEnd * 1000), reinforcing that this is the established convention for this plugin.
Why existing code does not prevent it
The plugin validator (visible in the github-actions[bot] PR summary, ✅ Passed) checks structural/schema correctness only — it does not validate that timeframe template values match the format the upstream API expects. The mismatch between the templated ISO 8601 string and Vercel's expected ms integer slips through validation cleanly.
Impact
The activity stream's paging.mode is none with limit=100, so even if Vercel silently ignores the malformed since/until params, the request still succeeds and returns Vercel's default recent-events window — capped at 100 events regardless of what timeframe the user selected. The Activity dashboard's timeframe selector (last24hours, last7days, etc., set at activity.dash.json:4 via last7days) becomes a no-op on the OOB Activity dashboard. The "Recent Activity" table, the "Daily Activity" line graph, and any user-authored activity tiles all show whatever Vercel's default window happens to be — not the timeframe the dashboard claims to be filtering by.
This is a visible behavioural regression on the OOB Activity dashboard, hence normal severity.
How to fix
Two-line edit on plugins/Vercel/v1/dataStreams/activity.json:
"getArgs": [
- { "key": "since", "value": "{{timeframe.start}}" },
- { "key": "until", "value": "{{timeframe.end}}" },
+ { "key": "since", "value": "{{timeframe.unixStart * 1000}}" },
+ { "key": "until", "value": "{{timeframe.unixEnd * 1000}}" },
{ "key": "limit", "value": "100" }
]This matches the convention used by the other two timeframe-aware Vercel streams in this PR.
Step-by-step proof
Concrete example with the dashboard's last7days timeframe selected on 2026-06-19:
- Dashboard timeframe
last7daysis resolved →timeframe.start = "2026-06-12T00:00:00.000Z",timeframe.end = "2026-06-19T00:00:00.000Z"(ISO 8601 strings, per the template docs). - Activity stream substitutes them into the request:
GET /v3/events?since=2026-06-12T00:00:00.000Z&until=2026-06-19T00:00:00.000Z&limit=100. - Vercel's
/v3/eventsparsessince/untilas integers.parseInt("2026-06-12T00:00:00.000Z")yields either2026(truncated parse) orNaN(strict parse) — either way, not the intended ms timestamp1749686400000. - Vercel either returns 400 (strict numeric validation), returns events newer than the year 2026 ms (i.e. events newer than ~1970-01-01T00:00:02Z — effectively everything), or silently ignores the malformed param and returns the default recent-events window.
- In all three failure modes, the request does not honour the dashboard's 7-day window. The user changes the timeframe to
last24hours— same broken behaviour, no change in the rendered tile. - For contrast, with the fix applied:
GET /v3/events?since=1749686400000&until=1750291200000&limit=100→ Vercel honours both bounds → the tile renders activity events from the selected 7-day window.
| - **Vercel Team** - one object per team in the configured account, that the token has access to. | ||
|
|
||
| Deployments, teams, members, and activity events are provided as **data streams** (not indexed objects) — query them on dashboards and scope deployment streams to a project. |
There was a problem hiding this comment.
🟡 The README contradicts itself on whether Team is indexed: lines 32-36 list Vercel Team as one of the three indexed object types, but line 38 immediately below says "Deployments, teams, members, and activity events are provided as data streams (not indexed objects)" — explicitly claiming teams are NOT indexed. The plugin actually does index Team (indexDefinitions/default.json:40-52, metadata.json:23 objectTypes, custom_types.json:16-22), so line 38 is the wrong one. Fix: drop teams from line 38, leaving "Deployments, members, and activity events are provided as data streams".
Extended reasoning...
What the bug is
plugins/Vercel/v1/docs/README.md is internally inconsistent about Team objects after Team was added as a third indexed type. Within a six-line span, the file says two contradictory things about teams:
- Line 32 (introduces the bullet list):
"The plugin imports three object types into the SquaredUp graph:"— the bullet at line 36 lists Vercel Team as one of those indexed object types. - Line 38 (immediately below the bullet list):
"Deployments, teams, members, and activity events are provided as **data streams** (not indexed objects)"— explicitly claims teams are NOT indexed objects.
Both cannot be true. The parenthetical (not indexed objects) on line 38 applies to the whole list, so teams on that line directly contradicts the Vercel Team bullet on line 36.
Which side is correct
The 'three indexed object types' side. Verified against the actual plugin:
plugins/Vercel/v1/indexDefinitions/default.json:40-52declares ateamsstep that runs theteamsdata stream and imports each row withtype.value: "Team"into the graph.plugins/Vercel/v1/metadata.json:23declares"objectTypes": ["Project", "Domain", "Team"]— Team is one of the plugin's three indexed object types.plugins/Vercel/v1/custom_types.json:16-22defines a Teamcustom_typewithsourceType: "Team", matching the index step.plugins/Vercel/v1/defaultContent/activity.dash.json:96scopes the Team Roster tile against objects withsourceType == "Team"— code that relies on Team objects existing in the graph.
So Teams are indeed indexed; line 38's parenthetical is the stale word. This is a holdover from when only Project and Domain were indexed — the commit that added the Team index step (9d4343d) updated the bullet list but missed the prose sentence below it.
Why existing code doesn't catch this
The plugin validator checks structural/schema correctness — the latest CI run on this PR reports ✅ Passed — but does not cross-reference README prose against indexDefinitions or custom_types.json. Internal documentation contradictions slip straight through validation.
Impact
The README is rendered in-product on the plugin configuration Need help? panel during plugin setup. Every user installing the Vercel plugin will see these two contradicting statements about Team objects six lines apart on their setup screen. They'll either trust the bullet list ('Team is indexed, I can scope a dashboard to it') or trust the prose sentence ('Team is just a data stream') — and one of those readings will turn out to be wrong.
Cosmetic only (no data flow breaks), hence nit severity, but the inconsistency is user-visible and the fix is one word.
Step-by-step proof
- Open
plugins/Vercel/v1/docs/README.mdat line 32 →"The plugin imports three object types into the SquaredUp graph:". - Read lines 34-36 → three bullets: Vercel Project, Vercel Domain, Vercel Team.
- Read line 38 →
"Deployments, teams, members, and activity events are provided as **data streams** (not indexed objects)". The wordteamsappears in a list explicitly labelled '(not indexed objects)'. - Cross-check the plugin code:
indexDefinitions/default.jsonhas ateamsstep (type.value: "Team"),metadata.jsonlists Team inobjectTypes,custom_types.jsondefines a Team type. Team objects are definitively imported into the graph. - Conclusion: the bullet list on line 36 is correct; the word
teamson line 38 is stale. Drop it.
How to fix
One-word edit on line 38 of plugins/Vercel/v1/docs/README.md:
- Deployments, teams, members, and activity events are provided as **data streams** (not indexed objects) — query them on dashboards and scope deployment streams to a project.
+ Deployments, members, and activity events are provided as **data streams** (not indexed objects) — query them on dashboards and scope deployment streams to a project.
🔌 Plugin overview
🖼️ Plugin screenshots
Plugin configuration
Default dashboards
🧪 Testing
📚 Checklist