Skip to content

[Hackathon] feat: Dashboard Visualizer — Presentable Results Boards from Workflow Data#5116

Open
EmilySun621 wants to merge 16 commits into
apache:mainfrom
EmilySun621:hackathon/dashboard-visualizer
Open

[Hackathon] feat: Dashboard Visualizer — Presentable Results Boards from Workflow Data#5116
EmilySun621 wants to merge 16 commits into
apache:mainfrom
EmilySun621:hackathon/dashboard-visualizer

Conversation

@EmilySun621
Copy link
Copy Markdown

Stop copy-pasting screenshots into slides.

Build live, interactive dashboards from your actual workflow results — drag, resize, and present directly from Texera.


😤 The Problem

You just ran a 15-operator ML pipeline. Your PI asks: "What did you find?"

Today's answer: open each operator's result panel one by one, take screenshots, paste into PowerPoint, lose all interactivity. 30 minutes of manual work for a 5-minute meeting.

✨ The Solution

One dashboard. Real data. Zero screenshots.

Run workflow → Results auto-cached → Open Dashboard → Add widgets → Present to PI

🚀 What We Built

📋 Dashboard Gallery

Sidebar → Your Work → Dashboards

Create, manage, and organize multiple dashboards — one per project, one per experiment, one per meeting.


➕ Add Widget — Two Paths

🔗 Path 1: From Workflow (the magic path)

Pick workflow → See REAL results → Click to add → Widget appears with live data
  • Results are auto-cached from WebSocket during workflow execution — no extra step needed
  • Plotly charts (scatter plots, confusion matrices) render as live interactive iframes
  • Metrics auto-extracted from result tables → instant accuracy/F1/precision cards
  • Every widget shows its source: "🔗 From [Example] Machine Learning on Iris Dataset"

✏️ Path 2: Manual Input

Quick presets for common metrics:

[+ Accuracy] [+ F1 Score] [+ Precision] [+ Recall] [+ AUC] [+ Sample Count]

Click → enter number → done. Also supports custom bar charts, donut charts, tables, and text notes.


🧩 7 Widget Types

Widget What it shows Example
🔢 Metric Card Single big number Accuracy: 96.7%
📊 Bar Chart Compare values across categories Model comparison (SVG)
🍩 Donut Chart Proportions and distributions Class balance 65/35 (SVG)
📈 Horizontal Bars Ranked items Feature importance scores (SVG)
📋 Table Full data table Complete metrics comparison
📝 Text / Notes Annotations "Key finding: LR outperforms RF"
🖼️ HTML / Chart Live Plotly visualizations Interactive scatter plot in iframe
Screenshot 2026-05-16 at 3 58 43 PM

Emily Sun and others added 16 commits May 15, 2026 21:55
This bundles the feature work that built up on this branch:

- Custom agents: dashboard CRUD page and editor dialog (48px icon tile,
  chip-style guardrails, model selector). Each custom agent now carries a
  LiteLLM model_name (Opus 4.7 / Haiku 4.5) that is passed through to the
  agent-service so different agents can use different models.

- Conversation history is scoped per (workflowId, agentId): switching
  agent or workflow yields a different conversation list. localStorage
  key: texera.workflowConversations.v1.{workflowId}.{agentId}.

- Time machine: workflow snapshot list, revert, and agent-tagged
  checkpoints. New workflow-history-tool in agent-service backs the
  "undo my last change" flow; amber gains a WorkflowSnapshotResource;
  sql/updates/23.sql adds the snapshot table.

- Operator-aware custom-agent prompts: the system prompt now injects the
  full operator catalog with a "prefer built-in operators over Python
  UDFs" rule, sourced from WorkflowSystemMetadata at request time.

- LiteLLM: added the claude-opus-4.7 entry alongside claude-haiku-4.5
  and gpt-5-mini in bin/litellm-config.yaml.

- Agent panel rewritten around the (conversation list / chat) two-view
  model with subscription-managed list reloads and per-step persistence.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a community gallery under /dashboard/hub/workflow-hub where users
browse, star, fork, and publish data science workflows. Backed by 15
seed entries and localStorage for stars/forks/views/publishes so the
page is never empty.

- List page: search, sort (trending/stars/forks/recent), category
  chips, featured grid, DAG-chain preview cards, agent badges.
- Detail page: SVG DAG preview, stats panel, fork-to-my-workflows
  (uses WorkflowPersistService.duplicateWorkflow when a backend wid is
  attached, otherwise falls back to a local stub), star toggle, and an
  optional 'Agent Included' card.
- Publish dialog: pulls the user's workflows via the persist service,
  derives operator chain from workflow content, writes a hub entry to
  localStorage.
- Sidebar: 'Workflow Hub' link added to the Hub submenu.
Seed entries don't have a workflowId, so the previous code only
incremented a localStorage counter and navigated to /dashboard/user/workflow
without actually writing to the backend — the forked workflow never showed
up in the Workflows page. Now the seed path calls
WorkflowPersistService.createWorkflow with empty content named
"[Fork] <title>", waits for the backend to return the new wid, and routes
straight into the new workflow's workspace. The duplicate-workflow path
for real-wid entries is unchanged.
…board

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…llback

