Skip to content

Add Vercel Plugin#66

Open
andrewmumblebee wants to merge 23 commits into
mainfrom
work/ah/vercel
Open

Add Vercel Plugin#66
andrewmumblebee wants to merge 23 commits into
mainfrom
work/ah/vercel

Conversation

@andrewmumblebee

@andrewmumblebee andrewmumblebee commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

🔌 Plugin overview

  • Plugin name: Vercel
  • Purpose / problem solved: Monitoring of Vercel platform analytics, i.e. deployments, cost, firewall events
  • Primary audience (e.g. platform teams, SREs, product teams): DevOps
  • Authentication method(s) (e.g. OAuth, Username/Password, API Key): API Key

🖼️ Plugin screenshots

Plugin configuration

image image

Default dashboards

image image image image image

🧪 Testing


⚠️ Known limitations

  • Deployments are not indexed. They are available only as data streams, because deployment volume and churn make them unsuitable as long-lived graph objects.
  • No real-time analytics or metrics via REST. Vercel does not expose Web Analytics (pageviews/visitors), Speed Insights (Core Web Vitals), or real-time Observability metrics (edge requests, function invocations, bandwidth) through its public REST API. Operational usage is available only as daily billed quantities via the cost stream — not real-time, per-request telemetry.
  • Cost data cannot be monitored the api to get this requires streaming, which we do not support atm, so causes timeouts and response size errors in AWS
  • Team members and cost require a Team and a token with adequate role/plan. On personal/Hobby accounts these streams may be empty.
    • One connection = one scope. Each configured plugin instance monitors either your personal account or a single team. To monitor multiple teams, add the plugin once per team.
  • Rate limits. Vercel enforces per-action rate limits (HTTP 429). Very large accounts may occasionally see throttling during imports.

📚 Checklist

  • Plugin, datastream and UI naming follow SquaredUp guidelines
  • Logo added
  • One or more dashboards added
  • README added including configuration guidance
  • No secrets or credentials included
  • I agree to the Code of Conduct

@andrewmumblebee andrewmumblebee marked this pull request as ready for review June 16, 2026 14:28
@andrewmumblebee andrewmumblebee requested a review from a team June 16, 2026 14:28
@andrewmumblebee

Copy link
Copy Markdown
Contributor Author

schemaVersion is breaking validation, as fix for it not live in prod yet.

Waiting on Vercel's APIs to be back up to share Cost dashboard, as currently failing due to platform issues on their end

@clarkd

clarkd commented Jun 17, 2026

Copy link
Copy Markdown
Member

@claude review

@clarkd

clarkd commented Jun 17, 2026

Copy link
Copy Markdown
Member

@claude review once

Comment thread plugins/Vercel/v1/custom_types.json
Comment thread plugins/Vercel/v1/metadata.json
Comment thread plugins/Vercel/v1/defaultContent/overview.dash.json Outdated
Comment thread plugins/Vercel/v1/defaultContent/deployments.dash.json Outdated
Comment thread plugins/Vercel/v1/defaultContent/scopes.json
Comment thread plugins/Vercel/v1/dataStreams/projects.json Outdated
Comment thread plugins/Vercel/v1/defaultContent/deployments.dash.json Outdated
Comment thread plugins/Vercel/v1/dataStreams/cost.json Outdated
Comment thread plugins/Vercel/v1/dataStreams/projects.json Outdated
Comment thread plugins/Vercel/v1/defaultContent/activity.dash.json Outdated
Comment thread plugins/Vercel/v1/ui.json Outdated
@TimWheeler-SQUP TimWheeler-SQUP added the new-plugin Used to PR newly added plugins label Jun 18, 2026
Comment thread plugins/Vercel/v1/defaultContent/cost.dash.json Outdated
Comment thread plugins/Vercel/v1/defaultContent/deployments.dash.json
Comment thread plugins/Vercel/v1/docs/README.md
Comment thread plugins/Vercel/v1/metadata.json
@andrewmumblebee

Copy link
Copy Markdown
Contributor Author

