From b2ad3f37c16c6b34b662d381d2a54da5a0fa8ce3 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Tue, 16 Jun 2026 14:46:03 +0100 Subject: [PATCH 01/33] 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 77b85f66046a514c71cb6af199e4d1e4d543ab78 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Tue, 16 Jun 2026 14:53:28 +0100 Subject: [PATCH 02/33] 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 c2f595c627d3d4df34de7521346394988443853d Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Thu, 18 Jun 2026 06:50:02 +0100 Subject: [PATCH 03/33] 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 1da1f9c6f12f424659c97bcbc92a93febf2f5303 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Thu, 18 Jun 2026 06:52:27 +0100 Subject: [PATCH 04/33] 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 a48c4092edfe03178b4ca7714649b5f66c6171af Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Thu, 18 Jun 2026 12:37:03 +0100 Subject: [PATCH 05/33] 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 a183bb0fc9437bd17fc95e8c8df9023f400fe6c3 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 12:52:50 +0100 Subject: [PATCH 06/33] 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 d312f4e5c9ae1446bb98a6103debb8c0170272f7 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 12:54:39 +0100 Subject: [PATCH 07/33] 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 189cf4eaecb4a2640827354d24ffdbc871821499 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:03:35 +0100 Subject: [PATCH 08/33] 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 b12c5f7843361b770bfb84a8c85c8a5dd1773156 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:04:47 +0100 Subject: [PATCH 09/33] 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 accc3ba9350701b4eb31c3a347d2e15ce2e6566a Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Wed, 24 Jun 2026 13:40:22 +0100 Subject: [PATCH 10/33] 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 932be760..89af6e2b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -25,6 +25,7 @@ plugins/UniFi/* @adamkinniburgh plugins/UptimeRobot/* @kieranlangton plugins/WorldCup2026/* @TimWheeler-SQUP plugins/Algolia/* @andrewmumblebee +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 a93e89d1b83d5b640cf94c30ba91714b37aeb68c Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:35:04 +0100 Subject: [PATCH 11/33] 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 7f0ba71b1d225f62e046d31eb1c5cbe0825d2d18 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:35:24 +0100 Subject: [PATCH 12/33] 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 a402b01dc6f3edfdbfc7de3ea22db4852cf68e36 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:36:53 +0100 Subject: [PATCH 13/33] 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 caa95be955b6ca7d513482ddc2afedefac6ccaf7 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:44:32 +0100 Subject: [PATCH 14/33] 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 a83929e56c436a2e486aa7fdb238ab5085e1dc97 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:57:46 +0100 Subject: [PATCH 15/33] 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 19195d2c6ff3c85d07c23c8660d04a5eccac9662 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:59:15 +0100 Subject: [PATCH 16/33] 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 4b43045f845a7294ded2d66e86b08c3b496d541b Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 14:04:49 +0100 Subject: [PATCH 17/33] 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 e3b701d5f769e7e534c76872bda760aa009c43bd Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 14:06:15 +0100 Subject: [PATCH 18/33] 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 b7aca2d3e61d92992c1358e534f7659c0342f3b0 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 14:07:34 +0100 Subject: [PATCH 19/33] 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 3dc61a20144f3f3279409a413eea66ec8b00f528 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 14:24:29 +0100 Subject: [PATCH 20/33] 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 fb0478b6efdbcc481d15c3a084ef7527409080c1 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 15:37:16 +0100 Subject: [PATCH 21/33] 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 7832970779ca70a6a116fee246bfa536e4411a5a Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 15:38:04 +0100 Subject: [PATCH 22/33] 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 965757c46cb26a320946cda545d2b65c18d87bbe Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 15:48:21 +0100 Subject: [PATCH 23/33] 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": { From 4d2cdfc7d826ca27dda52e2d86b17d4c72570619 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Mon, 22 Jun 2026 10:56:24 +0100 Subject: [PATCH 24/33] Remove teams comment about non-indexed objects --- plugins/Vercel/v1/docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Vercel/v1/docs/README.md b/plugins/Vercel/v1/docs/README.md index 3a86a590..116552a3 100644 --- a/plugins/Vercel/v1/docs/README.md +++ b/plugins/Vercel/v1/docs/README.md @@ -35,7 +35,7 @@ The plugin imports three object types into the SquaredUp graph: - **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, and activity events are provided as **data streams** (not indexed objects) — query them on dashboards and scope deployment streams to a project. +Deployments, members, and activity events are provided as **data streams** (not indexed objects) — query them on dashboards and scope deployment streams to a project. ## Known limitations From ed4ca406b86524fc6fba82d13d6961d73ebac53f Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Tue, 23 Jun 2026 06:56:14 +0100 Subject: [PATCH 25/33] remove monitorOld reference --- .../Vercel/v1/defaultContent/domain.dash.json | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/plugins/Vercel/v1/defaultContent/domain.dash.json b/plugins/Vercel/v1/defaultContent/domain.dash.json index 5e8cb0a8..849018dc 100644 --- a/plugins/Vercel/v1/defaultContent/domain.dash.json +++ b/plugins/Vercel/v1/defaultContent/domain.dash.json @@ -39,21 +39,13 @@ "config": { "data-stream-table": { "transpose": true, - "columnOrder": ["misconfigured", "serviceType", "configuredBy"] + "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"] } - } } } }, @@ -87,7 +79,12 @@ "config": { "data-stream-table": { "transpose": true, - "hiddenColumns": ["id", "link", "links", "label"] + "hiddenColumns": [ + "id", + "link", + "links", + "label" + ] } } } From 8149bb116970bf48f71a24de05dea8c80f2f7b79 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Tue, 23 Jun 2026 08:14:06 +0100 Subject: [PATCH 26/33] fix codeowners fallback superseding any defined authors --- .github/CODEOWNERS | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 89af6e2b..d0605412 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,6 +3,9 @@ /LICENSE @squaredup/community-moderators /.github/* @squaredup/community-moderators +# Fallback – if a plugin has no specified author +plugins/* @squaredup/community-moderators + # Request review from original author plugins/AutoTask/* @TimWheeler-SQUP plugins/DattoRMM/* @TimWheeler-SQUP @@ -28,5 +31,3 @@ plugins/Algolia/* @andrewmumblebee plugins/Vercel/* @andrewmumblebee -# Fallback – if a plugin has no specified author -plugins/* @squaredup/community-moderators From 0e17a6b9d504dd5d44de6f3d7d2786516347b2f8 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Tue, 23 Jun 2026 08:47:32 +0100 Subject: [PATCH 27/33] hide email by default in teamMembers --- plugins/Vercel/v1/dataStreams/teamMembers.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/Vercel/v1/dataStreams/teamMembers.json b/plugins/Vercel/v1/dataStreams/teamMembers.json index 6342de2b..34363e9a 100644 --- a/plugins/Vercel/v1/dataStreams/teamMembers.json +++ b/plugins/Vercel/v1/dataStreams/teamMembers.json @@ -34,7 +34,12 @@ "shape": "string", "role": "label" }, - { "name": "email", "displayName": "Email", "shape": "string" }, + { + "name": "email", + "displayName": "Email", + "shape": "string", + "visible": false + }, { "name": "role", "displayName": "Role", "shape": "string" }, { "name": "confirmed", "displayName": "Confirmed", "shape": "boolean" }, { "name": "createdAt", "displayName": "Created", "shape": "date" } From 0e9224188c74b521502bc9d0b3a9b51f97a23a88 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Tue, 23 Jun 2026 08:47:40 +0100 Subject: [PATCH 28/33] up activity limit to 1000 --- plugins/Vercel/v1/dataStreams/activity.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/Vercel/v1/dataStreams/activity.json b/plugins/Vercel/v1/dataStreams/activity.json index 8a8f8cd4..93003bb9 100644 --- a/plugins/Vercel/v1/dataStreams/activity.json +++ b/plugins/Vercel/v1/dataStreams/activity.json @@ -12,9 +12,11 @@ "getArgs": [ { "key": "since", "value": "{{timeframe.start}}" }, { "key": "until", "value": "{{timeframe.end}}" }, - { "key": "limit", "value": "100" } + { "key": "limit", "value": "1000" } ], - "paging": { "mode": "none" } + "paging": { + "mode": "none" + } }, "matches": "none", "metadata": [ From 08f7d1719725a68acf17447e329ae1948e0e6821 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Tue, 23 Jun 2026 08:48:00 +0100 Subject: [PATCH 29/33] shift activity dashboard tiles --- plugins/Vercel/v1/defaultContent/activity.dash.json | 6 +++--- plugins/Vercel/v1/defaultContent/project.dash.json | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/Vercel/v1/defaultContent/activity.dash.json b/plugins/Vercel/v1/defaultContent/activity.dash.json index d69c2ab1..eee9c75a 100644 --- a/plugins/Vercel/v1/defaultContent/activity.dash.json +++ b/plugins/Vercel/v1/defaultContent/activity.dash.json @@ -100,9 +100,9 @@ "static": false, "w": 4, "moved": false, - "h": 1, + "h": 2, "x": 0, - "y": 3, + "y": 5, "i": "fada223a-5939-4b55-bc61-3ef63deca7d2", "z": 0, "config": { @@ -142,7 +142,7 @@ "moved": false, "h": 3, "x": 0, - "y": 4, + "y": 3, "i": "34bbffe0-fdcd-479a-8c54-7757030d354a", "z": 0, "config": { diff --git a/plugins/Vercel/v1/defaultContent/project.dash.json b/plugins/Vercel/v1/defaultContent/project.dash.json index d85a05ba..08ec4c2d 100644 --- a/plugins/Vercel/v1/defaultContent/project.dash.json +++ b/plugins/Vercel/v1/defaultContent/project.dash.json @@ -288,6 +288,7 @@ "_type": "tile/data-stream", "dataStream": { "id": "{{dataStreams.[firewallEvents]}}", + "pluginConfigId": "{{configId}}", "name": "firewallEvents" }, "scope": { @@ -323,6 +324,7 @@ "_type": "tile/data-stream", "dataStream": { "id": "{{dataStreams.[firewallEvents]}}", + "pluginConfigId": "{{configId}}", "name": "firewallEvents" }, "scope": { From b047e0657423a310232e1ee94b1b3eddd06b827c Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Tue, 23 Jun 2026 08:48:11 +0100 Subject: [PATCH 30/33] update Vercel readme --- plugins/Vercel/v1/docs/README.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/plugins/Vercel/v1/docs/README.md b/plugins/Vercel/v1/docs/README.md index 116552a3..aa1746b8 100644 --- a/plugins/Vercel/v1/docs/README.md +++ b/plugins/Vercel/v1/docs/README.md @@ -1,14 +1,8 @@ -## What this plugin monitors +# Vercel plugin 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). -- **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. - -## Prerequisites +## Before you start 1. Sign in to Vercel and open **Account Settings → Tokens** (). 2. Click **Create Token**. @@ -21,12 +15,22 @@ Monitor your [Vercel](https://vercel.com) projects, deployments, and domains in 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. +Paste this token in the **API token** field. + ## 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). | +## What is monitored + +- **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). +- **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. + ## What gets indexed The plugin imports three object types into the SquaredUp graph: From d9791dc26cf77e50d1d728f45b02295a69013b17 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Tue, 23 Jun 2026 09:31:21 +0100 Subject: [PATCH 31/33] deployments filtering was not working --- plugins/Vercel/v1/dataStreams/deployments.json | 4 +++- plugins/Vercel/v1/dataStreams/scripts/deployments.js | 7 +------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/plugins/Vercel/v1/dataStreams/deployments.json b/plugins/Vercel/v1/dataStreams/deployments.json index 153fa2d7..0560443e 100644 --- a/plugins/Vercel/v1/dataStreams/deployments.json +++ b/plugins/Vercel/v1/dataStreams/deployments.json @@ -4,12 +4,14 @@ "description": "Vercel deployments across the account or a selected project, one row per deployment", "tags": ["Deployments"], "baseDataSourceName": "httpRequestUnscoped", + "providesPluginDiagnostics": true, "config": { "httpMethod": "get", "endpointPath": "v7/deployments", "postRequestScript": "deployments.js", "getArgs": [ - { "key": "since", "value": "{{timeframe.unixStart * 1000}}" } + { "key": "since", "value": "{{timeframe.unixStart * 1000}}" }, + { "key": "projectIds", "value": "{{ project?.map(p => p.rawId) }}" } ], "paging": { "mode": "token", diff --git a/plugins/Vercel/v1/dataStreams/scripts/deployments.js b/plugins/Vercel/v1/dataStreams/scripts/deployments.js index 3313455a..6353b09d 100644 --- a/plugins/Vercel/v1/dataStreams/scripts/deployments.js +++ b/plugins/Vercel/v1/dataStreams/scripts/deployments.js @@ -3,9 +3,6 @@ // "project") arrives at context.config.project as an array (multi-select), each const deployments = (data && data.deployments) || []; -const selected = (context.config && context.config.project) || []; -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) // that over-fetches rows newer than the window end — bound the top here. @@ -14,9 +11,7 @@ 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), + (d) => untilMs === null || d.created <= untilMs, ); result = scoped.map((d) => ({ From 817a3050cd42883afec9c7f56a474fa9c9645d6b Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Wed, 24 Jun 2026 13:39:53 +0100 Subject: [PATCH 32/33] bump up activity limit --- plugins/Vercel/v1/dataStreams/activity.json | 2 +- plugins/Vercel/v1/defaultContent/activity.dash.json | 2 +- plugins/Vercel/v1/defaultContent/deployments.dash.json | 4 ++++ plugins/Vercel/v1/docs/README.md | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/Vercel/v1/dataStreams/activity.json b/plugins/Vercel/v1/dataStreams/activity.json index 93003bb9..80815176 100644 --- a/plugins/Vercel/v1/dataStreams/activity.json +++ b/plugins/Vercel/v1/dataStreams/activity.json @@ -12,7 +12,7 @@ "getArgs": [ { "key": "since", "value": "{{timeframe.start}}" }, { "key": "until", "value": "{{timeframe.end}}" }, - { "key": "limit", "value": "1000" } + { "key": "limit", "value": "5000" } ], "paging": { "mode": "none" diff --git a/plugins/Vercel/v1/defaultContent/activity.dash.json b/plugins/Vercel/v1/defaultContent/activity.dash.json index eee9c75a..9b27a7ab 100644 --- a/plugins/Vercel/v1/defaultContent/activity.dash.json +++ b/plugins/Vercel/v1/defaultContent/activity.dash.json @@ -102,7 +102,7 @@ "moved": false, "h": 2, "x": 0, - "y": 5, + "y": 6, "i": "fada223a-5939-4b55-bc61-3ef63deca7d2", "z": 0, "config": { diff --git a/plugins/Vercel/v1/defaultContent/deployments.dash.json b/plugins/Vercel/v1/defaultContent/deployments.dash.json index d1f0ba19..9853fbdb 100644 --- a/plugins/Vercel/v1/defaultContent/deployments.dash.json +++ b/plugins/Vercel/v1/defaultContent/deployments.dash.json @@ -126,6 +126,10 @@ "group": { "by": [], "aggregate": [] + }, + "sort": { + "top": 10, + "by": [["created", "desc"]] } }, "_type": "tile/data-stream", diff --git a/plugins/Vercel/v1/docs/README.md b/plugins/Vercel/v1/docs/README.md index aa1746b8..78c73f47 100644 --- a/plugins/Vercel/v1/docs/README.md +++ b/plugins/Vercel/v1/docs/README.md @@ -11,7 +11,7 @@ Monitor your [Vercel](https://vercel.com) projects, deployments, and domains in - 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. +6. Click **Create** and copy the token value then paste it into the **API token** field. 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. From adf9d63ed170627df2910f452a2a0e10dd09ae2b Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Tue, 30 Jun 2026 10:18:39 +0100 Subject: [PATCH 33/33] rework Vercel Readme --- plugins/Vercel/v1/docs/README.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/plugins/Vercel/v1/docs/README.md b/plugins/Vercel/v1/docs/README.md index 78c73f47..a11b071e 100644 --- a/plugins/Vercel/v1/docs/README.md +++ b/plugins/Vercel/v1/docs/README.md @@ -1,8 +1,4 @@ -# Vercel plugin - -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. - -## Before you start +## Setup 1. Sign in to Vercel and open **Account Settings → Tokens** (). 2. Click **Create Token**. @@ -11,12 +7,11 @@ Monitor your [Vercel](https://vercel.com) projects, deployments, and domains in - 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 then paste it into the **API token** field. +6. Click **Create** and copy the token value +7. Paste this token into the **API token** field. 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. -Paste this token in the **API token** field. - ## Configuration fields | Field | Required | Description | Where to find it | @@ -25,6 +20,8 @@ Paste this token in the **API token** field. ## What is monitored +Monitors 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.)