- New WorkflowDataService: lists user workflows, parses operators from
  workflow content, fetches latest-execution runtime stats per operator
  via existing /executions REST endpoints.
- Widgets now record a WidgetSource ({manual} or {workflow, wid, opId?,
  metric, scope}). Workflow-sourced widgets refresh on dashboard load.
- Add Widget modal now has two paths:
    From a Workflow — pick workflow + widget type + metric (and operator
    for Metric Card); supports metric/bar/hbar/donut/table.
    Manual Entry — unchanged form for all 6 widget types.
- Seed dashboard is now empty with a clear CTA instead of hardcoded demo
  data.

Note: Texera does not expose operator tuple data via REST (results live
only in the WebSocket cache during a workspace session). We render the
data that IS persisted and REST-queryable: per-operator tuple counts,
sizes, processing times, worker counts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Switch to Texera's light theme: white card surfaces, #f5f6f8 page bg,
  light-mode chart colors, #1677ff accent matching the rest of the app.
  Removed every dark-theme rule.
- Add Widget modal redesigned for multi-select:
  - Checkbox gallery of all 6 widget types with inline config forms.
  - Quick metric presets (Accuracy / F1 / Precision / Recall) pre-fill
    the Metric Card.
  - "Add N widgets" submits everything checked at once; modal stays
    open with a confirmation badge so users can compose another batch.
  - "Done" closes the modal.
- Dropped the operator runtime-stats path. Texera does not expose tuple
  data via REST, and runtime stats (tuple counts, sizes, latencies)
  aren't what researchers want on a results dashboard. Deleted the
  workflow-data service and the WidgetSource discriminator.
- Removed seed data entirely. List page now shows a real empty state
  with a Create Dashboard CTA. Service no longer auto-creates anything;
  legacy seed-* dashboards from earlier iterations get purged on load.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add Widget is now a tabbed modal:

- Tab 1 "From Workflow" — pick a workflow that has been executed,
  see what's available, pick a data point, pick a widget type, add.
  Two data sources:
    * Saved results from localStorage at texera.workflow.results.{wid}
      (WorkflowResultsSnapshot shape) — picks up evaluation metrics
      (accuracy, F1, …) and tabular outputs whenever the workspace
      writes them. Allows Metric Card / Bar / Donut / Table widgets.
    * Runtime stats from REST — output rows, input rows, output size,
      processing time per operator. Always available after a run.
      Renders as Metric Card.

- Tab 2 "Manual Input" — pick widget type, fill in data, add.
  Five widget types: Metric / Bar / Donut / Table / Text.

Widgets added from a workflow carry a WidgetSource and display a
"From <workflow>" pill in the top-right of the widget frame.

Modal stays open after each add with a confirmation badge so users
can compose several widgets in one session; Done closes it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Result Panel already receives operator output via WebSocket during
execution. We tap the same stream — no new endpoint, no duplicate fetch.

- New DashboardResultCacheService subscribes to
  WorkflowResultService.getResultUpdateStream() at app load. Each
  update is snapshotted to localStorage:
    key   : texera.results.{wid}.{opId}
    value : { columns: string[], rows: any[][], timestamp: string }
  Caps at 100 rows per operator; LRU-evicts on QuotaExceededError. The
  current wid is tracked from /dashboard/user/workflow/{wid} URLs.
  Paginated operators are fetched via OperatorPaginationResultService
  .selectPage(1, 100) (debounced per op) since pagination updates only
  carry metadata, not rows.

- workflow-data.service.ts now reads per-operator keys and merges them
  with any legacy texera.workflow.results.{wid} bundle. Auto-detects
  metric shapes:
    * Single-row table → every numeric column becomes a metric
      (catches evaluation outputs like {accuracy, f1, precision}).
    * 2-column ≤20-row name|value table → each row becomes a metric
      (catches aggregations like {model: "RF", score: 0.94}).
  Rows are always exposed for Table / Bar / Donut widgets.

- AppComponent injects the cache service so it instantiates at boot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… Results

Two bugs in the Add Widget modal:

1. The workflow dropdown was sized by nz-select's default min-width
   (~120px), truncating names to "[Example] Da...". Made it stretch to
   the full modal width and ellipsize at the actual edge.

2. Data cards were single-select via a separate "Widget type" step,
   which felt unresponsive — users had to click twice to enable Add.
   Switched to multi-select with toggle-on-click:
   - Each card has a checkmark in the top-right when picked.
   - "Add N Widget(s)" creates one widget per selected card in one go;
     metric/stat cards become Metric Cards, row cards become Tables.
   - Modal stays open afterward with confirmation; selection clears so
     users can compose another batch.

Also restructured the sections:
- Results — actual run data — gets a primary blue-tinted card with a
  prominent header at the top.
