| name | superplane-dashboard-and-widgets |
| description | Implements and configures SuperPlane canvas dashboards (markdown, node, table, chart, number panels), widget data sources, CEL/templates, table row trigger actions, and dashboard YAML. Use when editing dashboard UI, panelTypes, useWidgetData, WidgetTable, canvas_dashboard_yml, Get/UpdateCanvasDashboard, or docs/prd/dashboard-and-widgets.md. |
SuperPlane dashboard and widgets
Use this skill when working on per-canvas dashboards: the workflow v2 overlay, typed panels, widget renderers, YAML import/export, or backend validation.
Canonical reference: docs/prd/dashboard-and-widgets.md — read it for full schemas, examples, and maintenance notes. This skill is the operational subset for agents.
Product rules (do not break)
- One dashboard per canvas (not templates). Stored in
canvas_dashboards as JSON panels + layout.
- Dashboard mode hides the graph; 12-column
react-grid-layout (DashboardView).
- Edit (panels, layout, YAML import):
canvases:update, not template, canvas not deleted.
- Run (node panel Run, table row actions): same as edit —
InvokeNodeTriggerHook; UI uses canRunNodes.
- YAML import is replace-all (max 50 panels, 1 MiB payload).
- User-facing name: SuperPlane (capital P).
- Row actions are
kind: trigger only — they fire trigger nodes; they do not call HTTP Request nodes directly.
Layer map
| Layer | Key paths |
|---|
| Host | web_src/src/pages/workflowv2/index.tsx — mode, feature flag, header |
| Overlay gate | dashboard/WorkflowDashboardOverlay.tsx |
| Overlay | dashboard/DashboardOverlay.tsx — query/mutation, context |
| Context | dashboard/DashboardContext.tsx, DashboardContextProvider.tsx |
| Trigger hook | dashboard/useDashboardTriggerNode.ts |
| Grid | dashboard/DashboardView.tsx |
| Local state | dashboard/useDashboardPanelState.ts (500ms debounced save) |
| Schema | dashboard/panelTypes.ts — types, templates, validators, normalizeTablePanelContent |
| YAML (FE) | dashboard/dashboardYaml.ts, DashboardYamlModal.tsx |
| Widget data | dashboard/widget/useWidgetData.ts |
| Widget UI | dashboard/widget/WidgetTable.tsx, WidgetChart.tsx, WidgetNumber.tsx |
| Backend | pkg/models/canvas_dashboard.go, canvas_dashboard_yml.go |
| API | pkg/grpc/actions/canvases/get_canvas_dashboard.go, update_canvas_dashboard.go |
| Proto | protos/canvases.proto — DashboardPanel, CanvasDashboard |
Invariant: panelTypes.ts validators, canvas_dashboard_yml.go, and widget types.ts must agree. Frontend fast-fail; backend authoritative on import.
Node references: always accept id or name via resolveDashboardNode in DashboardContext.tsx.
Panel types
type | Runtime | Main content |
|---|
markdown | GFM body | title?, body? |
node | Status chip + optional Run | node, showRun?, triggerName? |
table | WidgetTable | dataSource, render.kind: "table" |
chart | WidgetChart (SVG) | dataSource, render.kind: "chart" |
number | WidgetNumber | dataSource, render.kind: "number" |
New panels: templateForPanelType in panelTypes.ts. Draft states (e.g. empty memory namespace) should stay valid where possible.
Data sources (useWidgetData)
{ kind: "memory", namespace: string, fieldPath?: string }
{ kind: "executions", node?: string, limit?: number }
{ kind: "runs", limit?: number }
| Kind | Query | Notes |
|---|
memory | useCanvasMemoryEntries | Filter by namespace; fieldPath flattens nested lists (memoryRow.ts) |
executions | useInfiniteCanvasEvents | Flatten executions[]; optional node filter; eager pages until limit or cap (~500 events) |
runs | useInfiniteCanvasRuns | totalCount for count KPIs |
Execution rows get status, nodeName, durationMs. Status vocabulary: passed, failed, running, pending, cancelled, unknown.
Table panels (most complex)
Columns
Non-empty field; optional label, format (text, number, status, relative, link, …), show, href.
Filters
render.where[] — AND list; ops: eq, neq, contains, not_contains, gt, lt, exists, not_exists.
Row actions (trigger)
Required: kind: trigger, node (id or name). Optional: hook (default run), template, payload, confirm, show, variant, icon.
Runtime flow: WidgetTable → mergeTriggerPayload → onTriggerNode → useDashboardTriggerNode → InvokeNodeTriggerHook → invalidate events/runs/memory queries.
Legacy fields normalized in FE: target → node, triggerName → template.
Expressions
{{ CEL }} — cel-js via widget/celExpr.ts; row env + now (Unix seconds).
- Legacy
show — e.g. status == "running" (showExpression.ts, rowVisibility.ts).
- Prefer structured
where for simple validated filters.
Lint: loose equality in legacy expressions is intentional (scalar normalization). Do not add eslint-disable for == in dashboard code; refactor instead.
Editor memory hints: MemoryDiscoveryPanel.tsx, useMemoryCatalog.ts (suggestions only; YAML still validated).
Chart and number
Chart render.type: bar, stacked-bar, line, area, donut. xField + series[]; omit series[].field to count rows per bucket.
Number aggregations: count, sum, avg, min, max, first, last — non-count requires field.
YAML
apiVersion: v1
kind: Dashboard
metadata:
canvasId: <uuid>
name: <display>
spec:
panels: [{ id, type, content }]
layout: [{ i, x, y, w, h, minW?, minH? }]
- FE:
dashboardYaml.ts — parse/serialize + validatePanelContent
- BE:
DashboardFromYML / DashboardToYML in canvas_dashboard_yml.go
- Unknown fields rejected; missing
panels/layout → empty lists
Agent workflows
Fix a dashboard bug
- Reproduce in dashboard mode (not template); note panel
type and dataSource.kind.
- Trace: panel card →
useWidgetData → widget renderer → (if trigger) useDashboardTriggerNode.
- Check permissions in
pkg/authorization/interceptor.go if RPC-related.
- Add/update test under
web_src/src/pages/workflowv2/dashboard/**/*.spec.ts.
Add or change panel content fields
widget/types.ts (if widget-facing)
panelTypes.ts — interface, templateForPanelType, validatePanelContent, normalization
canvas_dashboard_yml.go — mirror validation
- Panel card + form component
- YAML tests:
dashboardYaml.spec.ts, canvas_dashboard_yml_test.go
Add a new panel type
PANEL_TYPES, PANEL_TYPE_META, validator, template
AllowedDashboardPanelTypes in Go
*PanelCard.tsx + DashboardView PanelCardRouter
- Update docs/prd/dashboard-and-widgets.md
Add a data source kind
- Extend types in
widget/types.ts + panelTypes.ts
DataSourceForm.tsx editor
- Branch in
useWidgetData.ts
- Backend YAML validator + tests
Configure memory table (user/agent task)
Use PRD example; namespace must match canvas memory keys. Row actions target trigger nodes only.
Verification
cd web_src && npm run test:run -- src/pages/workflowv2/dashboard
make format.js
make check.lint.ui
make check.build.ui
make format.go
make lint
make check.build.app
go test ./pkg/models -run 'TestDashboard|TestValidateDashboardContent'
go test ./pkg/grpc/actions/canvases -run CanvasDashboard
Repo conventions
- No
web_src/src/utils/* — use lib/ or hooks/.
- Dashboard has strict ESLint budget — refactor touched code; do not raise the budget.
- Split large components for Fast Refresh where the codebase already does.
- Never hand-write DB migrations;
make db.migration.create NAME=<dash-name> if persistence changes.
- AGENTS.md: protobuf enum mapping, authorization on new RPCs.
Quick file index
| Task | Start here |
|---|
| Grid / add panel | DashboardView.tsx, useDashboardPanelState.ts |
| Table CEL / filters / actions | WidgetTable.tsx, celExpr.ts, evalTableWhere.ts, mergeTriggerPayload.ts |
| Table editor | TablePanelForm.tsx, TablePanelFormRows.tsx |
| Trigger from dashboard | useDashboardTriggerNode.ts, dashboardTriggerParameters.ts |
| Node status chip | NodePanelCard.tsx, deriveNodeStatuses.ts |
| Header dashboard actions | dashboardHeaderActions.ts, useDashboardModeActions.ts |
| API hooks | web_src/src/hooks/useCanvasData.ts — useCanvasDashboard, useUpdateCanvasDashboard |