@claude review

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.js still exists as an orphaned 25-line script (no data stream references it), while user-facing references in metadata.json:6 (description mentions "a cost overview") and docs/README.md:38, 43, 44 continue to promise a feature that no longer ships. Either re-add the cost.json data stream + cost.dash.json dashboard, or delete cost.js and strip the four cost references from metadata.json and README.md so 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 no dataStreams/*.json file declares postRequestScript: "cost.js" anywhere (the only post-request scripts referenced are currentUser.js and deployments.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:6 description: "...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 in metadata.json.description or docs/README.md against 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:

    1. 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, domain only — no cost).
    2. 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.js itself causes no runtime errors (it is never invoked, since no data stream's config.postRequestScript references 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

    1. Run ls plugins/Vercel/v1/dataStreams/scripts/cost.js, currentUser.js, deployments.js — confirms cost.js is present.
    2. Run ls plugins/Vercel/v1/dataStreams/ → 10 .json files: activity, currentUser, deployments, domainConfig, domains, firewallEvents, projectInfo, projects, teamMembers, teams — no cost.json. So no data stream registers cost.js as a postRequestScript.
    3. 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 — no cost.dash.json.
    4. Read plugins/Vercel/v1/defaultContent/manifest.json → items list contains overview, deployments, activity, project, domain — no cost.
    5. Read plugins/Vercel/v1/metadata.json:6 → description contains "a cost overview".
    6. Read plugins/Vercel/v1/docs/README.md → grep for cost matches at lines 38, 43, 44 — all three reference the non-existent cost stream.
    7. 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 referencing cost.js) and cost.dash.json (dashboard), and re-register the cost dashboard in manifest.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.

Comment thread plugins/Vercel/v1/metadata.json Outdated
Comment thread plugins/Vercel/v1/configValidation.json Outdated
Comment thread plugins/Vercel/v1/docs/README.md Outdated
@andrewmumblebee andrewmumblebee requested a review from clarkd June 19, 2026 13:22
// 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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be needed if using schemaVersion 2.1 of plugins

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, have removed

Comment thread plugins/Vercel/v1/defaultContent/deployments.dash.json Outdated
Comment thread plugins/Vercel/v1/dataStreams/deployments.json Outdated
Comment thread plugins/Vercel/v1/docs/README.md Outdated
"_type": "tile/data-stream",
"description": "",
"activePluginConfigIds": ["{{configId}}"],
"title": "Projects By Framework",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 activityDaily Activity, Cost by dayCost 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

  1. Open plugins/Vercel/v1/defaultContent/overview.dash.json line 39 → "title": "Projects By Framework". The short preposition By is capitalised.
  2. Compare with the prior review thread (inline-comment 3433475469) that prescribed Cost by Day (lowercase by) and Deployments per Day (lowercase per) for sibling tiles — the same rule should apply here.
  3. Compare with plugins/Vercel/v1/defaultContent/cost.dash.json (post-sweep) — sibling tiles correctly use lowercase by.
  4. 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.
  5. Fix is one character; same family as the tile-title nits already filed on this PR.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still present at line 39 — "title": "Projects By Framework" is unchanged.

@andrewmumblebee andrewmumblebee requested a review from clarkd June 19, 2026 14:47
@github-actions

Copy link
Copy Markdown

🧩 Plugin PR Summary

📦 Modified Plugins

  • plugins/Vercel/v1

📋 Results

Step Status
Validation ✅ Passed
Deployment 🚀 Deployed

🔍 Validation Details

vercel
{
  "valid": true,
  "pluginName": "vercel",
  "pluginType": "hybrid",
  "summary": {
    "Data Streams": 10,
    "Import Definitions": 1,
    "UI Configuration": true,
    "Has Icon": true,
    "Has Default Content": true,
    "Config Validation": true,
    "Custom Types": true
  }
}

Comment on lines +12 to +14
"getArgs": [
{ "key": "since", "value": "{{timeframe.start}}" },
{ "key": "until", "value": "{{timeframe.end}}" },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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/deployments
  • firewallEvents.json:14,16startTimestamp / endTimestamp = {{timeframe.unixStart * 1000}} / {{timeframe.unixEnd * 1000}} for /v1/security/firewall/events
  • activity.json:13-14since/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:

  1. Dashboard timeframe last7days is resolved → timeframe.start = "2026-06-12T00:00:00.000Z", timeframe.end = "2026-06-19T00:00:00.000Z" (ISO 8601 strings, per the template docs).
  2. 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.
  3. Vercel's /v3/events parses since/until as integers. parseInt("2026-06-12T00:00:00.000Z") yields either 2026 (truncated parse) or NaN (strict parse) — either way, not the intended ms timestamp 1749686400000.
  4. 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.
  5. 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.
  6. 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.

Comment on lines +36 to +38
- **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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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-52 declares a teams step that runs the teams data stream and imports each row with type.value: "Team" into the graph.
  • plugins/Vercel/v1/metadata.json:23 declares "objectTypes": ["Project", "Domain", "Team"] — Team is one of the plugin's three indexed object types.
  • plugins/Vercel/v1/custom_types.json:16-22 defines a Team custom_type with sourceType: "Team", matching the index step.
  • plugins/Vercel/v1/defaultContent/activity.dash.json:96 scopes the Team Roster tile against objects with sourceType == "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

  1. Open plugins/Vercel/v1/docs/README.md at line 32 → "The plugin imports three object types into the SquaredUp graph:".
  2. Read lines 34-36 → three bullets: Vercel Project, Vercel Domain, Vercel Team.
  3. Read line 38 → "Deployments, teams, members, and activity events are provided as **data streams** (not indexed objects)". The word teams appears in a list explicitly labelled '(not indexed objects)'.
  4. Cross-check the plugin code: indexDefinitions/default.json has a teams step (type.value: "Team"), metadata.json lists Team in objectTypes, custom_types.json defines a Team type. Team objects are definitively imported into the graph.
  5. Conclusion: the bullet list on line 36 is correct; the word teams on 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new-plugin Used to PR newly added plugins

Development

Successfully merging this pull request may close these issues.

3 participants