- Runtime stats moved into a collapsed section (a count pill + caret).
  Expand only when needed; most users don't care about input row
  counts and processing time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The empty-grid had `span[nz-icon] { font-size: 56px }` as a descendant
selector, which cascaded onto the button's "+" icon too — that's what
made it render at 56px on its own line above the "Add Widget" label.
Scoped that rule to a new `.empty-grid-icon` class on the dashboard
icon only, and gave `.empty-cta` explicit inline-flex so the icon and
text render side-by-side.

Also gated the CTA button (and its "click + Add Widget" prompt) behind
mode === 'edit'. In view mode the empty state shows just an icon and
"No widgets yet." — no button to click since adding widgets requires
edit mode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the Result Panel receives output from a visualization operator
(Scatterplot, ConfusionMatrix, etc.), the row contains an html-content
column with a self-contained Plotly HTML document. We were storing it
in localStorage but then showing the raw HTML source in a Table widget.

- New "html" widget type. Renders via <iframe [srcdoc]> with
  sandbox="allow-scripts" + bypassSecurityTrustHtml so inline Plotly
  scripts actually execute. Mirrors what VisualizationFrameContentComponent
  does in the workspace.
- The Add Widget modal now scans cached operator outputs for cells
  that look like an HTML document (starts with <!doctype/<html/<script/
  <svg, or contains script/plotly markers) and surfaces each as its own
  HTML data point alongside the regular metrics/rows. Default widget
  type for those points is "html" with a chart-area icon.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root cause: safeHtmlContent was a getter that ran
sanitizer.bypassSecurityTrustHtml(...) on every change-detection cycle,
returning a fresh SafeHtml each time. Angular saw a "new" srcdoc value
and rebound it; the browser reloaded the iframe; Plotly redrew. Hence
the flicker.

- Cache the SafeHtml in a private field via an @input setter. We only
  recompute when the underlying htmlContent string actually changes.
- ChangeDetectionStrategy.OnPush on the widget component so CD only
  fires when its input reference actually changes.
- trackBy by widget id on the editor's widget *ngFor so a fresh
  widgets array reference doesn't destroy + recreate every iframe.
- min-height 240px on .html-iframe so a still-loading chart doesn't
  shift layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous attempt (cached SafeHtml + OnPush + trackBy) wasn't enough —
the user still saw the Plotly chart appear/disappear/reappear.

Switched to an imperative approach:

- Drop [srcdoc] binding entirely. Get the iframe via @ViewChild and
  set element.srcdoc = htmlContent imperatively. Track the currently-
  applied content; only re-set when the html string actually changes.
  Angular's change detection now has no way to retrigger a srcdoc
  attribute write, no matter how many times the parent's array
  reference flips.

- Memoize widgetStyle() in the editor — keyed on widget id + layout
  signature — so [ngStyle] receives a referentially-stable object
  when nothing has moved. Prevents Angular from re-applying inline
  styles on every CD cycle, which in turn keeps the widget-cell from
  retriggering CSS transitions that could resize the iframe and force
  Plotly to redraw.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the 12-column grid in favor of pixel-perfect x/y/width/height
layouts. Several problems with the grid:

- Newly-added widgets, dragged in grid units with a re-assigned mouse
  origin on every snap, gave a "glued to the cursor" feel — the widget
  appeared to chase the mouse after Add.
- Snapping made the resize handle feel sluggish.
- CSS transitions on the cell's width/height meant a resize redrew
  Plotly continuously.

New layout:
- WidgetLayout is { x, y, width, height } in pixels.
- nextLayout() places new widgets at x=16, y=(max bottom + 16). They
  always land below the existing stack — never under the cursor.
- Drag captures (mouseStart, widgetStart) on mousedown and applies
  newX = widgetStart + (mouseNow - mouseStart). Standard pattern; no
  origin re-assignment, no jitter.
- Resize handle: same pattern on bottom-right; clamped to MIN 160x120.
- CSS transitions on the cell's geometry removed.
- Edit-mode-only drag/resize via mode check at the top of startMove
  / startResize.
- Persists on every layout update through localStorage.
- Legacy {x,y,w,h} dashboards migrate to pixels on load.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Resize handle is now an always-visible diagonal grip in the
  bottom-right corner of every widget when in Edit mode. Drawn with
  two CSS pseudo-element stripes (no icon font). Hover and active
  drag turn it blue.
- Blue selection border on the widget frame no longer appears on
  hover; it only shows on .widget-cell.dragging, which is toggled
  via isWidgetActive(w.id) when the user is actively moving or
  resizing the widget.
- iframe in the HTML widget drops min-height: 240px and gets
  min-height: 0 plus height: 100%, so it shrinks freely with the
  widget. The flex column on .widget already handles the title +
  iframe split, so the iframe fills whatever vertical space the
  user resizes to.
- Min widget size raised to 200x150 per spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…L rendering

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added engine ddl-change Changes to the TexeraDB DDL frontend Changes related to the frontend GUI dev common agent-service labels May 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-service common ddl-change Changes to the TexeraDB DDL dev engine frontend Changes related to the frontend GUI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant