From 0326602b3a314dfe2a7c1c8a2ca01d1a27462cc9 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Tue, 16 Jun 2026 14:46:03 +0100 Subject: [PATCH 01/23] Add initial Vercel plugin dataStreams and dashboards --- plugins/Vercel/v1/configValidation.json | 11 + plugins/Vercel/v1/custom_types.json | 16 + plugins/Vercel/v1/dataStreams/activity.json | 36 ++ plugins/Vercel/v1/dataStreams/cost.json | 71 ++++ .../Vercel/v1/dataStreams/currentUser.json | 21 ++ .../Vercel/v1/dataStreams/deployments.json | 95 +++++ .../Vercel/v1/dataStreams/domainConfig.json | 18 + plugins/Vercel/v1/dataStreams/domains.json | 31 ++ .../Vercel/v1/dataStreams/firewallEvents.json | 69 ++++ .../v1/dataStreams/listDeployments.json | 51 +++ .../Vercel/v1/dataStreams/projectInfo.json | 23 ++ plugins/Vercel/v1/dataStreams/projects.json | 43 +++ plugins/Vercel/v1/dataStreams/scripts/cost.js | 25 ++ .../v1/dataStreams/scripts/currentUser.js | 2 + .../v1/dataStreams/scripts/deployments.js | 37 ++ .../Vercel/v1/dataStreams/teamMembers.json | 43 +++ plugins/Vercel/v1/dataStreams/teams.json | 28 ++ .../v1/defaultContent/activity.dash.json | 177 +++++++++ .../Vercel/v1/defaultContent/cost.dash.json | 161 ++++++++ .../v1/defaultContent/deployments.dash.json | 297 +++++++++++++++ .../Vercel/v1/defaultContent/domain.dash.json | 98 +++++ .../Vercel/v1/defaultContent/manifest.json | 10 + .../v1/defaultContent/overview.dash.json | 305 +++++++++++++++ .../v1/defaultContent/project.dash.json | 356 ++++++++++++++++++ plugins/Vercel/v1/defaultContent/scopes.json | 26 ++ plugins/Vercel/v1/docs/README.md | 57 +++ plugins/Vercel/v1/icon.svg | 4 + .../Vercel/v1/indexDefinitions/default.json | 24 ++ plugins/Vercel/v1/metadata.json | 40 ++ plugins/Vercel/v1/ui.json | 21 ++ 30 files changed, 2196 insertions(+) create mode 100644 plugins/Vercel/v1/configValidation.json create mode 100644 plugins/Vercel/v1/custom_types.json create mode 100644 plugins/Vercel/v1/dataStreams/activity.json create mode 100644 plugins/Vercel/v1/dataStreams/cost.json create mode 100644 plugins/Vercel/v1/dataStreams/currentUser.json create mode 100644 plugins/Vercel/v1/dataStreams/deployments.json create mode 100644 plugins/Vercel/v1/dataStreams/domainConfig.json create mode 100644 plugins/Vercel/v1/dataStreams/domains.json create mode 100644 plugins/Vercel/v1/dataStreams/firewallEvents.json create mode 100644 plugins/Vercel/v1/dataStreams/listDeployments.json create mode 100644 plugins/Vercel/v1/dataStreams/projectInfo.json create mode 100644 plugins/Vercel/v1/dataStreams/projects.json create mode 100644 plugins/Vercel/v1/dataStreams/scripts/cost.js create mode 100644 plugins/Vercel/v1/dataStreams/scripts/currentUser.js create mode 100644 plugins/Vercel/v1/dataStreams/scripts/deployments.js create mode 100644 plugins/Vercel/v1/dataStreams/teamMembers.json create mode 100644 plugins/Vercel/v1/dataStreams/teams.json create mode 100644 plugins/Vercel/v1/defaultContent/activity.dash.json create mode 100644 plugins/Vercel/v1/defaultContent/cost.dash.json create mode 100644 plugins/Vercel/v1/defaultContent/deployments.dash.json create mode 100644 plugins/Vercel/v1/defaultContent/domain.dash.json create mode 100644 plugins/Vercel/v1/defaultContent/manifest.json create mode 100644 plugins/Vercel/v1/defaultContent/overview.dash.json create mode 100644 plugins/Vercel/v1/defaultContent/project.dash.json create mode 100644 plugins/Vercel/v1/defaultContent/scopes.json create mode 100644 plugins/Vercel/v1/docs/README.md create mode 100644 plugins/Vercel/v1/icon.svg create mode 100644 plugins/Vercel/v1/indexDefinitions/default.json create mode 100644 plugins/Vercel/v1/metadata.json create mode 100644 plugins/Vercel/v1/ui.json diff --git a/plugins/Vercel/v1/configValidation.json b/plugins/Vercel/v1/configValidation.json new file mode 100644 index 00000000..2ac57f11 --- /dev/null +++ b/plugins/Vercel/v1/configValidation.json @@ -0,0 +1,11 @@ +{ + "steps": [ + { + "displayName": "Authenticate", + "dataStream": { "name": "currentUser" }, + "required": true, + "error": "Could not authenticate with Vercel. Check that your API Token is valid and has not expired.", + "success": "Connected to Vercel successfully." + } + ] +} diff --git a/plugins/Vercel/v1/custom_types.json b/plugins/Vercel/v1/custom_types.json new file mode 100644 index 00000000..e25c0352 --- /dev/null +++ b/plugins/Vercel/v1/custom_types.json @@ -0,0 +1,16 @@ +[ + { + "name": "Vercel Project", + "sourceType": "Vercel Project", + "icon": "rocket", + "singular": "Project", + "plural": "Projects" + }, + { + "name": "Vercel Domain", + "sourceType": "Vercel Domain", + "icon": "globe", + "singular": "Domain", + "plural": "Domains" + } +] diff --git a/plugins/Vercel/v1/dataStreams/activity.json b/plugins/Vercel/v1/dataStreams/activity.json new file mode 100644 index 00000000..8abd5b02 --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/activity.json @@ -0,0 +1,36 @@ +{ + "name": "activity", + "displayName": "Activity", + "description": "Vercel account or team activity feed, one row per audit-style event. Backs activity and audit log tiles", + "tags": ["Activity"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v3/events", + "pathToData": "events", + "expandInnerObjects": true, + "getArgs": [ + { "key": "since", "value": "{{timeframe.start}}" }, + { "key": "until", "value": "{{timeframe.end}}" }, + { "key": "limit", "value": "100" } + ], + "paging": { "mode": "none" } + }, + "matches": "none", + "metadata": [ + { "name": "id", "displayName": "ID", "role": "id", "visible": false }, + { "name": "type", "displayName": "Type" }, + { "name": "text", "displayName": "Summary", "role": "label" }, + { + "name": "actor", + "displayName": "Actor", + "computed": true, + "valueExpression": "{{ $['user.username'] || $['user.email'] || $['userId'] }}" + }, + { "name": "user.username", "displayName": "User", "visible": false }, + { "name": "user.email", "displayName": "Email", "visible": false }, + { "name": "userId", "displayName": "User ID", "visible": false }, + { "name": "createdAt", "displayName": "Created", "shape": "date", "role": "timestamp" } + ], + "timeframes": true +} diff --git a/plugins/Vercel/v1/dataStreams/cost.json b/plugins/Vercel/v1/dataStreams/cost.json new file mode 100644 index 00000000..82f237d1 --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/cost.json @@ -0,0 +1,71 @@ +{ + "name": "cost", + "displayName": "Cost", + "description": "Vercel usage cost and consumption from the FOCUS billing endpoint, one row per daily charge", + "tags": ["Cost", "Billing"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v1/billing/charges", + "postRequestScript": "cost.js", + "getArgs": [ + { "key": "from", "value": "{{timeframe.start}}" }, + { "key": "to", "value": "{{timeframe.end}}" } + ], + "headers": [ + { + "key": "Accept-Encoding", + "value": "gzip" + } + ] + }, + "matches": "none", + "metadata": [ + { + "name": "service", + "displayName": "Service", + "shape": "string", + "role": "label" + }, + { + "name": "billedCost", + "displayName": "Billed Cost ($)", + "shape": [ + "currency", + { + "code": "usd", + "decimalPlaces": 2, + "thousandsSeparator": true + } + ], + "role": "value" + }, + { + "name": "effectiveCost", + "displayName": "Effective Cost ($)", + "shape": [ + "currency", + { + "code": "usd", + "decimalPlaces": 2, + "thousandsSeparator": true + } + ] + }, + { "name": "quantity", "displayName": "Quantity", "shape": "number" }, + { "name": "unit", "displayName": "Unit", "shape": "string" }, + { + "name": "projectName", + "displayName": "Project", + "shape": "string", + "role": "label" + }, + { + "name": "periodStart", + "displayName": "Period Start", + "shape": "date", + "role": "timestamp" + } + ], + "timeframes": ["last24hours", "last7days", "last30days", "thisMonth"] +} diff --git a/plugins/Vercel/v1/dataStreams/currentUser.json b/plugins/Vercel/v1/dataStreams/currentUser.json new file mode 100644 index 00000000..844ab4ac --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/currentUser.json @@ -0,0 +1,21 @@ +{ + "name": "currentUser", + "displayName": "Current User", + "description": "Returns the authenticated Vercel user. Used to validate the connection.", + "tags": [], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v2/user", + "postRequestScript": "currentUser.js" + }, + "matches": "none", + "metadata": [ + { "name": "id", "displayName": "ID", "visible": false }, + { "name": "username", "displayName": "Username", "role": "label" }, + { "name": "name", "displayName": "Name" }, + { "name": "email", "displayName": "Email" } + ], + "timeframes": false, + "visibility": { "type": "hidden" } +} diff --git a/plugins/Vercel/v1/dataStreams/deployments.json b/plugins/Vercel/v1/dataStreams/deployments.json new file mode 100644 index 00000000..6066a57c --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/deployments.json @@ -0,0 +1,95 @@ +{ + "name": "deployments", + "displayName": "Deployments", + "description": "Vercel deployments across the account or a selected project, one row per deployment. Backs deployment health and history tiles", + "tags": ["Deployments"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v7/deployments", + "postRequestScript": "deployments.js", + "getArgs": [ + { "key": "since", "value": "{{timeframe.unixStart * 1000}}" } + ], + "paging": { + "mode": "token", + "pageSize": { + "realm": "queryArg", + "path": "limit", + "value": "100" + }, + "in": { "realm": "payload", "path": "pagination.next" }, + "out": { "realm": "queryArg", "path": "until" } + } + }, + "matches": "none", + "ui": [ + { + "type": "objects", + "name": "project", + "label": "Project (optional)", + "matches": { + "sourceType": { "type": "equals", "value": "Vercel Project" } + } + } + ], + "metadata": [ + { + "name": "uid", + "displayName": "ID", + "role": "value", + "visible": false + }, + { "name": "name", "displayName": "Name", "role": "label" }, + { + "name": "state", + "displayName": "State", + "shape": [ + "state", + { + "map": { + "success": ["READY"], + "error": ["ERROR", "CANCELED"], + "warning": ["BUILDING", "QUEUED", "INITIALIZING"], + "unknown": ["DELETED"] + } + } + ] + }, + { "name": "target", "displayName": "Target" }, + { "name": "projectId", "displayName": "Project ID", "visible": false }, + { + "name": "created", + "displayName": "Created", + "shape": "date", + "role": "timestamp" + }, + { "name": "url", "displayName": "Url", "shape": "url" }, + { + "name": "inspectorUrl", + "displayName": "Inspector Url", + "shape": "url" + }, + { "name": "creator", "displayName": "Creator" }, + { + "name": "ready", + "displayName": "Ready", + "shape": "date", + "role": "timestamp" + }, + { + "name": "buildingAt", + "displayName": "Building At", + "shape": "date", + "role": "timestamp" + }, + { + "name": "createdAt", + "displayName": "Created At", + "shape": "date", + "role": "timestamp" + }, + { "pattern": ".*" } + ], + "timeframes": true +} diff --git a/plugins/Vercel/v1/dataStreams/domainConfig.json b/plugins/Vercel/v1/dataStreams/domainConfig.json new file mode 100644 index 00000000..8a430814 --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/domainConfig.json @@ -0,0 +1,18 @@ +{ + "name": "domainConfig", + "displayName": "Domain Config", + "description": "Configuration health for a single Vercel domain — whether DNS/nameservers are misconfigured, the service type, and who configured it", + "tags": ["Domain"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "v6/domains/{{object.name}}/config" + }, + "matches": { "sourceType": { "type": "equals", "value": "Vercel Domain" } }, + "metadata": [ + { "name": "misconfigured", "displayName": "Misconfigured", "shape": "boolean" }, + { "name": "serviceType", "displayName": "Service Type", "shape": "string" }, + { "name": "configuredBy", "displayName": "Configured By", "shape": "string" } + ], + "timeframes": false +} diff --git a/plugins/Vercel/v1/dataStreams/domains.json b/plugins/Vercel/v1/dataStreams/domains.json new file mode 100644 index 00000000..860b80ec --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/domains.json @@ -0,0 +1,31 @@ +{ + "name": "domains", + "displayName": "Domains", + "description": "Lists Vercel custom domains in the configured account or team. Backs the Vercel Domain import and domain inventory tiles.", + "tags": [], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v5/domains", + "pathToData": "domains", + "paging": { + "mode": "token", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "100" }, + "in": { "realm": "payload", "path": "pagination.next" }, + "out": { "realm": "queryArg", "path": "until" } + } + }, + "matches": "none", + "metadata": [ + { "name": "id", "displayName": "ID", "visible": false }, + { "name": "sourceType", "computed": true, "valueExpression": "Vercel Domain", "visible": false }, + { "name": "name", "displayName": "Domain", "role": "label" }, + { "name": "verified", "displayName": "Verified" }, + { "name": "serviceType", "displayName": "Service Type" }, + { "name": "expiresAt", "displayName": "Expires", "shape": "date" }, + { "name": "boughtAt", "displayName": "Bought", "shape": "date" }, + { "name": "renew", "displayName": "Auto-renew" }, + { "name": "createdAt", "displayName": "Created", "shape": "date" } + ], + "timeframes": false +} diff --git a/plugins/Vercel/v1/dataStreams/firewallEvents.json b/plugins/Vercel/v1/dataStreams/firewallEvents.json new file mode 100644 index 00000000..06f5897f --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/firewallEvents.json @@ -0,0 +1,69 @@ +{ + "name": "firewallEvents", + "displayName": "Firewall Events", + "description": "Per-action firewall event counts over the timeframe for a single Vercel project, one row per time-bucket and action type", + "tags": ["Security", "Firewall"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "v1/security/firewall/events", + "getArgs": [ + { "key": "projectId", "value": "{{object.rawId}}" }, + { + "key": "startTimestamp", + "value": "{{timeframe.unixStart * 1000}}" + }, + { "key": "endTimestamp", "value": "{{timeframe.unixEnd * 1000}}" } + ], + "pathToData": "actions" + }, + "matches": { + "sourceType": { "type": "equals", "value": "Vercel Project" } + }, + "metadata": [ + { + "name": "startTime", + "displayName": "Time", + "shape": "date", + "role": "timestamp" + }, + { + "name": "action", + "displayName": "Action", + "shape": "string", + "role": "label" + }, + { + "name": "count", + "displayName": "Count", + "shape": "number", + "role": "value" + }, + { + "name": "host", + "displayName": "Host", + "shape": "string" + }, + { + "name": "public_ip", + "displayName": "Public IP", + "shape": "string" + }, + { + "name": "action_type", + "displayName": "Action Category", + "shape": "string", + "visible": false + }, + { + "name": "isActive", + "displayName": "Active", + "shape": "boolean", + "visible": false + }, + { + "pattern": ".*" + } + ], + "timeframes": true +} diff --git a/plugins/Vercel/v1/dataStreams/listDeployments.json b/plugins/Vercel/v1/dataStreams/listDeployments.json new file mode 100644 index 00000000..2da76017 --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/listDeployments.json @@ -0,0 +1,51 @@ +{ + "name": "deployments", + "displayName": "Deployments", + "description": "Vercel deployments across the account or a selected project, one row per deployment. Backs deployment health and history tiles", + "tags": ["Deployments"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v7/deployments", + "postRequestScript": "deployments.js", + "getArgs": [ + { "key": "since", "value": "{{timeframe.unixStart * 1000}}" } + ], + "paging": { + "mode": "token", + "pageSize": { + "realm": "queryArg", + "path": "limit", + "value": "100" + }, + "in": { "realm": "payload", "path": "pagination.next" }, + "out": { "realm": "queryArg", "path": "until" } + } + }, + "matches": "none", + "ui": [ + { + "type": "objects", + "name": "project", + "label": "Project (optional)", + "matches": { + "sourceType": { "type": "equals", "value": "Vercel Project" } + } + } + ], + "metadata": [ + { + "name": "uid", + "displayName": "ID", + "role": "value" + }, + { + "name": "label", + "displayName": "Label", + "role": "label", + "valueExpression": "{{$['status']}}" + }, + { "pattern": ".*" } + ], + "timeframes": true +} diff --git a/plugins/Vercel/v1/dataStreams/projectInfo.json b/plugins/Vercel/v1/dataStreams/projectInfo.json new file mode 100644 index 00000000..dd7b5c5a --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/projectInfo.json @@ -0,0 +1,23 @@ +{ + "name": "projectInfo", + "displayName": "Project Info", + "description": "Per-project detail (framework, Node version, timestamps, git repo) for a single Vercel project", + "tags": ["Project"], + "baseDataSourceName": "httpRequestScopedSingle", + "config": { + "httpMethod": "get", + "endpointPath": "v9/projects/{{object.rawId}}", + "expandInnerObjects": true + }, + "matches": { "sourceType": { "type": "equals", "value": "Vercel Project" } }, + "metadata": [ + { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, + { "name": "framework", "displayName": "Framework", "shape": "string" }, + { "name": "nodeVersion", "displayName": "Node Version", "shape": "string" }, + { "name": "createdAt", "displayName": "Created", "shape": "date" }, + { "name": "updatedAt", "displayName": "Updated", "shape": "date" }, + { "name": "link.repo", "displayName": "Git Repo", "shape": "string" }, + { "name": "link.type", "displayName": "Git Provider", "shape": "string" } + ], + "timeframes": false +} diff --git a/plugins/Vercel/v1/dataStreams/projects.json b/plugins/Vercel/v1/dataStreams/projects.json new file mode 100644 index 00000000..f15c053a --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/projects.json @@ -0,0 +1,43 @@ +{ + "name": "projects", + "displayName": "Projects", + "description": "Lists Vercel projects in the configured account or team. Backs the Vercel Project import and project inventory tiles.", + "tags": [], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v10/projects", + "pathToData": "projects", + "paging": { + "mode": "token", + "pageSize": { + "realm": "queryArg", + "path": "limit", + "value": "100" + }, + "in": { "realm": "payload", "path": "pagination.next" }, + "out": { "realm": "queryArg", "path": "from" } + } + }, + "matches": "none", + "metadata": [ + { + "name": "id", + "displayName": "ID", + "role": "value", + "visible": false + }, + { + "name": "sourceType", + "computed": true, + "valueExpression": "Vercel Project", + "visible": false + }, + { "name": "name", "displayName": "Name", "role": "label" }, + { "name": "framework", "displayName": "Framework" }, + { "name": "nodeVersion", "displayName": "Node Version" }, + { "name": "createdAt", "displayName": "Created", "shape": "date" }, + { "name": "updatedAt", "displayName": "Updated", "shape": "date" } + ], + "timeframes": false +} diff --git a/plugins/Vercel/v1/dataStreams/scripts/cost.js b/plugins/Vercel/v1/dataStreams/scripts/cost.js new file mode 100644 index 00000000..32245cc8 --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/scripts/cost.js @@ -0,0 +1,25 @@ +// GET /v1/billing/charges returns application/jsonl — newline-delimited JSON +// objects (FOCUS v1.3 cost/usage records), NOT a JSON array. The handler only +// auto-parses JSON/XML, so `data` is null/incomplete here; the raw text is on +// response.body. Split on newlines, drop empties, JSON.parse each line, then map +// each FOCUS record to a flat row with real JS primitives. +const records = (response.body || "") + .split("\n") + .map((line) => line.trim()) + .filter((line) => line.length > 0) + .map((line) => JSON.parse(line)); + +const num = (v) => (v === null || v === undefined || v === "" ? null : Number(v)); + +result = records.map((r) => { + const tags = r.Tags || {}; + return { + service: r.ServiceName, + billedCost: num(r.BilledCost), + effectiveCost: num(r.EffectiveCost), + quantity: num(r.ConsumedQuantity), + unit: r.ConsumedUnit, + projectName: tags.ProjectName || tags.ProjectId || null, + periodStart: r.ChargePeriodStart + }; +}); diff --git a/plugins/Vercel/v1/dataStreams/scripts/currentUser.js b/plugins/Vercel/v1/dataStreams/scripts/currentUser.js new file mode 100644 index 00000000..d294752f --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/scripts/currentUser.js @@ -0,0 +1,2 @@ +// /v2/user returns a single { user: {...} } object — wrap it as one row. +result = data && data.user ? [data.user] : []; diff --git a/plugins/Vercel/v1/dataStreams/scripts/deployments.js b/plugins/Vercel/v1/dataStreams/scripts/deployments.js new file mode 100644 index 00000000..18402b4e --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/scripts/deployments.js @@ -0,0 +1,37 @@ +// /v7/deployments returns { deployments: [...], pagination: {...} }. +// One row per deployment. Optional `project` objects-picker (stream ui name +// "project") arrives at context.config.project as an array (multi-select), each +// 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); +const selected = (context.config && context.config.project) || []; +const projectIds = new Set( + selected.map((o) => unwrap(o.rawId)).filter(Boolean), +); + +// Token paging walks `until` from pagination.next back to the `since` floor, so +// the first page starts at "now". For a historical timeframe (e.g. lastMonth) +// that over-fetches rows newer than the window end — bound the top here. +// (We can't set an `until` getArg: it collides with the paging `out=until`.) +const tf = context.timeframe || {}; +const untilMs = tf.unixEnd ? tf.unixEnd * 1000 : null; + +const scoped = deployments.filter( + (d) => + (!projectIds.size || projectIds.has(d.projectId)) && + (untilMs === null || d.created <= untilMs), +); + +result = scoped.map((d) => ({ + ...d, + uid: d.uid, + name: d.name, + state: d.readyState || d.state, + target: d.target || "preview", + projectId: d.projectId, + created: d.created, + inspectorUrl: d.inspectorUrl, + creator: (d.creator && (d.creator.username || d.creator.uid)) || null, +})); + diff --git a/plugins/Vercel/v1/dataStreams/teamMembers.json b/plugins/Vercel/v1/dataStreams/teamMembers.json new file mode 100644 index 00000000..d7284898 --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/teamMembers.json @@ -0,0 +1,43 @@ +{ + "name": "teamMembers", + "displayName": "Team Members", + "description": "Lists members of the configured Vercel team", + "tags": ["Team"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v3/teams/{{dataSource.teamId}}/members", + "pathToData": "members", + "paging": { + "mode": "token", + "pageSize": { + "realm": "queryArg", + "path": "limit", + "value": "100" + }, + "in": { "realm": "payload", "path": "pagination.next" }, + "out": { "realm": "queryArg", "path": "until" } + } + }, + "matches": "none", + "metadata": [ + { + "name": "uid", + "displayName": "ID", + "shape": "string", + "visible": false + }, + { "name": "name", "displayName": "Name", "shape": "string" }, + { + "name": "username", + "displayName": "Username", + "shape": "string", + "role": "label" + }, + { "name": "email", "displayName": "Email", "shape": "string" }, + { "name": "role", "displayName": "Role", "shape": "string" }, + { "name": "confirmed", "displayName": "Confirmed", "shape": "boolean" }, + { "name": "createdAt", "displayName": "Created", "shape": "date" } + ], + "timeframes": false +} diff --git a/plugins/Vercel/v1/dataStreams/teams.json b/plugins/Vercel/v1/dataStreams/teams.json new file mode 100644 index 00000000..6acafe5f --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/teams.json @@ -0,0 +1,28 @@ +{ + "name": "teams", + "displayName": "Teams", + "description": "Lists the Vercel teams the configured token can access", + "tags": ["Teams"], + "baseDataSourceName": "httpRequestUnscoped", + "config": { + "httpMethod": "get", + "endpointPath": "v2/teams", + "pathToData": "teams", + "expandInnerObjects": true, + "paging": { + "mode": "token", + "pageSize": { "realm": "queryArg", "path": "limit", "value": "100" }, + "in": { "realm": "payload", "path": "pagination.next" }, + "out": { "realm": "queryArg", "path": "until" } + } + }, + "matches": "none", + "metadata": [ + { "name": "id", "displayName": "ID", "visible": false }, + { "name": "name", "displayName": "Name", "role": "label" }, + { "name": "slug", "displayName": "Slug" }, + { "name": "membership.role", "displayName": "Your Role" }, + { "name": "createdAt", "displayName": "Created", "shape": "date" } + ], + "timeframes": false +} diff --git a/plugins/Vercel/v1/defaultContent/activity.dash.json b/plugins/Vercel/v1/defaultContent/activity.dash.json new file mode 100644 index 00000000..b672b3c6 --- /dev/null +++ b/plugins/Vercel/v1/defaultContent/activity.dash.json @@ -0,0 +1,177 @@ +{ + "name": "Activity & Team", + "schemaVersion": "1.5", + "timeframe": "last7days", + "dashboard": { + "_type": "layout/grid", + "version": 115, + "contents": [ + { + "static": false, + "w": 3, + "moved": false, + "h": 3, + "x": 0, + "y": 0, + "i": "4c0a11bb-6c6b-481b-951c-fe4586313ea4", + "z": 0, + "config": { + "dataStream": { + "name": "activity", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[activity]}}", + "sort": { + "by": [["createdAt", "desc"]] + } + }, + "_type": "tile/data-stream", + "description": "Account events", + "activePluginConfigIds": ["{{configId}}"], + "title": "Recent Activity", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "createdAt", + "type", + "text", + "actor" + ], + "hiddenColumns": [ + "id", + "user.username", + "user.email", + "userId" + ], + "transpose": false + } + } + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 3, + "x": 3, + "y": 0, + "i": "9838709b-83d2-4542-85a7-6458352f4d93", + "z": 0, + "config": { + "timeframe": "none", + "dataStream": { + "name": "teamMembers", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[teamMembers]}}", + "sort": { + "by": [["name", "asc"]] + } + }, + "_type": "tile/data-stream", + "description": "Members of the configured team", + "activePluginConfigIds": ["{{configId}}"], + "title": "Team Roster", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": ["username", "role"], + "hiddenColumns": [ + "uid", + "createdAt", + "email", + "name", + "confirmed" + ], + "transpose": false + } + } + } + } + }, + { + "static": false, + "w": 4, + "moved": false, + "h": 2, + "x": 0, + "y": 3, + "i": "fada223a-5939-4b55-bc61-3ef63deca7d2", + "z": 0, + "config": { + "timeframe": "none", + "dataStream": { + "name": "teams", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[teams]}}", + "sort": { + "by": [["name", "asc"]] + } + }, + "_type": "tile/data-stream", + "description": "Teams accessible to the configured token", + "activePluginConfigIds": ["{{configId}}"], + "title": "Teams", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "name", + "slug", + "membership.role", + "createdAt" + ], + "hiddenColumns": ["id"], + "transpose": false + } + } + } + } + }, + { + "static": false, + "w": 4, + "moved": false, + "h": 3, + "x": 0, + "y": 5, + "i": "34bbffe0-fdcd-479a-8c54-7757030d354a", + "z": 0, + "config": { + "dataStream": { + "name": "activity", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[activity]}}", + "sort": {}, + "group": { + "by": [["createdAt", "byDay"]], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Daily activity", + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "seriesColumn": "none", + "xAxisColumn": "createdAt_byDay", + "yAxisColumn": ["count"] + } + } + } + } + } + ], + "columns": 4 + } +} diff --git a/plugins/Vercel/v1/defaultContent/cost.dash.json b/plugins/Vercel/v1/defaultContent/cost.dash.json new file mode 100644 index 00000000..d2eebea9 --- /dev/null +++ b/plugins/Vercel/v1/defaultContent/cost.dash.json @@ -0,0 +1,161 @@ +{ + "name": "Cost", + "schemaVersion": "1.5", + "timeframe": "last7days", + "dashboard": { + "_type": "layout/grid", + "contents": [ + { + "static": false, + "w": 2, + "moved": false, + "h": 4, + "x": 0, + "y": 0, + "i": "0f2bc2a4-cb0b-41a1-a4fb-9b0e29e8a088", + "z": 0, + "config": { + "timeframe": "last7days", + "dataStream": { + "name": "cost", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[cost]}}", + "sort": { + "by": [["billedCost_sum", "desc"]] + }, + "group": { + "by": [["service", "uniqueValues"]], + "aggregate": [ + { + "type": "sum", + "names": ["billedCost"] + } + ] + } + }, + "_type": "tile/data-stream", + "description": "Billed cost grouped by service", + "activePluginConfigIds": ["{{configId}}"], + "title": "Cost by Service", + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "billedCost_sum", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "auto", + "legendMode": "table", + "labelColumn": "service_uniqueValues" + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "h": 4, + "x": 2, + "y": 0, + "i": "64bb0cfc-e350-43e6-a047-5fa04377cc10", + "z": 0, + "config": { + "timeframe": "last7days", + "dataStream": { + "name": "cost", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[cost]}}", + "sort": { + "top": 10, + "by": [["billedCost_sum", "desc"]] + }, + "group": { + "by": [["projectName", "uniqueValues"]], + "aggregate": [ + { + "type": "sum", + "names": ["billedCost"] + } + ] + } + }, + "_type": "tile/data-stream", + "description": "Billed cost grouped by project", + "activePluginConfigIds": ["{{configId}}"], + "title": "Cost by Project", + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisGroup": "none", + "showLegend": false, + "range": { + "type": "auto" + }, + "showGrid": true, + "grouping": false, + "xAxisData": "projectName_uniqueValues", + "displayMode": "actual", + "yAxisLabel": "Billed Cost ($)", + "horizontalLayout": "horizontal", + "yAxisData": ["billedCost_sum"], + "showYAxisLabel": true, + "legendPosition": "bottom", + "showXAxisLabel": false + } + } + } + } + }, + { + "static": false, + "w": 4, + "moved": false, + "h": 3, + "x": 0, + "y": 4, + "i": "731acf15-16ed-4e1a-baa9-7f3d992e172f", + "z": 0, + "config": { + "timeframe": "last7days", + "dataStream": { + "name": "cost", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[cost]}}", + "sort": {}, + "group": { + "by": [["periodStart", "byDay"]], + "aggregate": [ + { + "type": "sum", + "names": ["billedCost"] + } + ] + } + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Cost by day", + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "seriesColumn": "none", + "dataPoints": true, + "showTrendLine": true, + "cumulative": false, + "xAxisColumn": "service_uniqueValues", + "yAxisColumn": ["billedCost_sum"] + } + } + } + } + } + ], + "version": 16, + "columns": 4 + } +} diff --git a/plugins/Vercel/v1/defaultContent/deployments.dash.json b/plugins/Vercel/v1/defaultContent/deployments.dash.json new file mode 100644 index 00000000..6eb0ed17 --- /dev/null +++ b/plugins/Vercel/v1/defaultContent/deployments.dash.json @@ -0,0 +1,297 @@ +{ + "name": "Deployments", + "schemaVersion": "1.5", + "timeframe": "last30days", + "dashboard": { + "_type": "layout/grid", + "version": 79, + "contents": [ + { + "w": 2, + "h": 3, + "x": 0, + "y": 0, + "z": 0, + "i": "8880ebf2-9570-488b-ab5f-6cfeed0c1727", + "moved": false, + "static": false, + "config": { + "dataStream": { + "name": "deployments", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "or", + "filters": [] + }, + "id": "{{dataStreams.[deployments]}}", + "sort": { + "top": 10, + "by": [["created", "asc"]] + }, + "group": { + "by": [], + "aggregate": [] + } + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Latest Builds (Top 10)", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "state", + "name", + "url", + "created", + "source", + "readyState", + "readySubstate", + "type", + "creator", + "inspectorUrl", + "attribution.commitMeta.name", + "attribution.commitMeta.email", + "attribution.commitMeta.isVerified", + "attribution.gitUser.id", + "attribution.gitUser.login", + "attribution.gitUser.type", + "attribution.gitUser.provider", + "attribution.vercelUser.id", + "attribution.vercelUser.username", + "attribution.vercelUser.teamRoles.0", + "meta.githubCommitAuthorName", + "meta.githubCommitAuthorEmail", + "meta.githubCommitMessage", + "meta.githubCommitOrg", + "meta.githubCommitRef", + "meta.githubCommitRepo", + "meta.githubCommitSha", + "meta.githubDeployment", + "meta.githubOrg", + "meta.githubRepo", + "meta.githubRepoOwnerType", + "meta.githubCommitRepoId", + "meta.githubRepoId", + "meta.githubRepoVisibility", + "meta.githubHost", + "meta.githubCommitAuthorLogin", + "meta.githubCommitVerification", + "meta.repoPushedAt", + "meta.branchAlias", + "meta.lambdaRuntimeStats", + "target", + "aliasError", + "aliasAssigned", + "isRollbackCandidate", + "createdAt", + "buildingAt", + "ready", + "projectSettings.commandForIgnoringBuildStep", + "meta.githubPrId", + "state[Expanded].rawState" + ], + "hiddenColumns": ["uid", "projectId"] + } + } + } + } + }, + { + "w": 2, + "h": 3, + "x": 2, + "y": 0, + "z": 0, + "i": "69617b87-c49a-43a7-a4ec-4b60e2cdd16e", + "moved": false, + "static": false, + "config": { + "dataStream": { + "name": "deployments", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "or", + "filters": [ + { + "column": "state", + "operation": "equals", + "value": "error" + } + ] + }, + "id": "{{dataStreams.[deployments]}}", + "group": { + "by": [], + "aggregate": [] + } + }, + "scope": { + "query": "g.V().has('id', within(ids_defaultScopeIds))", + "bindings": { + "ids_defaultScopeIds": [ + "node-1dwkxV4HX7tOQS5MZOwLQEJ7OAPUH1yXV3Crp-aqMqx0SfUpUnwez0xGyS" + ] + }, + "queryDetail": { + "ids": [ + "node-1dwkxV4HX7tOQS5MZOwLQEJ7OAPUH1yXV3Crp-aqMqx0SfUpUnwez0xGyS" + ], + "types": [ + { + "value": "data-source" + } + ] + } + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Latest Failed Builds", + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "stateColumn": "state", + "linkColumn": "inspectorUrl", + "sublabel": "url", + "labelColumn": "name" + } + } + } + } + }, + { + "w": 2, + "h": 3, + "x": 0, + "y": 3, + "z": 0, + "i": "ece1c11a-be71-4a6f-a1f2-ed56c15081d7", + "moved": false, + "static": false, + "config": { + "dataStream": { + "name": "deployments", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[deployments]}}", + "sort": { + "by": [["created_byDay", "asc"]] + }, + "group": { + "by": [ + ["created", "byDay"], + ["state", "uniqueValues"] + ], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "_type": "tile/data-stream", + "description": "Deployments per day, split by state", + "activePluginConfigIds": ["{{configId}}"], + "title": "Deployments per day", + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "color": { + "type": "default" + }, + "xAxisGroup": "state_uniqueValues", + "showLegend": true, + "range": { + "type": "auto" + }, + "showGrid": true, + "grouping": false, + "xAxisData": "created_byDay", + "displayMode": "actual", + "showTotals": true, + "yAxisLabel": "Deployments", + "horizontalLayout": "vertical", + "showValue": false, + "yAxisData": ["count"], + "showYAxisLabel": true, + "xAxisLabel": "", + "legendPosition": "bottom", + "showXAxisLabel": false + } + } + } + } + }, + { + "w": 2, + "h": 3, + "x": 2, + "y": 3, + "z": 0, + "i": "d2eaaf90-d384-478a-b14d-dc955bbdeb3e", + "moved": false, + "static": false, + "config": { + "title": "Average build times", + "description": "", + "_type": "tile/data-stream", + "activePluginConfigIds": ["{{configId}}"], + "dataStream": { + "id": "datastream-sql", + "dataSourceConfig": { + "version": "2.0", + "sql": "WITH\r\n \"build_times\" AS (\r\n SELECT\r\n STRFTIME(\r\n DATE_TRUNC('day', \"createdAt\"),\r\n '%Y-%m-%dT%H:%M:%SZ'\r\n ) AS \"date\",\r\n \"name\",\r\n EXTRACT (\r\n EPOCH\r\n FROM\r\n \"buildingAt\" - \"createdAt\"\r\n ) AS \"build_time\"\r\n FROM\r\n \"dataset1\"\r\n )\r\nSELECT\r\n \"date\",\r\n \"name\",\r\n AVG(\"build_time\") AS \"avg_build_time\"\r\nFROM\r\n \"build_times\"\r\nGROUP BY\r\n \"date\",\r\n \"name\"", + "tables": [ + { + "tableName": "dataset1", + "config": { + "activePluginConfigIds": [ + "{{configId}}" + ], + "dataStream": { + "id": "{{dataStreams.[deployments]}}", + "name": "deployments" + } + } + } + ] + }, + "metadata": [ + { + "shape": [ + "seconds", + { + "thousandsSeparator": true, + "formatDuration": true + } + ], + "name": "avg_build_time" + }, + { + "pattern": ".*" + } + ] + }, + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "xAxisColumn": "date", + "yAxisColumn": ["avg_build_time"], + "seriesColumn": "name", + "showLegend": true, + "legendPosition": "bottom", + "dataPoints": true + } + } + } + } + } + ], + "columns": 4 + } +} diff --git a/plugins/Vercel/v1/defaultContent/domain.dash.json b/plugins/Vercel/v1/defaultContent/domain.dash.json new file mode 100644 index 00000000..5e8cb0a8 --- /dev/null +++ b/plugins/Vercel/v1/defaultContent/domain.dash.json @@ -0,0 +1,98 @@ +{ + "name": "Domain", + "schemaVersion": "1.5", + "timeframe": "none", + "variables": ["{{variables.[Vercel Domain]}}"], + "dashboard": { + "_type": "layout/grid", + "columns": 4, + "version": 1, + "contents": [ + { + "i": "2d1a775f-5d77-4246-a727-47b4b22c0cc7", + "x": 0, + "y": 0, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Configuration Health", + "description": "DNS / nameserver configuration for this domain", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "{{dataStreams.[domainConfig]}}", + "name": "domainConfig", + "pluginConfigId": "{{configId}}" + }, + "scope": { + "scope": "{{scopes.[Vercel Domains]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Vercel Domain]}}" + }, + "variables": ["{{variables.[Vercel Domain]}}"], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "columnOrder": ["misconfigured", "serviceType", "configuredBy"] + } + } + }, + "monitorOld": { + "_type": "simple", + "monitorType": "threshold", + "aggregation": "count", + "groupBy": "__group_by_none__", + "frequency": 15, + "tileRollsUp": true, + "condition": { + "columns": ["misconfigured"], + "logic": { "if": [{ "==": [{ "var": "misconfigured" }, true] }, "error"] } + } + } + } + }, + { + "i": "da5c6f6d-e225-4de1-90c8-873e444e2fb2", + "x": 2, + "y": 0, + "w": 2, + "h": 3, + "moved": false, + "static": false, + "z": 0, + "config": { + "_type": "tile/data-stream", + "title": "Domain Details", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "timeframe": "none", + "dataStream": { + "id": "datastream-properties", + "name": "properties" + }, + "scope": { + "scope": "{{scopes.[Vercel Domains]}}", + "workspace": "{{workspaceId}}", + "variable": "{{variables.[Vercel Domain]}}" + }, + "variables": ["{{variables.[Vercel Domain]}}"], + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true, + "hiddenColumns": ["id", "link", "links", "label"] + } + } + } + } + } + ] + } +} diff --git a/plugins/Vercel/v1/defaultContent/manifest.json b/plugins/Vercel/v1/defaultContent/manifest.json new file mode 100644 index 00000000..0fcc169c --- /dev/null +++ b/plugins/Vercel/v1/defaultContent/manifest.json @@ -0,0 +1,10 @@ +{ + "items": [ + { "name": "overview", "type": "dashboard" }, + { "name": "cost", "type": "dashboard" }, + { "name": "deployments", "type": "dashboard" }, + { "name": "activity", "type": "dashboard" }, + { "name": "project", "type": "dashboard" }, + { "name": "domain", "type": "dashboard" } + ] +} diff --git a/plugins/Vercel/v1/defaultContent/overview.dash.json b/plugins/Vercel/v1/defaultContent/overview.dash.json new file mode 100644 index 00000000..dae24374 --- /dev/null +++ b/plugins/Vercel/v1/defaultContent/overview.dash.json @@ -0,0 +1,305 @@ +{ + "name": "Overview", + "schemaVersion": "1.5", + "timeframe": "last7days", + "dashboard": { + "_type": "layout/grid", + "version": 95, + "contents": [ + { + "static": false, + "w": 1, + "moved": false, + "h": 3, + "x": 0, + "y": 0, + "i": "fa9e69dc-7d70-450b-b973-1a215df3a398", + "z": 0, + "config": { + "timeframe": "none", + "dataStream": { + "name": "projects", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[projects]}}", + "sort": { + "by": [["count", "desc"]] + }, + "group": { + "by": [["framework", "uniqueValues"]], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Projects by Framework", + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "count", + "hideCenterValue": false, + "showValuesAsPercentage": false, + "legendPosition": "auto", + "legendMode": "table", + "labelColumn": "framework_uniqueValues" + } + } + } + } + }, + { + "static": false, + "w": 3, + "moved": false, + "h": 3, + "x": 1, + "y": 0, + "i": "3850d290-308d-44a1-81de-aeff5200d0ba", + "z": 0, + "config": { + "timeframe": "none", + "dataStream": { + "name": "projects", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[proejcts]}}", + "sort": { + "by": [["updatedAt", "desc"]] + } + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Projects", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "name", + "framework", + "nodeVersion", + "updatedAt" + ], + "hiddenColumns": [ + "id", + "sourceType", + "createdAt" + ], + "transpose": false + } + } + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 3, + "x": 0, + "y": 3, + "i": "f3ff120e-5ed1-414f-b57e-a2982d25ebbe", + "z": 0, + "config": { + "timeframe": "last7days", + "dataStream": { + "name": "cost", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[cost]}}", + "sort": { + "top": 10, + "by": [] + }, + "group": { + "by": [], + "aggregate": [ + { + "type": "sum", + "names": ["billedCost"] + } + ] + } + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Overall Cost", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "billedCost_sum", + "comparisonColumn": "none" + } + } + } + } + }, + { + "static": false, + "w": 3, + "moved": false, + "h": 3, + "x": 1, + "y": 3, + "i": "098760ae-37bf-485c-8b14-605051f34733", + "z": 0, + "config": { + "timeframe": "last7days", + "dataStream": { + "name": "cost", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[cost]}}", + "sort": { + "top": 10, + "by": [["billedCost_sum", "desc"]] + }, + "group": { + "by": [["projectName", "uniqueValues"]], + "aggregate": [ + { + "type": "sum", + "names": ["billedCost"] + } + ] + } + }, + "_type": "tile/data-stream", + "description": "Billed cost grouped by project", + "activePluginConfigIds": ["{{configId}}"], + "title": "Cost by Project", + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "color": { + "type": "default" + }, + "xAxisGroup": "none", + "showLegend": false, + "range": { + "type": "auto" + }, + "showGrid": true, + "grouping": false, + "xAxisData": "projectName_uniqueValues", + "displayMode": "actual", + "showTotals": false, + "yAxisLabel": "Billed Cost ($)", + "horizontalLayout": "horizontal", + "showValue": false, + "yAxisData": ["billedCost_sum"], + "showYAxisLabel": true, + "xAxisLabel": "", + "legendPosition": "bottom", + "showXAxisLabel": false + } + } + } + } + }, + { + "static": false, + "w": 1, + "moved": false, + "h": 2, + "x": 0, + "y": 6, + "i": "60fe71cb-be1f-4627-9eb7-c4aa7fe10feb", + "z": 0, + "config": { + "timeframe": "none", + "dataStream": { + "name": "domains", + "pluginConfigId": "{{configId}}", + "filter": { + "multiOperation": "and", + "filters": [ + { + "column": "verified", + "operation": "equals", + "value": "false" + } + ] + }, + "id": "{{dataStreams.[domains]}}", + "group": { + "by": [], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "_type": "tile/data-stream", + "description": "Domains that are not verified", + "activePluginConfigIds": ["{{configId}}"], + "title": "Unverified Domains", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": "count", + "comparisonColumn": "none", + "label": "Unverified" + } + } + } + } + }, + { + "static": false, + "w": 3, + "moved": false, + "h": 2, + "x": 1, + "y": 6, + "i": "f2843ac7-c436-4ad4-a6c8-5e5cb570420b", + "z": 0, + "config": { + "timeframe": "none", + "dataStream": { + "name": "domains", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[domains]}}", + "sort": { + "by": [["expiresAt", "asc"]] + } + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Domain Inventory", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "name", + "verified", + "serviceType", + "renew", + "expiresAt" + ], + "hiddenColumns": [ + "id", + "sourceType", + "boughtAt", + "createdAt" + ], + "transpose": false + } + } + } + } + } + ], + "columns": 4 + } +} diff --git a/plugins/Vercel/v1/defaultContent/project.dash.json b/plugins/Vercel/v1/defaultContent/project.dash.json new file mode 100644 index 00000000..95714503 --- /dev/null +++ b/plugins/Vercel/v1/defaultContent/project.dash.json @@ -0,0 +1,356 @@ +{ + "name": "Project", + "schemaVersion": "1.5", + "timeframe": "last7days", + "variables": ["{{variables.[Vercel Project]}}"], + "dashboard": { + "_type": "layout/grid", + "version": 29, + "contents": [ + { + "w": 1, + "h": 2, + "x": 0, + "y": 0, + "z": 0, + "i": "05a07731-9c30-4908-95b4-94f9f345d54c", + "moved": false, + "static": false, + "config": { + "variables": ["{{variables.[Vercel Project]}}"], + "dataStream": { + "name": "deployments", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "project": { + "variable": "{{variables.[Vercel Project]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Vercel Projects]}}" + } + }, + "id": "{{dataStreams.[deployments]}}", + "sort": { + "top": 1, + "by": [["created", "desc"]] + } + }, + "_type": "tile/data-stream", + "description": "Most recent deployment for this project", + "activePluginConfigIds": ["{{configId}}"], + "title": "Latest Deployment State", + "visualisation": { + "type": "data-stream-blocks", + "config": { + "data-stream-blocks": { + "stateColumn": "state", + "linkColumn": "inspectorUrl", + "sublabel": "target", + "columns": 1, + "labelColumn": "name" + } + } + } + } + }, + { + "w": 1, + "h": 2, + "x": 1, + "y": 0, + "z": 0, + "i": "d04fd358-ff9f-41f2-a7ea-5398a4cd34b9", + "moved": false, + "static": false, + "config": { + "variables": ["{{variables.[Vercel Project]}}"], + "dataStream": { + "name": "deployments", + "pluginConfigId": "{{configId}}", + "filter": { + "filters": [ + { + "column": "state", + "operation": "equals", + "value": "error" + } + ], + "multiOperation": "or" + }, + "dataSourceConfig": { + "project": { + "variable": "{{variables.[Vercel Project]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Vercel Projects]}}" + } + }, + "id": "{{dataStreams.[deployments]}}", + "group": { + "by": [], + "aggregate": [] + }, + "sort": {} + }, + "_type": "tile/data-stream", + "description": "Deployments in an error state for this project", + "activePluginConfigIds": ["{{configId}}"], + "title": "Failed Deployments", + "visualisation": { + "type": "data-stream-scalar", + "config": { + "data-stream-scalar": { + "value": { + "type": "count" + }, + "comparisonColumn": "none", + "label": "Failed" + } + } + } + } + }, + { + "w": 2, + "h": 2, + "x": 2, + "y": 0, + "z": 0, + "i": "508189d0-5357-4722-8701-09b082e20c35", + "moved": false, + "static": false, + "config": { + "variables": ["{{variables.[Vercel Project]}}"], + "dataStream": { + "name": "deployments", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "project": { + "variable": "{{variables.[Vercel Project]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Vercel Projects]}}" + } + }, + "id": "{{dataStreams.[deployments]}}", + "sort": { + "by": [["created_byDay", "asc"]] + }, + "group": { + "by": [ + ["created", "byDay"], + ["state", "uniqueValues"] + ], + "aggregate": [ + { + "type": "count" + } + ] + } + }, + "_type": "tile/data-stream", + "description": "Deployments per day for this project", + "activePluginConfigIds": ["{{configId}}"], + "title": "Deployment Volume", + "visualisation": { + "type": "data-stream-bar-chart", + "config": { + "data-stream-bar-chart": { + "xAxisGroup": "state_uniqueValues", + "showLegend": false, + "range": { + "type": "auto" + }, + "showGrid": true, + "grouping": false, + "xAxisData": "created_byDay", + "displayMode": "actual", + "yAxisLabel": "Deployments", + "horizontalLayout": "vertical", + "yAxisData": ["count"], + "showYAxisLabel": true, + "legendPosition": "bottom", + "showXAxisLabel": false, + "showValue": false, + "xAxisLabel": "", + "color": { + "type": "default" + }, + "showTotals": true + } + } + } + } + }, + { + "w": 4, + "h": 3, + "x": 0, + "y": 2, + "z": 0, + "i": "b2a0e5d2-746d-48e2-b294-0cd82105f86c", + "moved": false, + "static": false, + "config": { + "variables": ["{{variables.[Vercel Project]}}"], + "dataStream": { + "name": "deployments", + "pluginConfigId": "{{configId}}", + "dataSourceConfig": { + "project": { + "variable": "{{variables.[Vercel Project]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Vercel Projects]}}" + } + }, + "id": "{{dataStreams.[deployments]}}", + "sort": { + "by": [["created", "desc"]] + } + }, + "_type": "tile/data-stream", + "description": "Deployments for this project", + "activePluginConfigIds": ["{{configId}}"], + "title": "Deployment History", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "created", + "name", + "state", + "target", + "creator", + "inspectorUrl" + ], + "hiddenColumns": ["uid", "projectId"], + "transpose": false + } + } + } + } + }, + { + "w": 1, + "h": 3, + "x": 0, + "y": 5, + "z": 0, + "i": "378b1b08-9b44-4564-a83f-f59f2d02b9f8", + "moved": false, + "static": false, + "config": { + "timeframe": "none", + "variables": ["{{variables.[Vercel Project]}}"], + "dataStream": { + "name": "projectInfo", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[projectInfo]}}" + }, + "scope": { + "variable": "{{variables.[Vercel Project]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Vercel Projects]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Project Info", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "name", + "framework", + "nodeVersion", + "link.repo", + "link.type", + "createdAt", + "updatedAt" + ], + "transpose": true + } + } + } + } + }, + { + "w": 1, + "h": 3, + "x": 1, + "y": 5, + "z": 0, + "i": "6a4aa9a9-0870-452b-a66b-83917f0641da", + "moved": false, + "static": false, + "config": { + "title": "Firewall Event Actions", + "description": "", + "_type": "tile/data-stream", + "dataStream": { + "id": "{{dataStreams.[firewallEvents]}}", + "name": "firewallEvents" + }, + "scope": { + "variable": "{{variables.[Vercel Project]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Vercel Projects]}}" + }, + "visualisation": { + "type": "data-stream-donut-chart", + "config": { + "data-stream-donut-chart": { + "valueColumn": "count", + "labelColumn": "action" + } + } + }, + "activePluginConfigIds": ["{{configId}}"], + "variables": ["{{variables.[Vercel Project]}}"] + } + }, + { + "w": 2, + "h": 3, + "x": 2, + "y": 5, + "z": 0, + "i": "97508e56-5d5b-4716-9223-8a3903c5d3da", + "moved": false, + "static": false, + "config": { + "title": "Firewall events", + "description": "", + "_type": "tile/data-stream", + "dataStream": { + "id": "{{dataStreams.[firewallEvents]}}", + "name": "firewallEvents" + }, + "scope": { + "variable": "{{variables.[Vercel Project]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Vercel Projects]}}" + }, + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "hiddenColumns": ["isActive", "action_type"], + "columnOrder": [ + "action", + "count", + "host", + "startTime", + "endTime", + "public_ip" + ] + } + } + }, + "activePluginConfigIds": ["{{configId}}"], + "variables": ["{{variables.[Vercel Project]}}"] + } + } + ], + "columns": 4 + } +} diff --git a/plugins/Vercel/v1/defaultContent/scopes.json b/plugins/Vercel/v1/defaultContent/scopes.json new file mode 100644 index 00000000..164d0191 --- /dev/null +++ b/plugins/Vercel/v1/defaultContent/scopes.json @@ -0,0 +1,26 @@ +[ + { + "name": "Vercel Projects", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Vercel Project"] } + }, + "variable": { + "name": "Vercel Project", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + }, + { + "name": "Vercel Domains", + "matches": { + "sourceType": { "type": "oneOf", "values": ["Vercel Domain"] } + }, + "variable": { + "name": "Vercel Domain", + "allowMultipleSelection": false, + "default": "none", + "type": "object" + } + } +] diff --git a/plugins/Vercel/v1/docs/README.md b/plugins/Vercel/v1/docs/README.md new file mode 100644 index 00000000..f1081f25 --- /dev/null +++ b/plugins/Vercel/v1/docs/README.md @@ -0,0 +1,57 @@ +# Vercel + +Monitor your [Vercel](https://vercel.com) projects, deployments, and domains in SquaredUp. This plugin connects to the Vercel REST API to import your projects and domains as objects you can scope dashboards to, and provides data streams for deployment health and history, domain verification and expiry, team membership and activity, and a billing-based cost overview. + +## What this plugin monitors + +- **Projects** — your Vercel projects, including framework, git repository, and latest production deployment status. Imported as objects. +- **Domains** — custom domains, including verification status, expiry/renewal dates, and configuration health. Imported as objects. +- **Deployments** — deployment volume, success/failure rates, and history over time, account-wide or drilled down per project. (Deployments are *not* indexed as objects, because they change too frequently — they are available as data streams instead.) +- **Team & activity** — team membership roster and recent account activity (events). +- **Cost** — a cost/usage overview derived from Vercel billing data (daily granularity). +- **Firewall / security** — per-project [Vercel Firewall (WAF)](https://vercel.com/docs/vercel-firewall) posture: whether the firewall is enabled, which managed protections (OWASP-style CRS categories, bot protection, AI bots) are active and their action, plus active attack anomalies over time. Available as data streams on the **Project** perspective. + +The plugin ships with three out-of-the-box dashboards: an account **Overview**, a **Project** perspective (which includes a **Security** section), and a **Domain** perspective. + +## Prerequisites — getting a Vercel Access Token + +1. Sign in to Vercel and open **Account Settings → Tokens** (). +2. Click **Create Token**. +3. Give it a name (e.g. `SquaredUp`). +4. **Scope** — choose the scope the token can access: + - To monitor a **Team**, set the scope to that team and note the team's ID or slug (see the `Team ID` field below). + - To monitor your **personal account**, scope it to your personal account. +5. **Expiration** — choose an expiration (or no expiration). If the token expires, the plugin's data streams will stop returning data until you supply a new token. +6. Click **Create** and copy the token value immediately — Vercel only shows it once. + +For the **cost overview** and **team members** streams, the token must belong to a Team and carry a role with billing/member visibility (Owner, Member, Developer, Security, or Billing). On personal/Hobby accounts these streams may return no data. + +### Finding your Team ID + +If you are monitoring a Team, open **Team Settings → General** in Vercel; the **Team ID** (format `team_xxxxxxxx`) is shown there. Enter that value in the `Team ID` field. (Leave the field blank to monitor your personal account instead.) + +## Configuration fields + +| Field | Required | Description | Where to find it | +| --- | --- | --- | --- | +| **API Token** | Yes | The Vercel Access Token used to authenticate. Sent as a bearer token on every request. | Account Settings → Tokens (see above). | +| **Team ID** | No | The Team ID or slug to monitor. Leave blank to monitor your personal account. When set, all requests are scoped to this team. | Team Settings → General (`team_…`), or the slug in your Vercel URL. | + +## What gets indexed + +The plugin imports two object types into the SquaredUp graph: + +- **Vercel Project** — one object per project in the configured account/team. Carries the project name, framework, git repository link, and identifiers. +- **Vercel Domain** — one object per custom domain. Carries the domain name, verification status, and expiry information. + +Deployments, teams, members, activity events, and cost data are provided as **data streams** (not indexed objects) — query them on dashboards and scope deployment streams to a project. + +## 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 is daily granularity** and the billing endpoint returns very large datasets, so the cost stream is restricted to the **Last 24 hours** and **Last 7 days** timeframes. +- **Team members and cost require a Team** and a token with adequate role/plan. On personal/Hobby accounts these streams may be empty. +- **Firewall streams only return data for projects with a configured firewall.** The Vercel Firewall config endpoint returns a 404 (not an empty result) for any project that has never configured a firewall, so the firewall posture and managed-rules tiles render only for projects where the WAF has been set up; for other projects the tile shows no data. The attack-anomalies stream returns an empty result (not an error) when there is no active attack. +- **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. diff --git a/plugins/Vercel/v1/icon.svg b/plugins/Vercel/v1/icon.svg new file mode 100644 index 00000000..73961ba7 --- /dev/null +++ b/plugins/Vercel/v1/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/plugins/Vercel/v1/indexDefinitions/default.json b/plugins/Vercel/v1/indexDefinitions/default.json new file mode 100644 index 00000000..7642ba9b --- /dev/null +++ b/plugins/Vercel/v1/indexDefinitions/default.json @@ -0,0 +1,24 @@ +{ + "steps": [ + { + "name": "projects", + "dataStream": { "name": "projects" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": "sourceType" + } + }, + { + "name": "domains", + "dataStream": { "name": "domains" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": "sourceType" + } + } + ] +} diff --git a/plugins/Vercel/v1/metadata.json b/plugins/Vercel/v1/metadata.json new file mode 100644 index 00000000..675c824b --- /dev/null +++ b/plugins/Vercel/v1/metadata.json @@ -0,0 +1,40 @@ +{ + "name": "vercel", + "displayName": "Vercel", + "version": "1.2.0", + "author": { "name": "Andrew Harris", "type": "community" }, + "description": "Monitor your Vercel projects, deployments, and domains in SquaredUp — track deployment health and history, domain verification and expiry, team activity, a cost overview, and firewall (WAF) security posture.", + "category": "Cloud Platforms", + "type": "hybrid", + "schemaVersion": "2.1", + "importNotSupported": false, + "restrictedToPlatforms": [], + "keywords": ["vercel", "deployments", "hosting", "frontend", "projects", "domains", "security", "firewall", "waf"], + "objectTypes": ["Vercel Project", "Vercel Domain"], + "links": [ + { + "category": "documentation", + "url": "https://github.com/squaredup/plugins/blob/main/plugins/Vercel/v1/docs/README.md", + "label": "Help adding this plugin" + }, + { + "category": "source", + "url": "https://github.com/squaredup/plugins/tree/main/plugins/Vercel", + "label": "Repository" + } + ], + "base": { + "plugin": "WebAPI", + "majorVersion": "1", + "config": { + "baseUrl": "https://api.vercel.com", + "authMode": "none", + "headers": [ + { "key": "Authorization", "value": "Bearer {{accessToken}}" } + ], + "queryArgs": [ + { "key": "teamId", "value": "{{teamId}}" } + ] + } + } +} diff --git a/plugins/Vercel/v1/ui.json b/plugins/Vercel/v1/ui.json new file mode 100644 index 00000000..86f3d3b9 --- /dev/null +++ b/plugins/Vercel/v1/ui.json @@ -0,0 +1,21 @@ +[ + { + "type": "markdown", + "name": "info", + "content": "Create a Vercel **Access Token** in [Account Settings → Tokens](https://vercel.com/account/tokens) and paste it below. To monitor a Team, also provide its **Team ID**; leave it blank to monitor your personal account." + }, + { + "type": "password", + "name": "accessToken", + "label": "API Token", + "help": "A Vercel Access Token. Sent as a bearer token on every request. Create one at [Account Settings → Tokens](https://vercel.com/account/tokens).", + "validation": { "required": true } + }, + { + "type": "text", + "name": "teamId", + "label": "Team ID", + "placeholder": "team_xxxxxxxxxxxxxxxx", + "help": "Optional. The ID of the Vercel Team to monitor (found at Team Settings → General, format `team_…`). Leave blank to monitor your personal account." + } +] From 138fe02a66a04be5e0a7fa7d71533440fca5efe1 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Tue, 16 Jun 2026 14:53:28 +0100 Subject: [PATCH 02/23] Remove unused listDeployments dataStream --- .../v1/dataStreams/listDeployments.json | 51 ------------------- plugins/Vercel/v1/docs/README.md | 4 +- plugins/Vercel/v1/metadata.json | 20 +++++--- 3 files changed, 16 insertions(+), 59 deletions(-) delete mode 100644 plugins/Vercel/v1/dataStreams/listDeployments.json diff --git a/plugins/Vercel/v1/dataStreams/listDeployments.json b/plugins/Vercel/v1/dataStreams/listDeployments.json deleted file mode 100644 index 2da76017..00000000 --- a/plugins/Vercel/v1/dataStreams/listDeployments.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "deployments", - "displayName": "Deployments", - "description": "Vercel deployments across the account or a selected project, one row per deployment. Backs deployment health and history tiles", - "tags": ["Deployments"], - "baseDataSourceName": "httpRequestUnscoped", - "config": { - "httpMethod": "get", - "endpointPath": "v7/deployments", - "postRequestScript": "deployments.js", - "getArgs": [ - { "key": "since", "value": "{{timeframe.unixStart * 1000}}" } - ], - "paging": { - "mode": "token", - "pageSize": { - "realm": "queryArg", - "path": "limit", - "value": "100" - }, - "in": { "realm": "payload", "path": "pagination.next" }, - "out": { "realm": "queryArg", "path": "until" } - } - }, - "matches": "none", - "ui": [ - { - "type": "objects", - "name": "project", - "label": "Project (optional)", - "matches": { - "sourceType": { "type": "equals", "value": "Vercel Project" } - } - } - ], - "metadata": [ - { - "name": "uid", - "displayName": "ID", - "role": "value" - }, - { - "name": "label", - "displayName": "Label", - "role": "label", - "valueExpression": "{{$['status']}}" - }, - { "pattern": ".*" } - ], - "timeframes": true -} diff --git a/plugins/Vercel/v1/docs/README.md b/plugins/Vercel/v1/docs/README.md index f1081f25..a1fa5ca1 100644 --- a/plugins/Vercel/v1/docs/README.md +++ b/plugins/Vercel/v1/docs/README.md @@ -9,7 +9,7 @@ Monitor your [Vercel](https://vercel.com) projects, deployments, and domains in - **Deployments** — deployment volume, success/failure rates, and history over time, account-wide or drilled down per project. (Deployments are *not* indexed as objects, because they change too frequently — they are available as data streams instead.) - **Team & activity** — team membership roster and recent account activity (events). - **Cost** — a cost/usage overview derived from Vercel billing data (daily granularity). -- **Firewall / security** — per-project [Vercel Firewall (WAF)](https://vercel.com/docs/vercel-firewall) posture: whether the firewall is enabled, which managed protections (OWASP-style CRS categories, bot protection, AI bots) are active and their action, plus active attack anomalies over time. Available as data streams on the **Project** perspective. +- **Firewall / security** — per-project [Vercel Firewall (WAF)](https://vercel.com/docs/vercel-firewall) event activity: counts of firewall events broken down by action (e.g. allow / deny / challenge) over time. Available as data streams on the **Project** perspective's **Security** section. The plugin ships with three out-of-the-box dashboards: an account **Overview**, a **Project** perspective (which includes a **Security** section), and a **Domain** perspective. @@ -52,6 +52,6 @@ Deployments, teams, members, activity events, and cost data are provided as **da - **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 is daily granularity** and the billing endpoint returns very large datasets, so the cost stream is restricted to the **Last 24 hours** and **Last 7 days** timeframes. - **Team members and cost require a Team** and a token with adequate role/plan. On personal/Hobby accounts these streams may be empty. -- **Firewall streams only return data for projects with a configured firewall.** The Vercel Firewall config endpoint returns a 404 (not an empty result) for any project that has never configured a firewall, so the firewall posture and managed-rules tiles render only for projects where the WAF has been set up; for other projects the tile shows no data. The attack-anomalies stream returns an empty result (not an error) when there is no active attack. +- **Firewall events only appear for projects with the WAF configured.** The Firewall events stream returns rows only for projects that have the Vercel Firewall set up and that recorded events within the selected timeframe; a project with no firewall activity in that window shows an empty result (not an error). - **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. diff --git a/plugins/Vercel/v1/metadata.json b/plugins/Vercel/v1/metadata.json index 675c824b..83e2e0a0 100644 --- a/plugins/Vercel/v1/metadata.json +++ b/plugins/Vercel/v1/metadata.json @@ -1,15 +1,25 @@ { "name": "vercel", "displayName": "Vercel", - "version": "1.2.0", + "version": "1.0.0", "author": { "name": "Andrew Harris", "type": "community" }, - "description": "Monitor your Vercel projects, deployments, and domains in SquaredUp — track deployment health and history, domain verification and expiry, team activity, a cost overview, and firewall (WAF) security posture.", + "description": "Monitor your Vercel projects, deployments, and domains in SquaredUp — track deployment health and history, domain verification and expiry, team activity, a cost overview, and firewall (WAF) event activity.", "category": "Cloud Platforms", "type": "hybrid", "schemaVersion": "2.1", "importNotSupported": false, "restrictedToPlatforms": [], - "keywords": ["vercel", "deployments", "hosting", "frontend", "projects", "domains", "security", "firewall", "waf"], + "keywords": [ + "vercel", + "deployments", + "hosting", + "frontend", + "projects", + "domains", + "security", + "firewall", + "waf" + ], "objectTypes": ["Vercel Project", "Vercel Domain"], "links": [ { @@ -32,9 +42,7 @@ "headers": [ { "key": "Authorization", "value": "Bearer {{accessToken}}" } ], - "queryArgs": [ - { "key": "teamId", "value": "{{teamId}}" } - ] + "queryArgs": [{ "key": "teamId", "value": "{{teamId}}" }] } } } From fc8a29af16b6a6da11074ae89d5cf11191f7a4b3 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Thu, 18 Jun 2026 06:50:02 +0100 Subject: [PATCH 03/23] Vercel plugin review comments --- plugins/Vercel/v1/custom_types.json | 8 +++---- .../Vercel/v1/dataStreams/deployments.json | 2 +- .../Vercel/v1/dataStreams/domainConfig.json | 20 ++++++++++++++---- .../Vercel/v1/dataStreams/firewallEvents.json | 2 +- .../Vercel/v1/dataStreams/projectInfo.json | 21 +++++++++++++++---- .../v1/defaultContent/deployments.dash.json | 18 ---------------- .../v1/defaultContent/overview.dash.json | 2 +- .../Vercel/v1/indexDefinitions/default.json | 8 +++++-- 8 files changed, 46 insertions(+), 35 deletions(-) diff --git a/plugins/Vercel/v1/custom_types.json b/plugins/Vercel/v1/custom_types.json index e25c0352..5ad86d20 100644 --- a/plugins/Vercel/v1/custom_types.json +++ b/plugins/Vercel/v1/custom_types.json @@ -1,14 +1,14 @@ [ { - "name": "Vercel Project", - "sourceType": "Vercel Project", + "name": "Project", + "sourceType": "Project", "icon": "rocket", "singular": "Project", "plural": "Projects" }, { - "name": "Vercel Domain", - "sourceType": "Vercel Domain", + "name": "Domain", + "sourceType": "Domain", "icon": "globe", "singular": "Domain", "plural": "Domains" diff --git a/plugins/Vercel/v1/dataStreams/deployments.json b/plugins/Vercel/v1/dataStreams/deployments.json index 6066a57c..42eea108 100644 --- a/plugins/Vercel/v1/dataStreams/deployments.json +++ b/plugins/Vercel/v1/dataStreams/deployments.json @@ -29,7 +29,7 @@ "name": "project", "label": "Project (optional)", "matches": { - "sourceType": { "type": "equals", "value": "Vercel Project" } + "sourceType": { "type": "equals", "value": "Project" } } } ], diff --git a/plugins/Vercel/v1/dataStreams/domainConfig.json b/plugins/Vercel/v1/dataStreams/domainConfig.json index 8a430814..cd87e7a2 100644 --- a/plugins/Vercel/v1/dataStreams/domainConfig.json +++ b/plugins/Vercel/v1/dataStreams/domainConfig.json @@ -8,11 +8,23 @@ "httpMethod": "get", "endpointPath": "v6/domains/{{object.name}}/config" }, - "matches": { "sourceType": { "type": "equals", "value": "Vercel Domain" } }, + "matches": { "sourceType": { "type": "equals", "value": "Domain" } }, "metadata": [ - { "name": "misconfigured", "displayName": "Misconfigured", "shape": "boolean" }, - { "name": "serviceType", "displayName": "Service Type", "shape": "string" }, - { "name": "configuredBy", "displayName": "Configured By", "shape": "string" } + { + "name": "misconfigured", + "displayName": "Misconfigured", + "shape": "boolean" + }, + { + "name": "serviceType", + "displayName": "Service Type", + "shape": "string" + }, + { + "name": "configuredBy", + "displayName": "Configured By", + "shape": "string" + } ], "timeframes": false } diff --git a/plugins/Vercel/v1/dataStreams/firewallEvents.json b/plugins/Vercel/v1/dataStreams/firewallEvents.json index 06f5897f..f77a4298 100644 --- a/plugins/Vercel/v1/dataStreams/firewallEvents.json +++ b/plugins/Vercel/v1/dataStreams/firewallEvents.json @@ -18,7 +18,7 @@ "pathToData": "actions" }, "matches": { - "sourceType": { "type": "equals", "value": "Vercel Project" } + "sourceType": { "type": "equals", "value": "Project" } }, "metadata": [ { diff --git a/plugins/Vercel/v1/dataStreams/projectInfo.json b/plugins/Vercel/v1/dataStreams/projectInfo.json index dd7b5c5a..aa69f7ec 100644 --- a/plugins/Vercel/v1/dataStreams/projectInfo.json +++ b/plugins/Vercel/v1/dataStreams/projectInfo.json @@ -9,15 +9,28 @@ "endpointPath": "v9/projects/{{object.rawId}}", "expandInnerObjects": true }, - "matches": { "sourceType": { "type": "equals", "value": "Vercel Project" } }, + "matches": { "sourceType": { "type": "equals", "value": "Project" } }, "metadata": [ - { "name": "name", "displayName": "Name", "shape": "string", "role": "label" }, + { + "name": "name", + "displayName": "Name", + "shape": "string", + "role": "label" + }, { "name": "framework", "displayName": "Framework", "shape": "string" }, - { "name": "nodeVersion", "displayName": "Node Version", "shape": "string" }, + { + "name": "nodeVersion", + "displayName": "Node Version", + "shape": "string" + }, { "name": "createdAt", "displayName": "Created", "shape": "date" }, { "name": "updatedAt", "displayName": "Updated", "shape": "date" }, { "name": "link.repo", "displayName": "Git Repo", "shape": "string" }, - { "name": "link.type", "displayName": "Git Provider", "shape": "string" } + { + "name": "link.type", + "displayName": "Git Provider", + "shape": "string" + } ], "timeframes": false } diff --git a/plugins/Vercel/v1/defaultContent/deployments.dash.json b/plugins/Vercel/v1/defaultContent/deployments.dash.json index 6eb0ed17..ca646afd 100644 --- a/plugins/Vercel/v1/defaultContent/deployments.dash.json +++ b/plugins/Vercel/v1/defaultContent/deployments.dash.json @@ -128,24 +128,6 @@ "aggregate": [] } }, - "scope": { - "query": "g.V().has('id', within(ids_defaultScopeIds))", - "bindings": { - "ids_defaultScopeIds": [ - "node-1dwkxV4HX7tOQS5MZOwLQEJ7OAPUH1yXV3Crp-aqMqx0SfUpUnwez0xGyS" - ] - }, - "queryDetail": { - "ids": [ - "node-1dwkxV4HX7tOQS5MZOwLQEJ7OAPUH1yXV3Crp-aqMqx0SfUpUnwez0xGyS" - ], - "types": [ - { - "value": "data-source" - } - ] - } - }, "_type": "tile/data-stream", "description": "", "activePluginConfigIds": ["{{configId}}"], diff --git a/plugins/Vercel/v1/defaultContent/overview.dash.json b/plugins/Vercel/v1/defaultContent/overview.dash.json index dae24374..80f7ada6 100644 --- a/plugins/Vercel/v1/defaultContent/overview.dash.json +++ b/plugins/Vercel/v1/defaultContent/overview.dash.json @@ -66,7 +66,7 @@ "dataStream": { "name": "projects", "pluginConfigId": "{{configId}}", - "id": "{{dataStreams.[proejcts]}}", + "id": "{{dataStreams.[projects]}}", "sort": { "by": [["updatedAt", "desc"]] } diff --git a/plugins/Vercel/v1/indexDefinitions/default.json b/plugins/Vercel/v1/indexDefinitions/default.json index 7642ba9b..a07b6dc4 100644 --- a/plugins/Vercel/v1/indexDefinitions/default.json +++ b/plugins/Vercel/v1/indexDefinitions/default.json @@ -7,7 +7,9 @@ "objectMapping": { "id": "id", "name": "name", - "type": "sourceType" + "type": { + "value": "Project" + } } }, { @@ -17,7 +19,9 @@ "objectMapping": { "id": "id", "name": "name", - "type": "sourceType" + "type": { + "value": "Domain" + } } } ] From 5b2e47a09e137cf026930649aaab87d2b2e39376 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Thu, 18 Jun 2026 06:52:27 +0100 Subject: [PATCH 04/23] update plugin author --- plugins/Vercel/v1/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Vercel/v1/metadata.json b/plugins/Vercel/v1/metadata.json index 83e2e0a0..36f885db 100644 --- a/plugins/Vercel/v1/metadata.json +++ b/plugins/Vercel/v1/metadata.json @@ -2,7 +2,7 @@ "name": "vercel", "displayName": "Vercel", "version": "1.0.0", - "author": { "name": "Andrew Harris", "type": "community" }, + "author": { "name": "@andrewmumblebee", "type": "community" }, "description": "Monitor your Vercel projects, deployments, and domains in SquaredUp — track deployment health and history, domain verification and expiry, team activity, a cost overview, and firewall (WAF) event activity.", "category": "Cloud Platforms", "type": "hybrid", From c70fa6e055d77c8aeac14d9cb05ec13bc6c15ce4 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Thu, 18 Jun 2026 12:37:03 +0100 Subject: [PATCH 05/23] Ensure scopes and object types match new sourceTypes --- plugins/Vercel/v1/defaultContent/scopes.json | 4 ++-- plugins/Vercel/v1/docs/README.md | 20 +++++++++----------- plugins/Vercel/v1/metadata.json | 2 +- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/plugins/Vercel/v1/defaultContent/scopes.json b/plugins/Vercel/v1/defaultContent/scopes.json index 164d0191..da9b2912 100644 --- a/plugins/Vercel/v1/defaultContent/scopes.json +++ b/plugins/Vercel/v1/defaultContent/scopes.json @@ -2,7 +2,7 @@ { "name": "Vercel Projects", "matches": { - "sourceType": { "type": "oneOf", "values": ["Vercel Project"] } + "sourceType": { "type": "oneOf", "values": ["Project"] } }, "variable": { "name": "Vercel Project", @@ -14,7 +14,7 @@ { "name": "Vercel Domains", "matches": { - "sourceType": { "type": "oneOf", "values": ["Vercel Domain"] } + "sourceType": { "type": "oneOf", "values": ["Domain"] } }, "variable": { "name": "Vercel Domain", diff --git a/plugins/Vercel/v1/docs/README.md b/plugins/Vercel/v1/docs/README.md index a1fa5ca1..84047b70 100644 --- a/plugins/Vercel/v1/docs/README.md +++ b/plugins/Vercel/v1/docs/README.md @@ -1,26 +1,24 @@ -# Vercel +## What this plugin monitors Monitor your [Vercel](https://vercel.com) projects, deployments, and domains in SquaredUp. This plugin connects to the Vercel REST API to import your projects and domains as objects you can scope dashboards to, and provides data streams for deployment health and history, domain verification and expiry, team membership and activity, and a billing-based cost overview. -## What this plugin monitors - - **Projects** — your Vercel projects, including framework, git repository, and latest production deployment status. Imported as objects. - **Domains** — custom domains, including verification status, expiry/renewal dates, and configuration health. Imported as objects. -- **Deployments** — deployment volume, success/failure rates, and history over time, account-wide or drilled down per project. (Deployments are *not* indexed as objects, because they change too frequently — they are available as data streams instead.) +- **Deployments** — deployment volume, success/failure rates, and history over time, account-wide or drilled down per project. (Deployments are _not_ indexed as objects, because they change too frequently — they are available as data streams instead.) - **Team & activity** — team membership roster and recent account activity (events). - **Cost** — a cost/usage overview derived from Vercel billing data (daily granularity). - **Firewall / security** — per-project [Vercel Firewall (WAF)](https://vercel.com/docs/vercel-firewall) event activity: counts of firewall events broken down by action (e.g. allow / deny / challenge) over time. Available as data streams on the **Project** perspective's **Security** section. The plugin ships with three out-of-the-box dashboards: an account **Overview**, a **Project** perspective (which includes a **Security** section), and a **Domain** perspective. -## Prerequisites — getting a Vercel Access Token +## Prerequisites 1. Sign in to Vercel and open **Account Settings → Tokens** (). 2. Click **Create Token**. 3. Give it a name (e.g. `SquaredUp`). 4. **Scope** — choose the scope the token can access: - - To monitor a **Team**, set the scope to that team and note the team's ID or slug (see the `Team ID` field below). - - To monitor your **personal account**, scope it to your personal account. + - To monitor a **Team**, set the scope to that team and note the team's ID or slug (see the `Team ID` field below). + - To monitor your **personal account**, scope it to your personal account. 5. **Expiration** — choose an expiration (or no expiration). If the token expires, the plugin's data streams will stop returning data until you supply a new token. 6. Click **Create** and copy the token value immediately — Vercel only shows it once. @@ -32,10 +30,10 @@ If you are monitoring a Team, open **Team Settings → General** in Vercel; the ## Configuration fields -| Field | Required | Description | Where to find it | -| --- | --- | --- | --- | -| **API Token** | Yes | The Vercel Access Token used to authenticate. Sent as a bearer token on every request. | Account Settings → Tokens (see above). | -| **Team ID** | No | The Team ID or slug to monitor. Leave blank to monitor your personal account. When set, all requests are scoped to this team. | Team Settings → General (`team_…`), or the slug in your Vercel URL. | +| Field | Required | Description | Where to find it | +| ------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | +| **API Token** | Yes | The Vercel Access Token used to authenticate. Sent as a bearer token on every request. | Account Settings → Tokens (see above). | +| **Team ID** | No | The Team ID or slug to monitor. Leave blank to monitor your personal account. When set, all requests are scoped to this team. | Team Settings → General (`team_…`), or the slug in your Vercel URL. | ## What gets indexed diff --git a/plugins/Vercel/v1/metadata.json b/plugins/Vercel/v1/metadata.json index 36f885db..a8be3c4f 100644 --- a/plugins/Vercel/v1/metadata.json +++ b/plugins/Vercel/v1/metadata.json @@ -20,7 +20,7 @@ "firewall", "waf" ], - "objectTypes": ["Vercel Project", "Vercel Domain"], + "objectTypes": ["Project", "Domain"], "links": [ { "category": "documentation", From 79b05d53e595698e965f2aa791a94a0f09826200 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 12:52:50 +0100 Subject: [PATCH 06/23] Remove Vercel cost data stream Due to the size of the response data, we can not monitor this right now --- plugins/Vercel/v1/dataStreams/cost.json | 71 -------- plugins/Vercel/v1/dataStreams/domains.json | 7 +- plugins/Vercel/v1/dataStreams/projects.json | 6 - .../Vercel/v1/defaultContent/cost.dash.json | 161 ------------------ .../Vercel/v1/defaultContent/manifest.json | 1 - .../v1/defaultContent/overview.dash.json | 107 ------------ 6 files changed, 5 insertions(+), 348 deletions(-) delete mode 100644 plugins/Vercel/v1/dataStreams/cost.json delete mode 100644 plugins/Vercel/v1/defaultContent/cost.dash.json diff --git a/plugins/Vercel/v1/dataStreams/cost.json b/plugins/Vercel/v1/dataStreams/cost.json deleted file mode 100644 index 82f237d1..00000000 --- a/plugins/Vercel/v1/dataStreams/cost.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "name": "cost", - "displayName": "Cost", - "description": "Vercel usage cost and consumption from the FOCUS billing endpoint, one row per daily charge", - "tags": ["Cost", "Billing"], - "baseDataSourceName": "httpRequestUnscoped", - "config": { - "httpMethod": "get", - "endpointPath": "v1/billing/charges", - "postRequestScript": "cost.js", - "getArgs": [ - { "key": "from", "value": "{{timeframe.start}}" }, - { "key": "to", "value": "{{timeframe.end}}" } - ], - "headers": [ - { - "key": "Accept-Encoding", - "value": "gzip" - } - ] - }, - "matches": "none", - "metadata": [ - { - "name": "service", - "displayName": "Service", - "shape": "string", - "role": "label" - }, - { - "name": "billedCost", - "displayName": "Billed Cost ($)", - "shape": [ - "currency", - { - "code": "usd", - "decimalPlaces": 2, - "thousandsSeparator": true - } - ], - "role": "value" - }, - { - "name": "effectiveCost", - "displayName": "Effective Cost ($)", - "shape": [ - "currency", - { - "code": "usd", - "decimalPlaces": 2, - "thousandsSeparator": true - } - ] - }, - { "name": "quantity", "displayName": "Quantity", "shape": "number" }, - { "name": "unit", "displayName": "Unit", "shape": "string" }, - { - "name": "projectName", - "displayName": "Project", - "shape": "string", - "role": "label" - }, - { - "name": "periodStart", - "displayName": "Period Start", - "shape": "date", - "role": "timestamp" - } - ], - "timeframes": ["last24hours", "last7days", "last30days", "thisMonth"] -} diff --git a/plugins/Vercel/v1/dataStreams/domains.json b/plugins/Vercel/v1/dataStreams/domains.json index 860b80ec..f2608056 100644 --- a/plugins/Vercel/v1/dataStreams/domains.json +++ b/plugins/Vercel/v1/dataStreams/domains.json @@ -10,7 +10,11 @@ "pathToData": "domains", "paging": { "mode": "token", - "pageSize": { "realm": "queryArg", "path": "limit", "value": "100" }, + "pageSize": { + "realm": "queryArg", + "path": "limit", + "value": "100" + }, "in": { "realm": "payload", "path": "pagination.next" }, "out": { "realm": "queryArg", "path": "until" } } @@ -18,7 +22,6 @@ "matches": "none", "metadata": [ { "name": "id", "displayName": "ID", "visible": false }, - { "name": "sourceType", "computed": true, "valueExpression": "Vercel Domain", "visible": false }, { "name": "name", "displayName": "Domain", "role": "label" }, { "name": "verified", "displayName": "Verified" }, { "name": "serviceType", "displayName": "Service Type" }, diff --git a/plugins/Vercel/v1/dataStreams/projects.json b/plugins/Vercel/v1/dataStreams/projects.json index f15c053a..aef79672 100644 --- a/plugins/Vercel/v1/dataStreams/projects.json +++ b/plugins/Vercel/v1/dataStreams/projects.json @@ -27,12 +27,6 @@ "role": "value", "visible": false }, - { - "name": "sourceType", - "computed": true, - "valueExpression": "Vercel Project", - "visible": false - }, { "name": "name", "displayName": "Name", "role": "label" }, { "name": "framework", "displayName": "Framework" }, { "name": "nodeVersion", "displayName": "Node Version" }, diff --git a/plugins/Vercel/v1/defaultContent/cost.dash.json b/plugins/Vercel/v1/defaultContent/cost.dash.json deleted file mode 100644 index d2eebea9..00000000 --- a/plugins/Vercel/v1/defaultContent/cost.dash.json +++ /dev/null @@ -1,161 +0,0 @@ -{ - "name": "Cost", - "schemaVersion": "1.5", - "timeframe": "last7days", - "dashboard": { - "_type": "layout/grid", - "contents": [ - { - "static": false, - "w": 2, - "moved": false, - "h": 4, - "x": 0, - "y": 0, - "i": "0f2bc2a4-cb0b-41a1-a4fb-9b0e29e8a088", - "z": 0, - "config": { - "timeframe": "last7days", - "dataStream": { - "name": "cost", - "pluginConfigId": "{{configId}}", - "id": "{{dataStreams.[cost]}}", - "sort": { - "by": [["billedCost_sum", "desc"]] - }, - "group": { - "by": [["service", "uniqueValues"]], - "aggregate": [ - { - "type": "sum", - "names": ["billedCost"] - } - ] - } - }, - "_type": "tile/data-stream", - "description": "Billed cost grouped by service", - "activePluginConfigIds": ["{{configId}}"], - "title": "Cost by Service", - "visualisation": { - "type": "data-stream-donut-chart", - "config": { - "data-stream-donut-chart": { - "valueColumn": "billedCost_sum", - "hideCenterValue": false, - "showValuesAsPercentage": false, - "legendPosition": "auto", - "legendMode": "table", - "labelColumn": "service_uniqueValues" - } - } - } - } - }, - { - "static": false, - "w": 2, - "moved": false, - "h": 4, - "x": 2, - "y": 0, - "i": "64bb0cfc-e350-43e6-a047-5fa04377cc10", - "z": 0, - "config": { - "timeframe": "last7days", - "dataStream": { - "name": "cost", - "pluginConfigId": "{{configId}}", - "id": "{{dataStreams.[cost]}}", - "sort": { - "top": 10, - "by": [["billedCost_sum", "desc"]] - }, - "group": { - "by": [["projectName", "uniqueValues"]], - "aggregate": [ - { - "type": "sum", - "names": ["billedCost"] - } - ] - } - }, - "_type": "tile/data-stream", - "description": "Billed cost grouped by project", - "activePluginConfigIds": ["{{configId}}"], - "title": "Cost by Project", - "visualisation": { - "type": "data-stream-bar-chart", - "config": { - "data-stream-bar-chart": { - "xAxisGroup": "none", - "showLegend": false, - "range": { - "type": "auto" - }, - "showGrid": true, - "grouping": false, - "xAxisData": "projectName_uniqueValues", - "displayMode": "actual", - "yAxisLabel": "Billed Cost ($)", - "horizontalLayout": "horizontal", - "yAxisData": ["billedCost_sum"], - "showYAxisLabel": true, - "legendPosition": "bottom", - "showXAxisLabel": false - } - } - } - } - }, - { - "static": false, - "w": 4, - "moved": false, - "h": 3, - "x": 0, - "y": 4, - "i": "731acf15-16ed-4e1a-baa9-7f3d992e172f", - "z": 0, - "config": { - "timeframe": "last7days", - "dataStream": { - "name": "cost", - "pluginConfigId": "{{configId}}", - "id": "{{dataStreams.[cost]}}", - "sort": {}, - "group": { - "by": [["periodStart", "byDay"]], - "aggregate": [ - { - "type": "sum", - "names": ["billedCost"] - } - ] - } - }, - "_type": "tile/data-stream", - "description": "", - "activePluginConfigIds": ["{{configId}}"], - "title": "Cost by day", - "visualisation": { - "type": "data-stream-line-graph", - "config": { - "data-stream-line-graph": { - "seriesColumn": "none", - "dataPoints": true, - "showTrendLine": true, - "cumulative": false, - "xAxisColumn": "service_uniqueValues", - "yAxisColumn": ["billedCost_sum"] - } - } - } - } - } - ], - "version": 16, - "columns": 4 - } -} diff --git a/plugins/Vercel/v1/defaultContent/manifest.json b/plugins/Vercel/v1/defaultContent/manifest.json index 0fcc169c..f7f81e36 100644 --- a/plugins/Vercel/v1/defaultContent/manifest.json +++ b/plugins/Vercel/v1/defaultContent/manifest.json @@ -1,7 +1,6 @@ { "items": [ { "name": "overview", "type": "dashboard" }, - { "name": "cost", "type": "dashboard" }, { "name": "deployments", "type": "dashboard" }, { "name": "activity", "type": "dashboard" }, { "name": "project", "type": "dashboard" }, diff --git a/plugins/Vercel/v1/defaultContent/overview.dash.json b/plugins/Vercel/v1/defaultContent/overview.dash.json index 80f7ada6..2d464de3 100644 --- a/plugins/Vercel/v1/defaultContent/overview.dash.json +++ b/plugins/Vercel/v1/defaultContent/overview.dash.json @@ -96,113 +96,6 @@ } } }, - { - "static": false, - "w": 1, - "moved": false, - "h": 3, - "x": 0, - "y": 3, - "i": "f3ff120e-5ed1-414f-b57e-a2982d25ebbe", - "z": 0, - "config": { - "timeframe": "last7days", - "dataStream": { - "name": "cost", - "pluginConfigId": "{{configId}}", - "id": "{{dataStreams.[cost]}}", - "sort": { - "top": 10, - "by": [] - }, - "group": { - "by": [], - "aggregate": [ - { - "type": "sum", - "names": ["billedCost"] - } - ] - } - }, - "_type": "tile/data-stream", - "description": "", - "activePluginConfigIds": ["{{configId}}"], - "title": "Overall Cost", - "visualisation": { - "type": "data-stream-scalar", - "config": { - "data-stream-scalar": { - "value": "billedCost_sum", - "comparisonColumn": "none" - } - } - } - } - }, - { - "static": false, - "w": 3, - "moved": false, - "h": 3, - "x": 1, - "y": 3, - "i": "098760ae-37bf-485c-8b14-605051f34733", - "z": 0, - "config": { - "timeframe": "last7days", - "dataStream": { - "name": "cost", - "pluginConfigId": "{{configId}}", - "id": "{{dataStreams.[cost]}}", - "sort": { - "top": 10, - "by": [["billedCost_sum", "desc"]] - }, - "group": { - "by": [["projectName", "uniqueValues"]], - "aggregate": [ - { - "type": "sum", - "names": ["billedCost"] - } - ] - } - }, - "_type": "tile/data-stream", - "description": "Billed cost grouped by project", - "activePluginConfigIds": ["{{configId}}"], - "title": "Cost by Project", - "visualisation": { - "type": "data-stream-bar-chart", - "config": { - "data-stream-bar-chart": { - "color": { - "type": "default" - }, - "xAxisGroup": "none", - "showLegend": false, - "range": { - "type": "auto" - }, - "showGrid": true, - "grouping": false, - "xAxisData": "projectName_uniqueValues", - "displayMode": "actual", - "showTotals": false, - "yAxisLabel": "Billed Cost ($)", - "horizontalLayout": "horizontal", - "showValue": false, - "yAxisData": ["billedCost_sum"], - "showYAxisLabel": true, - "xAxisLabel": "", - "legendPosition": "bottom", - "showXAxisLabel": false - } - } - } - } - }, { "static": false, "w": 1, From 703ded54d290092feccbb03fd3866d3a3607f9f1 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 12:54:39 +0100 Subject: [PATCH 07/23] shift overview tiles up --- plugins/Vercel/v1/defaultContent/overview.dash.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Vercel/v1/defaultContent/overview.dash.json b/plugins/Vercel/v1/defaultContent/overview.dash.json index 2d464de3..05694b48 100644 --- a/plugins/Vercel/v1/defaultContent/overview.dash.json +++ b/plugins/Vercel/v1/defaultContent/overview.dash.json @@ -102,7 +102,7 @@ "moved": false, "h": 2, "x": 0, - "y": 6, + "y": 3, "i": "60fe71cb-be1f-4627-9eb7-c4aa7fe10feb", "z": 0, "config": { @@ -152,7 +152,7 @@ "moved": false, "h": 2, "x": 1, - "y": 6, + "y": 3, "i": "f2843ac7-c436-4ad4-a6c8-5e5cb570420b", "z": 0, "config": { From 5c4a222f6f8eccbf0ac6ec180ef9fd714dbb7c42 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:03:35 +0100 Subject: [PATCH 08/23] Add team error handling --- plugins/Vercel/v1/dataStreams/scripts/teamErrorHandling.js | 5 +++++ plugins/Vercel/v1/dataStreams/teamMembers.json | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 plugins/Vercel/v1/dataStreams/scripts/teamErrorHandling.js diff --git a/plugins/Vercel/v1/dataStreams/scripts/teamErrorHandling.js b/plugins/Vercel/v1/dataStreams/scripts/teamErrorHandling.js new file mode 100644 index 00000000..cdef1021 --- /dev/null +++ b/plugins/Vercel/v1/dataStreams/scripts/teamErrorHandling.js @@ -0,0 +1,5 @@ +if (response.status === 404) { + result = + "This data stream requires a Team ID. Add your Vercel Team ID in the data source settings to monitor a Team."; +} + diff --git a/plugins/Vercel/v1/dataStreams/teamMembers.json b/plugins/Vercel/v1/dataStreams/teamMembers.json index d7284898..42f1a803 100644 --- a/plugins/Vercel/v1/dataStreams/teamMembers.json +++ b/plugins/Vercel/v1/dataStreams/teamMembers.json @@ -17,6 +17,10 @@ }, "in": { "realm": "payload", "path": "pagination.next" }, "out": { "realm": "queryArg", "path": "until" } + }, + "errorHandling": { + "type": "script", + "script": "teamErrorHandling.js" } }, "matches": "none", From 85dd9d36360beb2bd394fd5b3952b113006be885 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:04:47 +0100 Subject: [PATCH 09/23] slight text tweak --- plugins/Vercel/v1/dataStreams/scripts/teamErrorHandling.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Vercel/v1/dataStreams/scripts/teamErrorHandling.js b/plugins/Vercel/v1/dataStreams/scripts/teamErrorHandling.js index cdef1021..f7be74f9 100644 --- a/plugins/Vercel/v1/dataStreams/scripts/teamErrorHandling.js +++ b/plugins/Vercel/v1/dataStreams/scripts/teamErrorHandling.js @@ -1,5 +1,5 @@ if (response.status === 404) { result = - "This data stream requires a Team ID. Add your Vercel Team ID in the data source settings to monitor a Team."; + "This data stream requires a Team ID. Add your Vercel Team ID in the data source settings to monitor Teams."; } From c3b9d5561f10b45c42cccd5edd1f1d084e872791 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:08:50 +0100 Subject: [PATCH 10/23] Add codeowners for vercel --- .github/CODEOWNERS | 1 + plugins/Vercel/v1/dataStreams/projects.json | 2 +- plugins/Vercel/v1/defaultContent/deployments.dash.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9690e211..4d1c5edc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -24,6 +24,7 @@ plugins/TransportForLondon/* @clarkd plugins/UniFi/* @adamkinniburgh plugins/UptimeRobot/* @kieranlangton plugins/WorldCup2026/* @TimWheeler-SQUP +plugins/Vercel/* @andrewmumblebee # Fallback – if a plugin has no specified author diff --git a/plugins/Vercel/v1/dataStreams/projects.json b/plugins/Vercel/v1/dataStreams/projects.json index aef79672..aaf569db 100644 --- a/plugins/Vercel/v1/dataStreams/projects.json +++ b/plugins/Vercel/v1/dataStreams/projects.json @@ -1,7 +1,7 @@ { "name": "projects", "displayName": "Projects", - "description": "Lists Vercel projects in the configured account or team. Backs the Vercel Project import and project inventory tiles.", + "description": "Lists Vercel projects in the configured account or team", "tags": [], "baseDataSourceName": "httpRequestUnscoped", "config": { diff --git a/plugins/Vercel/v1/defaultContent/deployments.dash.json b/plugins/Vercel/v1/defaultContent/deployments.dash.json index ca646afd..97f87714 100644 --- a/plugins/Vercel/v1/defaultContent/deployments.dash.json +++ b/plugins/Vercel/v1/defaultContent/deployments.dash.json @@ -226,7 +226,7 @@ "id": "datastream-sql", "dataSourceConfig": { "version": "2.0", - "sql": "WITH\r\n \"build_times\" AS (\r\n SELECT\r\n STRFTIME(\r\n DATE_TRUNC('day', \"createdAt\"),\r\n '%Y-%m-%dT%H:%M:%SZ'\r\n ) AS \"date\",\r\n \"name\",\r\n EXTRACT (\r\n EPOCH\r\n FROM\r\n \"buildingAt\" - \"createdAt\"\r\n ) AS \"build_time\"\r\n FROM\r\n \"dataset1\"\r\n )\r\nSELECT\r\n \"date\",\r\n \"name\",\r\n AVG(\"build_time\") AS \"avg_build_time\"\r\nFROM\r\n \"build_times\"\r\nGROUP BY\r\n \"date\",\r\n \"name\"", + "sql": "WITH\r\n \"build_times\" AS (\r\n SELECT\r\n STRFTIME(\r\n DATE_TRUNC('day', \"createdAt\"),\r\n '%Y-%m-%dT%H:%M:%SZ'\r\n ) AS \"date\",\r\n \"name\",\r\n EXTRACT (\r\n EPOCH\r\n FROM\r\n \"ready\" - \"buildingAt\"\r\n ) AS \"build_time\"\r\n FROM\r\n \"dataset1\"\r\n )\r\nSELECT\r\n \"date\",\r\n \"name\",\r\n AVG(\"build_time\") AS \"avg_build_time\"\r\nFROM\r\n \"build_times\"\r\nGROUP BY\r\n \"date\",\r\n \"name\"", "tables": [ { "tableName": "dataset1", From d38b7ce6babe045610c3d205ab125845b46e6589 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:35:04 +0100 Subject: [PATCH 11/23] Update Vercel readme --- plugins/Vercel/v1/docs/README.md | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/plugins/Vercel/v1/docs/README.md b/plugins/Vercel/v1/docs/README.md index 84047b70..814d3701 100644 --- a/plugins/Vercel/v1/docs/README.md +++ b/plugins/Vercel/v1/docs/README.md @@ -1,39 +1,31 @@ ## What this plugin monitors -Monitor your [Vercel](https://vercel.com) projects, deployments, and domains in SquaredUp. This plugin connects to the Vercel REST API to import your projects and domains as objects you can scope dashboards to, and provides data streams for deployment health and history, domain verification and expiry, team membership and activity, and a billing-based cost overview. +Monitor your [Vercel](https://vercel.com) projects, deployments, and domains in SquaredUp. This plugin connects to the Vercel REST API to import your projects and domains as objects you can scope dashboards to, and provides data streams for deployment health and history, domain verification and expiry, team membership and activity. - **Projects** — your Vercel projects, including framework, git repository, and latest production deployment status. Imported as objects. - **Domains** — custom domains, including verification status, expiry/renewal dates, and configuration health. Imported as objects. - **Deployments** — deployment volume, success/failure rates, and history over time, account-wide or drilled down per project. (Deployments are _not_ indexed as objects, because they change too frequently — they are available as data streams instead.) - **Team & activity** — team membership roster and recent account activity (events). -- **Cost** — a cost/usage overview derived from Vercel billing data (daily granularity). - **Firewall / security** — per-project [Vercel Firewall (WAF)](https://vercel.com/docs/vercel-firewall) event activity: counts of firewall events broken down by action (e.g. allow / deny / challenge) over time. Available as data streams on the **Project** perspective's **Security** section. -The plugin ships with three out-of-the-box dashboards: an account **Overview**, a **Project** perspective (which includes a **Security** section), and a **Domain** perspective. - ## Prerequisites 1. Sign in to Vercel and open **Account Settings → Tokens** (). 2. Click **Create Token**. 3. Give it a name (e.g. `SquaredUp`). 4. **Scope** — choose the scope the token can access: - - To monitor a **Team**, set the scope to that team and note the team's ID or slug (see the `Team ID` field below). + - To monitor a **Team**, set the scope to that team. - To monitor your **personal account**, scope it to your personal account. 5. **Expiration** — choose an expiration (or no expiration). If the token expires, the plugin's data streams will stop returning data until you supply a new token. 6. Click **Create** and copy the token value immediately — Vercel only shows it once. -For the **cost overview** and **team members** streams, the token must belong to a Team and carry a role with billing/member visibility (Owner, Member, Developer, Security, or Billing). On personal/Hobby accounts these streams may return no data. - -### Finding your Team ID - -If you are monitoring a Team, open **Team Settings → General** in Vercel; the **Team ID** (format `team_xxxxxxxx`) is shown there. Enter that value in the `Team ID` field. (Leave the field blank to monitor your personal account instead.) +For the **team members** streams, the token must belong to a Team and carry a role with billing/member visibility (Owner, Member, Developer, Security, or Billing). On personal/Hobby accounts these streams may return no data. ## Configuration fields -| Field | Required | Description | Where to find it | -| ------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | -| **API Token** | Yes | The Vercel Access Token used to authenticate. Sent as a bearer token on every request. | Account Settings → Tokens (see above). | -| **Team ID** | No | The Team ID or slug to monitor. Leave blank to monitor your personal account. When set, all requests are scoped to this team. | Team Settings → General (`team_…`), or the slug in your Vercel URL. | +| Field | Required | Description | Where to find it | +| ------------- | -------- | -------------------------------------------------------------------------------------- | -------------------------------------- | +| **API Token** | Yes | The Vercel Access Token used to authenticate. Sent as a bearer token on every request. | Account Settings → Tokens (see above). | ## What gets indexed @@ -41,6 +33,7 @@ The plugin imports two object types into the SquaredUp graph: - **Vercel Project** — one object per project in the configured account/team. Carries the project name, framework, git repository link, and identifiers. - **Vercel Domain** — one object per custom domain. Carries the domain name, verification status, and expiry information. +- **Vercel Team** - one object per team in the configured account. Will be one or zero, as tokens can only be scoped to one team. Deployments, teams, members, activity events, and cost data are provided as **data streams** (not indexed objects) — query them on dashboards and scope deployment streams to a project. @@ -48,7 +41,6 @@ Deployments, teams, members, activity events, and cost data are provided as **da - **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 is daily granularity** and the billing endpoint returns very large datasets, so the cost stream is restricted to the **Last 24 hours** and **Last 7 days** timeframes. - **Team members and cost require a Team** and a token with adequate role/plan. On personal/Hobby accounts these streams may be empty. - **Firewall events only appear for projects with the WAF configured.** The Firewall events stream returns rows only for projects that have the Vercel Firewall set up and that recorded events within the selected timeframe; a project with no firewall activity in that window shows an empty result (not an error). - **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. From 9d4343d43a40fb443a39177eb7cdc271357fdd71 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:35:24 +0100 Subject: [PATCH 12/23] Remove need for user to add teamId We can get this by indexing the team, which will be one object --- plugins/Vercel/v1/custom_types.json | 7 +++++ .../dataStreams/scripts/teamErrorHandling.js | 5 ---- .../Vercel/v1/dataStreams/teamMembers.json | 12 +++----- .../v1/defaultContent/activity.dash.json | 5 ++++ .../Vercel/v1/indexDefinitions/default.json | 30 +++++++++++++++++-- plugins/Vercel/v1/metadata.json | 3 +- plugins/Vercel/v1/ui.json | 9 +----- 7 files changed, 46 insertions(+), 25 deletions(-) delete mode 100644 plugins/Vercel/v1/dataStreams/scripts/teamErrorHandling.js diff --git a/plugins/Vercel/v1/custom_types.json b/plugins/Vercel/v1/custom_types.json index 5ad86d20..9e98e362 100644 --- a/plugins/Vercel/v1/custom_types.json +++ b/plugins/Vercel/v1/custom_types.json @@ -12,5 +12,12 @@ "icon": "globe", "singular": "Domain", "plural": "Domains" + }, + { + "name": "Team", + "sourceType": "Team", + "icon": "people-group", + "singular": "Team", + "plural": "Teams" } ] diff --git a/plugins/Vercel/v1/dataStreams/scripts/teamErrorHandling.js b/plugins/Vercel/v1/dataStreams/scripts/teamErrorHandling.js deleted file mode 100644 index f7be74f9..00000000 --- a/plugins/Vercel/v1/dataStreams/scripts/teamErrorHandling.js +++ /dev/null @@ -1,5 +0,0 @@ -if (response.status === 404) { - result = - "This data stream requires a Team ID. Add your Vercel Team ID in the data source settings to monitor Teams."; -} - diff --git a/plugins/Vercel/v1/dataStreams/teamMembers.json b/plugins/Vercel/v1/dataStreams/teamMembers.json index 42f1a803..6342de2b 100644 --- a/plugins/Vercel/v1/dataStreams/teamMembers.json +++ b/plugins/Vercel/v1/dataStreams/teamMembers.json @@ -1,12 +1,12 @@ { "name": "teamMembers", "displayName": "Team Members", - "description": "Lists members of the configured Vercel team", + "description": "Lists members of the team", "tags": ["Team"], - "baseDataSourceName": "httpRequestUnscoped", + "baseDataSourceName": "httpRequestScopedSingle", "config": { "httpMethod": "get", - "endpointPath": "v3/teams/{{dataSource.teamId}}/members", + "endpointPath": "v3/teams/{{object.rawId}}/members", "pathToData": "members", "paging": { "mode": "token", @@ -17,13 +17,9 @@ }, "in": { "realm": "payload", "path": "pagination.next" }, "out": { "realm": "queryArg", "path": "until" } - }, - "errorHandling": { - "type": "script", - "script": "teamErrorHandling.js" } }, - "matches": "none", + "matches": { "sourceType": { "type": "equals", "value": "Team" } }, "metadata": [ { "name": "uid", diff --git a/plugins/Vercel/v1/defaultContent/activity.dash.json b/plugins/Vercel/v1/defaultContent/activity.dash.json index b672b3c6..19c08f1e 100644 --- a/plugins/Vercel/v1/defaultContent/activity.dash.json +++ b/plugins/Vercel/v1/defaultContent/activity.dash.json @@ -69,6 +69,11 @@ "by": [["name", "asc"]] } }, + "scope": { + "query": "g.V().order().by('__name').hasNot('__canonicalType').has(\"__configId\", within(\"{{configId}}\")).or(__.has(\"sourceType\", \"Team\")).limit(500)", + "bindings": {}, + "queryDetail": {} + }, "_type": "tile/data-stream", "description": "Members of the configured team", "activePluginConfigIds": ["{{configId}}"], diff --git a/plugins/Vercel/v1/indexDefinitions/default.json b/plugins/Vercel/v1/indexDefinitions/default.json index a07b6dc4..8dbe16a8 100644 --- a/plugins/Vercel/v1/indexDefinitions/default.json +++ b/plugins/Vercel/v1/indexDefinitions/default.json @@ -9,7 +9,13 @@ "name": "name", "type": { "value": "Project" - } + }, + "properties": [ + "framework", + "nodeVersion", + "createdAt", + "updatedAt" + ] } }, { @@ -21,7 +27,27 @@ "name": "name", "type": { "value": "Domain" - } + }, + "properties": [ + "verified", + "boughtAt", + "expiresAt", + "renew", + "createdAt" + ] + } + }, + { + "name": "teams", + "dataStream": { "name": "teams" }, + "timeframe": "none", + "objectMapping": { + "id": "id", + "name": "name", + "type": { + "value": "Team" + }, + "properties": ["slug", "createdAt"] } } ] diff --git a/plugins/Vercel/v1/metadata.json b/plugins/Vercel/v1/metadata.json index a8be3c4f..e143c7e9 100644 --- a/plugins/Vercel/v1/metadata.json +++ b/plugins/Vercel/v1/metadata.json @@ -41,8 +41,7 @@ "authMode": "none", "headers": [ { "key": "Authorization", "value": "Bearer {{accessToken}}" } - ], - "queryArgs": [{ "key": "teamId", "value": "{{teamId}}" }] + ] } } } diff --git a/plugins/Vercel/v1/ui.json b/plugins/Vercel/v1/ui.json index 86f3d3b9..7a07d071 100644 --- a/plugins/Vercel/v1/ui.json +++ b/plugins/Vercel/v1/ui.json @@ -7,15 +7,8 @@ { "type": "password", "name": "accessToken", - "label": "API Token", + "label": "API token", "help": "A Vercel Access Token. Sent as a bearer token on every request. Create one at [Account Settings → Tokens](https://vercel.com/account/tokens).", "validation": { "required": true } - }, - { - "type": "text", - "name": "teamId", - "label": "Team ID", - "placeholder": "team_xxxxxxxxxxxxxxxx", - "help": "Optional. The ID of the Vercel Team to monitor (found at Team Settings → General, format `team_…`). Leave blank to monitor your personal account." } ] From e590528b9c8ef70e250e93fa9ef41bd15d2b186e Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:36:53 +0100 Subject: [PATCH 13/23] Token could belong to multiple teams --- plugins/Vercel/v1/defaultContent/activity.dash.json | 4 ++-- plugins/Vercel/v1/docs/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Vercel/v1/defaultContent/activity.dash.json b/plugins/Vercel/v1/defaultContent/activity.dash.json index 19c08f1e..2a0e6aad 100644 --- a/plugins/Vercel/v1/defaultContent/activity.dash.json +++ b/plugins/Vercel/v1/defaultContent/activity.dash.json @@ -100,7 +100,7 @@ "static": false, "w": 4, "moved": false, - "h": 2, + "h": 1, "x": 0, "y": 3, "i": "fada223a-5939-4b55-bc61-3ef63deca7d2", @@ -142,7 +142,7 @@ "moved": false, "h": 3, "x": 0, - "y": 5, + "y": 4, "i": "34bbffe0-fdcd-479a-8c54-7757030d354a", "z": 0, "config": { diff --git a/plugins/Vercel/v1/docs/README.md b/plugins/Vercel/v1/docs/README.md index 814d3701..64ec2464 100644 --- a/plugins/Vercel/v1/docs/README.md +++ b/plugins/Vercel/v1/docs/README.md @@ -33,7 +33,7 @@ The plugin imports two object types into the SquaredUp graph: - **Vercel Project** — one object per project in the configured account/team. Carries the project name, framework, git repository link, and identifiers. - **Vercel Domain** — one object per custom domain. Carries the domain name, verification status, and expiry information. -- **Vercel Team** - one object per team in the configured account. Will be one or zero, as tokens can only be scoped to one team. +- **Vercel Team** - one object per team in the configured account, that the token has access to. Deployments, teams, members, activity events, and cost data are provided as **data streams** (not indexed objects) — query them on dashboards and scope deployment streams to a project. From 987c70da7bc263fa2a069223298ab0d0ce46b451 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:44:32 +0100 Subject: [PATCH 14/23] Add status colors to bar chart on deployment oob dashboard --- .../Vercel/v1/defaultContent/deployments.dash.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/plugins/Vercel/v1/defaultContent/deployments.dash.json b/plugins/Vercel/v1/defaultContent/deployments.dash.json index 97f87714..47c1df54 100644 --- a/plugins/Vercel/v1/defaultContent/deployments.dash.json +++ b/plugins/Vercel/v1/defaultContent/deployments.dash.json @@ -183,7 +183,17 @@ "config": { "data-stream-bar-chart": { "color": { - "type": "default" + "type": "custom", + "customColors": [ + { + "color": "#2BB660", + "expression": "series == \"Success\"" + }, + { + "color": "#F2164D", + "expression": "series == \"Error\"" + } + ] }, "xAxisGroup": "state_uniqueValues", "showLegend": true, From 5c2d51a734ecb00c6341939d605b1bbcace25bc6 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:57:46 +0100 Subject: [PATCH 15/23] Clean up Vercel tags and descriptions --- plugins/Vercel/v1/dataStreams/activity.json | 9 +++++++-- plugins/Vercel/v1/dataStreams/currentUser.json | 4 ++-- plugins/Vercel/v1/dataStreams/deployments.json | 2 +- plugins/Vercel/v1/dataStreams/domainConfig.json | 2 +- plugins/Vercel/v1/dataStreams/domains.json | 4 ++-- plugins/Vercel/v1/dataStreams/firewallEvents.json | 2 +- plugins/Vercel/v1/dataStreams/projects.json | 2 +- plugins/Vercel/v1/ui.json | 5 ----- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/plugins/Vercel/v1/dataStreams/activity.json b/plugins/Vercel/v1/dataStreams/activity.json index 8abd5b02..8a8f8cd4 100644 --- a/plugins/Vercel/v1/dataStreams/activity.json +++ b/plugins/Vercel/v1/dataStreams/activity.json @@ -1,7 +1,7 @@ { "name": "activity", "displayName": "Activity", - "description": "Vercel account or team activity feed, one row per audit-style event. Backs activity and audit log tiles", + "description": "Vercel account or team activity feed, one row per audit-style event", "tags": ["Activity"], "baseDataSourceName": "httpRequestUnscoped", "config": { @@ -30,7 +30,12 @@ { "name": "user.username", "displayName": "User", "visible": false }, { "name": "user.email", "displayName": "Email", "visible": false }, { "name": "userId", "displayName": "User ID", "visible": false }, - { "name": "createdAt", "displayName": "Created", "shape": "date", "role": "timestamp" } + { + "name": "createdAt", + "displayName": "Created", + "shape": "date", + "role": "timestamp" + } ], "timeframes": true } diff --git a/plugins/Vercel/v1/dataStreams/currentUser.json b/plugins/Vercel/v1/dataStreams/currentUser.json index 844ab4ac..4c2c42d1 100644 --- a/plugins/Vercel/v1/dataStreams/currentUser.json +++ b/plugins/Vercel/v1/dataStreams/currentUser.json @@ -1,8 +1,8 @@ { "name": "currentUser", "displayName": "Current User", - "description": "Returns the authenticated Vercel user. Used to validate the connection.", - "tags": [], + "description": "Returns the authenticated Vercel user. Used to validate the connection", + "tags": ["User"], "baseDataSourceName": "httpRequestUnscoped", "config": { "httpMethod": "get", diff --git a/plugins/Vercel/v1/dataStreams/deployments.json b/plugins/Vercel/v1/dataStreams/deployments.json index 42eea108..7bc73ddf 100644 --- a/plugins/Vercel/v1/dataStreams/deployments.json +++ b/plugins/Vercel/v1/dataStreams/deployments.json @@ -1,7 +1,7 @@ { "name": "deployments", "displayName": "Deployments", - "description": "Vercel deployments across the account or a selected project, one row per deployment. Backs deployment health and history tiles", + "description": "Vercel deployments across the account or a selected project, one row per deployment", "tags": ["Deployments"], "baseDataSourceName": "httpRequestUnscoped", "config": { diff --git a/plugins/Vercel/v1/dataStreams/domainConfig.json b/plugins/Vercel/v1/dataStreams/domainConfig.json index cd87e7a2..74902aa0 100644 --- a/plugins/Vercel/v1/dataStreams/domainConfig.json +++ b/plugins/Vercel/v1/dataStreams/domainConfig.json @@ -1,7 +1,7 @@ { "name": "domainConfig", "displayName": "Domain Config", - "description": "Configuration health for a single Vercel domain — whether DNS/nameservers are misconfigured, the service type, and who configured it", + "description": "Configuration health for a single Vercel domain", "tags": ["Domain"], "baseDataSourceName": "httpRequestScopedSingle", "config": { diff --git a/plugins/Vercel/v1/dataStreams/domains.json b/plugins/Vercel/v1/dataStreams/domains.json index f2608056..fb5daf3c 100644 --- a/plugins/Vercel/v1/dataStreams/domains.json +++ b/plugins/Vercel/v1/dataStreams/domains.json @@ -1,8 +1,8 @@ { "name": "domains", "displayName": "Domains", - "description": "Lists Vercel custom domains in the configured account or team. Backs the Vercel Domain import and domain inventory tiles.", - "tags": [], + "description": "Lists Vercel custom domains in the configured account or team", + "tags": ["Domain"], "baseDataSourceName": "httpRequestUnscoped", "config": { "httpMethod": "get", diff --git a/plugins/Vercel/v1/dataStreams/firewallEvents.json b/plugins/Vercel/v1/dataStreams/firewallEvents.json index f77a4298..4c9cff55 100644 --- a/plugins/Vercel/v1/dataStreams/firewallEvents.json +++ b/plugins/Vercel/v1/dataStreams/firewallEvents.json @@ -1,7 +1,7 @@ { "name": "firewallEvents", "displayName": "Firewall Events", - "description": "Per-action firewall event counts over the timeframe for a single Vercel project, one row per time-bucket and action type", + "description": "Per-action firewall event counts over the timeframe for a single Vercel project", "tags": ["Security", "Firewall"], "baseDataSourceName": "httpRequestScopedSingle", "config": { diff --git a/plugins/Vercel/v1/dataStreams/projects.json b/plugins/Vercel/v1/dataStreams/projects.json index aaf569db..44ba6496 100644 --- a/plugins/Vercel/v1/dataStreams/projects.json +++ b/plugins/Vercel/v1/dataStreams/projects.json @@ -2,7 +2,7 @@ "name": "projects", "displayName": "Projects", "description": "Lists Vercel projects in the configured account or team", - "tags": [], + "tags": ["Project"], "baseDataSourceName": "httpRequestUnscoped", "config": { "httpMethod": "get", diff --git a/plugins/Vercel/v1/ui.json b/plugins/Vercel/v1/ui.json index 7a07d071..7ee8461a 100644 --- a/plugins/Vercel/v1/ui.json +++ b/plugins/Vercel/v1/ui.json @@ -1,9 +1,4 @@ [ - { - "type": "markdown", - "name": "info", - "content": "Create a Vercel **Access Token** in [Account Settings → Tokens](https://vercel.com/account/tokens) and paste it below. To monitor a Team, also provide its **Team ID**; leave it blank to monitor your personal account." - }, { "type": "password", "name": "accessToken", From 82e23789daa5e0d8f8252bb51613229e013ab924 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:59:15 +0100 Subject: [PATCH 16/23] change deployments ordering --- plugins/Vercel/v1/defaultContent/deployments.dash.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Vercel/v1/defaultContent/deployments.dash.json b/plugins/Vercel/v1/defaultContent/deployments.dash.json index 47c1df54..d5ad551b 100644 --- a/plugins/Vercel/v1/defaultContent/deployments.dash.json +++ b/plugins/Vercel/v1/defaultContent/deployments.dash.json @@ -26,7 +26,7 @@ "id": "{{dataStreams.[deployments]}}", "sort": { "top": 10, - "by": [["created", "asc"]] + "by": [["created", "desc"]] }, "group": { "by": [], From 8a11ba29427a6180198fa6dd3b9d71f9e0aa79ab Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 14:04:49 +0100 Subject: [PATCH 17/23] title case dashboards --- plugins/Vercel/v1/defaultContent/activity.dash.json | 2 +- plugins/Vercel/v1/defaultContent/deployments.dash.json | 4 ++-- plugins/Vercel/v1/defaultContent/overview.dash.json | 2 +- plugins/Vercel/v1/defaultContent/project.dash.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/Vercel/v1/defaultContent/activity.dash.json b/plugins/Vercel/v1/defaultContent/activity.dash.json index 2a0e6aad..d69c2ab1 100644 --- a/plugins/Vercel/v1/defaultContent/activity.dash.json +++ b/plugins/Vercel/v1/defaultContent/activity.dash.json @@ -163,7 +163,7 @@ "_type": "tile/data-stream", "description": "", "activePluginConfigIds": ["{{configId}}"], - "title": "Daily activity", + "title": "Daily Activity", "visualisation": { "type": "data-stream-line-graph", "config": { diff --git a/plugins/Vercel/v1/defaultContent/deployments.dash.json b/plugins/Vercel/v1/defaultContent/deployments.dash.json index d5ad551b..3e12836a 100644 --- a/plugins/Vercel/v1/defaultContent/deployments.dash.json +++ b/plugins/Vercel/v1/defaultContent/deployments.dash.json @@ -177,7 +177,7 @@ "_type": "tile/data-stream", "description": "Deployments per day, split by state", "activePluginConfigIds": ["{{configId}}"], - "title": "Deployments per day", + "title": "Deployments Per Day", "visualisation": { "type": "data-stream-bar-chart", "config": { @@ -228,7 +228,7 @@ "moved": false, "static": false, "config": { - "title": "Average build times", + "title": "Average Build Times", "description": "", "_type": "tile/data-stream", "activePluginConfigIds": ["{{configId}}"], diff --git a/plugins/Vercel/v1/defaultContent/overview.dash.json b/plugins/Vercel/v1/defaultContent/overview.dash.json index 05694b48..e653afe6 100644 --- a/plugins/Vercel/v1/defaultContent/overview.dash.json +++ b/plugins/Vercel/v1/defaultContent/overview.dash.json @@ -36,7 +36,7 @@ "_type": "tile/data-stream", "description": "", "activePluginConfigIds": ["{{configId}}"], - "title": "Projects by Framework", + "title": "Projects By Framework", "visualisation": { "type": "data-stream-donut-chart", "config": { diff --git a/plugins/Vercel/v1/defaultContent/project.dash.json b/plugins/Vercel/v1/defaultContent/project.dash.json index 95714503..d85a05ba 100644 --- a/plugins/Vercel/v1/defaultContent/project.dash.json +++ b/plugins/Vercel/v1/defaultContent/project.dash.json @@ -318,7 +318,7 @@ "moved": false, "static": false, "config": { - "title": "Firewall events", + "title": "Firewall Events", "description": "", "_type": "tile/data-stream", "dataStream": { From 360104d5bfb62f9373d114de067ba4cd204e9adb Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 14:06:15 +0100 Subject: [PATCH 18/23] remove orphaned cost script --- plugins/Vercel/v1/dataStreams/scripts/cost.js | 25 ------------------- 1 file changed, 25 deletions(-) delete mode 100644 plugins/Vercel/v1/dataStreams/scripts/cost.js diff --git a/plugins/Vercel/v1/dataStreams/scripts/cost.js b/plugins/Vercel/v1/dataStreams/scripts/cost.js deleted file mode 100644 index 32245cc8..00000000 --- a/plugins/Vercel/v1/dataStreams/scripts/cost.js +++ /dev/null @@ -1,25 +0,0 @@ -// GET /v1/billing/charges returns application/jsonl — newline-delimited JSON -// objects (FOCUS v1.3 cost/usage records), NOT a JSON array. The handler only -// auto-parses JSON/XML, so `data` is null/incomplete here; the raw text is on -// response.body. Split on newlines, drop empties, JSON.parse each line, then map -// each FOCUS record to a flat row with real JS primitives. -const records = (response.body || "") - .split("\n") - .map((line) => line.trim()) - .filter((line) => line.length > 0) - .map((line) => JSON.parse(line)); - -const num = (v) => (v === null || v === undefined || v === "" ? null : Number(v)); - -result = records.map((r) => { - const tags = r.Tags || {}; - return { - service: r.ServiceName, - billedCost: num(r.BilledCost), - effectiveCost: num(r.EffectiveCost), - quantity: num(r.ConsumedQuantity), - unit: r.ConsumedUnit, - projectName: tags.ProjectName || tags.ProjectId || null, - periodStart: r.ChargePeriodStart - }; -}); From bd902317b83452b42500ceab7b7b670ee79d8417 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 14:07:34 +0100 Subject: [PATCH 19/23] remove cost from description --- plugins/Vercel/v1/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Vercel/v1/metadata.json b/plugins/Vercel/v1/metadata.json index e143c7e9..ae1a7c28 100644 --- a/plugins/Vercel/v1/metadata.json +++ b/plugins/Vercel/v1/metadata.json @@ -3,7 +3,7 @@ "displayName": "Vercel", "version": "1.0.0", "author": { "name": "@andrewmumblebee", "type": "community" }, - "description": "Monitor your Vercel projects, deployments, and domains in SquaredUp — track deployment health and history, domain verification and expiry, team activity, a cost overview, and firewall (WAF) event activity.", + "description": "Monitor your Vercel projects, deployments, and domains in SquaredUp - track deployment health and history, domains, team activity, and firewall (WAF) event activity.", "category": "Cloud Platforms", "type": "hybrid", "schemaVersion": "2.1", From 9f8473e33ccff88f864c7f2cb2e4f346c1373ff1 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 14:24:29 +0100 Subject: [PATCH 20/23] Claude review fixes --- plugins/Vercel/v1/configValidation.json | 2 +- plugins/Vercel/v1/docs/README.md | 8 ++++---- plugins/Vercel/v1/metadata.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/Vercel/v1/configValidation.json b/plugins/Vercel/v1/configValidation.json index 2ac57f11..d5dc3a2a 100644 --- a/plugins/Vercel/v1/configValidation.json +++ b/plugins/Vercel/v1/configValidation.json @@ -4,7 +4,7 @@ "displayName": "Authenticate", "dataStream": { "name": "currentUser" }, "required": true, - "error": "Could not authenticate with Vercel. Check that your API Token is valid and has not expired.", + "error": "Could not authenticate with Vercel. Check that your API token is valid and has not expired.", "success": "Connected to Vercel successfully." } ] diff --git a/plugins/Vercel/v1/docs/README.md b/plugins/Vercel/v1/docs/README.md index 64ec2464..b510a6fa 100644 --- a/plugins/Vercel/v1/docs/README.md +++ b/plugins/Vercel/v1/docs/README.md @@ -29,19 +29,19 @@ For the **team members** streams, the token must belong to a Team and carry a ro ## What gets indexed -The plugin imports two object types into the SquaredUp graph: +The plugin imports three object types into the SquaredUp graph: - **Vercel Project** — one object per project in the configured account/team. Carries the project name, framework, git repository link, and identifiers. - **Vercel Domain** — one object per custom domain. Carries the domain name, verification status, and expiry information. - **Vercel Team** - one object per team in the configured account, that the token has access to. -Deployments, teams, members, activity events, and cost data are provided as **data streams** (not indexed objects) — query them on dashboards and scope deployment streams to a project. +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. ## 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. -- **Team members and cost require a Team** and a token with adequate role/plan. On personal/Hobby accounts these streams may be empty. +- **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. +- **Team members require a Team** and a token with adequate role/plan. On personal/Hobby accounts these streams may be empty. - **Firewall events only appear for projects with the WAF configured.** The Firewall events stream returns rows only for projects that have the Vercel Firewall set up and that recorded events within the selected timeframe; a project with no firewall activity in that window shows an empty result (not an error). - **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. diff --git a/plugins/Vercel/v1/metadata.json b/plugins/Vercel/v1/metadata.json index ae1a7c28..bfeda2b8 100644 --- a/plugins/Vercel/v1/metadata.json +++ b/plugins/Vercel/v1/metadata.json @@ -20,7 +20,7 @@ "firewall", "waf" ], - "objectTypes": ["Project", "Domain"], + "objectTypes": ["Project", "Domain", "Team"], "links": [ { "category": "documentation", From 88f9c728a12db25fa1226ca753088edf7763a729 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 15:37:16 +0100 Subject: [PATCH 21/23] Vercel claude review tweaks --- plugins/Vercel/v1/dataStreams/deployments.json | 4 ++-- plugins/Vercel/v1/defaultContent/deployments.dash.json | 2 +- plugins/Vercel/v1/docs/README.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/Vercel/v1/dataStreams/deployments.json b/plugins/Vercel/v1/dataStreams/deployments.json index 7bc73ddf..153fa2d7 100644 --- a/plugins/Vercel/v1/dataStreams/deployments.json +++ b/plugins/Vercel/v1/dataStreams/deployments.json @@ -64,10 +64,10 @@ "shape": "date", "role": "timestamp" }, - { "name": "url", "displayName": "Url", "shape": "url" }, + { "name": "url", "displayName": "URL", "shape": "url" }, { "name": "inspectorUrl", - "displayName": "Inspector Url", + "displayName": "Inspector URL", "shape": "url" }, { "name": "creator", "displayName": "Creator" }, diff --git a/plugins/Vercel/v1/defaultContent/deployments.dash.json b/plugins/Vercel/v1/defaultContent/deployments.dash.json index 3e12836a..d1f0ba19 100644 --- a/plugins/Vercel/v1/defaultContent/deployments.dash.json +++ b/plugins/Vercel/v1/defaultContent/deployments.dash.json @@ -177,7 +177,7 @@ "_type": "tile/data-stream", "description": "Deployments per day, split by state", "activePluginConfigIds": ["{{configId}}"], - "title": "Deployments Per Day", + "title": "Deployments per Day", "visualisation": { "type": "data-stream-bar-chart", "config": { diff --git a/plugins/Vercel/v1/docs/README.md b/plugins/Vercel/v1/docs/README.md index b510a6fa..3a86a590 100644 --- a/plugins/Vercel/v1/docs/README.md +++ b/plugins/Vercel/v1/docs/README.md @@ -25,7 +25,7 @@ For the **team members** streams, the token must belong to a Team and carry a ro | Field | Required | Description | Where to find it | | ------------- | -------- | -------------------------------------------------------------------------------------- | -------------------------------------- | -| **API Token** | Yes | The Vercel Access Token used to authenticate. Sent as a bearer token on every request. | Account Settings → Tokens (see above). | +| **API token** | Yes | The Vercel Access Token used to authenticate. Sent as a bearer token on every request. | Account Settings → Tokens (see above). | ## What gets indexed From 04dc94c8b96a18d9e57295c4f2212e5231ddab22 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 15:38:04 +0100 Subject: [PATCH 22/23] remove unwrap as not needed --- plugins/Vercel/v1/dataStreams/scripts/deployments.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/Vercel/v1/dataStreams/scripts/deployments.js b/plugins/Vercel/v1/dataStreams/scripts/deployments.js index 18402b4e..3313455a 100644 --- a/plugins/Vercel/v1/dataStreams/scripts/deployments.js +++ b/plugins/Vercel/v1/dataStreams/scripts/deployments.js @@ -1,14 +1,10 @@ // /v7/deployments returns { deployments: [...], pagination: {...} }. // One row per deployment. Optional `project` objects-picker (stream ui name // "project") arrives at context.config.project as an array (multi-select), each -// 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); const selected = (context.config && context.config.project) || []; -const projectIds = new Set( - selected.map((o) => unwrap(o.rawId)).filter(Boolean), -); +const projectIds = new Set(selected.map((o) => o.rawId).filter(Boolean)); // Token paging walks `until` from pagination.next back to the `since` floor, so // the first page starts at "now". For a historical timeframe (e.g. lastMonth) From 0d456448a0ee0b05fba5f976c034520de32d12d5 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 15:48:21 +0100 Subject: [PATCH 23/23] By -> by --- plugins/Vercel/v1/defaultContent/overview.dash.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Vercel/v1/defaultContent/overview.dash.json b/plugins/Vercel/v1/defaultContent/overview.dash.json index e653afe6..05694b48 100644 --- a/plugins/Vercel/v1/defaultContent/overview.dash.json +++ b/plugins/Vercel/v1/defaultContent/overview.dash.json @@ -36,7 +36,7 @@ "_type": "tile/data-stream", "description": "", "activePluginConfigIds": ["{{configId}}"], - "title": "Projects By Framework", + "title": "Projects by Framework", "visualisation": { "type": "data-stream-donut-chart", "config": {