Files
ss-tools/specs/031-maintenance-banner/ux_reference.md
2026-05-22 11:22:19 +03:00

12 KiB

UX Reference: Maintenance Banner for Dashboards

Feature Branch: 031-maintenance-banner Created: 2026-05-21 Status: Draft

1. User Persona & Context

  • Who is the user?:

    • ETL Operator: System administrator or DevOps engineer running ETL pipelines (Airflow, cron, dbt). Does not interact with the ss-tools UI directly — API only.
    • ss-tools Administrator: Platform administrator managing maintenance mode settings through the web UI.
    • Dashboard Viewer: Business user viewing dashboards in Superset. Sees the maintenance banner but does not trigger it.
  • What is their goal?:

    • Operator: Automatically warn dashboard users that data is being refreshed and may be inaccurate.
    • Administrator: Control the maintenance process, manually remove banners if needed, configure exclusions.
    • Dashboard viewer: Understand why dashboard data appears incomplete.
  • Context:

    • Operator calls REST API from a pipeline (Airflow DAG, shell script, CI/CD).
    • Administrator works in the ss-tools web interface (SvelteKit SPA, desktop).
    • Dashboard viewer sees the result in Superset (third-party web application).

2. The "Happy Path" Narrative

Maintenance start (Operator → ss-tools → Superset)
An Airflow DAG finishes clearing staging tables and triggers: curl -X POST https://ss-tools/api/maintenance/start -d '{"tables":["raw.sales","raw.inventory"],"start_time":"2026-05-21T22:00:00+03:00","end_time":"2026-05-22T02:00:00+03:00"}'. The API immediately returns {"task_id":"task-abc","maintenance_id":"m-abc123","status":"pending"}. The operator polls GET /api/tasks/task-abc (or receives a WebSocket notification). Within 60 seconds, the task completes, and 12 dashboards in Superset display an orange banner at the very top: "⚠️ Maintenance in progress: 2026-05-21 22:00 — 2026-05-22 02:00 MSK. Data may be incomplete." Dashboard viewers see the warning immediately upon opening the page.

Maintenance end (Operator → ss-tools → Superset)
The ETL pipeline completes successfully. The Airflow DAG calls curl -X POST https://ss-tools/api/maintenance/m-abc123/end. The API returns {"task_id":"task-end-abc","status":"pending"}. Upon task completion, banners disappear from all dashboards. In the ss-tools UI, the administrator sees the event marked as "Completed".

Manual override (Administrator → ss-tools UI → Superset)
An administrator notices the ETL has stalled and banners remain past the scheduled end time. They navigate to ss-tools → Maintenance → see the list of active events → click "Remove All". Within 10 seconds, all banners are removed.

3. Interface Mockups

API Interaction

# ===== Start maintenance =====
$ curl -X POST https://ss-tools/api/maintenance/start \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "tables": ["raw.sales", "raw.inventory"],
    "start_time": "2026-05-21T22:00:00+03:00",
    "end_time": "2026-05-22T02:00:00+03:00",
    "message": "Scheduled data mart refresh"
  }'

# Success response — async (HTTP 202):
{
  "task_id": "task-maint-abc123",
  "maintenance_id": "m-ev-20260521-abc123",
  "status": "pending"
}

# Poll task status:
$ curl https://ss-tools/api/tasks/task-maint-abc123 \
  -H "Authorization: Bearer $JWT"

# Task completed response:
{
  "task_id": "task-maint-abc123",
  "status": "completed",
  "result": {
    "maintenance_id": "m-ev-20260521-abc123",
    "affected_dashboards": 12,
    "dashboards": [
      {"id": 42, "title": "Daily Sales Overview", "chart_id": 902},
      {"id": 57, "title": "Inventory Health Check", "chart_id": 903}
    ],
    "unmatched_tables": []
  }
}

# Partial success — async (HTTP 202):
{
  "task_id": "task-maint-def456",
  "maintenance_id": "m-ev-20260521-def456",
  "status": "pending"
}
# Task result on completion (via GET /api/tasks/{task_id}):
{
  "task_id": "task-maint-def456",
  "status": "completed",
  "result": {
    "maintenance_id": "m-ev-20260521-def456",
    "status": "partial",
    "affected_dashboards": 10,
    "failed_dashboards": [
      {"id": 99, "title": "Archived Report", "error": "Dashboard not found in Superset"}
    ],
    "unmatched_tables": ["raw.unknown_table"]
  }
}

# No dashboards found — sync fast-path (HTTP 200, no task needed):
{
  "maintenance_id": null,
  "status": "no_match",
  "affected_dashboards": 0,
  "message": "No dashboards found referencing tables: raw.archived",
  "unmatched_tables": ["raw.archived"]
}

# ===== End maintenance =====
$ curl -X POST https://ss-tools/api/maintenance/m-ev-20260521-abc123/end \
  -H "Authorization: Bearer $JWT"

# Success response — async (HTTP 202):
{
  "task_id": "task-end-abc123",
  "status": "pending"
}
# Task result on completion:
{
  "task_id": "task-end-abc123",
  "status": "completed",
  "result": {
    "maintenance_id": "m-ev-20260521-abc123",
    "removed_from": 12,
    "failed_removals": []
  }
}

# Already completed (HTTP 200 — sync, no task needed):
{
  "maintenance_id": "m-ev-20260521-abc123",
  "status": "already_completed",
  "message": "Maintenance was already completed at 2026-05-22T02:05:00+03:00"
}

# ===== Bulk removal =====
$ curl -X POST https://ss-tools/api/maintenance/end-all \
  -H "Authorization: Bearer $JWT"

# Success response — async (HTTP 202):
{
  "task_id": "task-endall-xyz",
  "status": "pending"
}
# Task result on completion:
{
  "task_id": "task-endall-xyz",
  "status": "completed",
  "result": {
    "closed_events": 3,
    "removed_from": 25,
    "failed_removals": []
  }
}

# ===== Settings =====
$ curl https://ss-tools/api/maintenance/settings \
  -H "Authorization: Bearer $JWT"

# Response:
{
  "target_environment_id": "prod",
  "dashboard_scope": "published_only",
  "excluded_dashboard_ids": [99, 101],
  "forced_dashboard_ids": [10, 42],
  "updated_at": "2026-05-20T15:00:00+03:00"
}

$ curl -X PUT https://ss-tools/api/maintenance/settings \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"dashboard_scope": "all", "excluded_dashboard_ids": [99]}'

# Response: (settings object, same shape as GET)

UI Layout & Flow

Screen 1: Maintenance Banners Management (/maintenance)

  • Layout: Single-page management panel. Top section — settings, bottom section — events table.
  • Key Elements:
    • Settings Panel (collapsible):
      • Dashboard Scope: Radio group: "Published only" / "Drafts only" / "All".
      • Excluded Dashboards: Multi-select search by dashboard title. Removable badge chips.
      • Forced Dashboards: Multi-select search by dashboard title. Removable badge chips.
      • "Save Settings" button (Primary, blue).
    • Active Events Table:
      • Columns: Event ID, Tables (truncated list + tooltip), Affected Dashboards, Start Time, End Time, Status (badge: 🟢 Active / Completed / Failed), Actions.
      • Actions: "Remove Banners" button (Warning, orange) per active event.
      • Above table: "Remove All Banners" button (Danger, red) with confirmation dialog.
    • Completed Events Table (collapsible, hidden by default):
      • Same columns + "Completed At" timestamp. No action buttons.
    • Contract Mapping:
      • @UX_STATE: idle (table loaded), loading (skeleton), action_in_progress (remove button shows spinner, others disabled), error (error toast, retry available), empty (no active events — placeholder "No active maintenance events").
      • @UX_FEEDBACK: Toast notifications: success (green "Banners removed from N dashboards"), error (red "Removal error: ..."), warning (yellow "Partial success: X/Y processed").
      • @UX_RECOVERY: On removal error — "Retry" button per failed dashboard. On network error — auto-retry after 3s with countdown display.
      • @UX_REACTIVITY: $state for activeEvents, completedEvents, settings; $derived for filtered/sorted views; $effect for auto-refresh (30s polling).

Screen 2: Dashboard Hub — Maintenance Indicator (modification to existing /dashboards)

  • Layout: New indicator added to each dashboard row (alongside existing Git/LLM status badges).
  • Key Elements:
    • Maintenance Badge: Orange badge "⚠️ Maintenance" in the dashboard row.
    • Tooltip: On hover — start and end times, source event ID.
    • Contract Mapping:
      • @UX_STATE: no_maintenance (no badge), maintenance_active (orange badge), loading (grey skeleton badge).
      • @UX_REACTIVITY: $derived from maintenance state data, refreshed alongside dashboard polling.

Screen 3: Superset Dashboard — Maintenance Markdown Chart (result in third-party application)

  • Layout: Markdown chart at the very top of the dashboard (position (0,0) in layout), full width.
  • Appearance:
    ⚠️ Maintenance in progress
    
    Scheduled data mart refresh
    Start: May 21, 2026, 10:00 PM (MSK)
    End:   May 22, 2026,  2:00 AM (MSK)
    
    Data may be incomplete or temporarily unavailable.
    
  • Style: Markdown with orange-tinted background (CSS via markdown HTML or Superset markdown CSS class). Bold header.

4. The "Error" Experience

Philosophy: API returns maximally detailed responses so the caller can make informed decisions. UI provides explicit recovery paths.

Scenario A: Table not found in any dashboard

  • User Action (Operator): Passes a table name with a typo: raw.sales_typo.
  • System Response: {"status": "no_match", "affected_dashboards": 0, "message": "No dashboards found referencing tables: raw.sales_typo"}.
  • Recovery: Operator corrects the table name and retries.

Scenario B: Some dashboards no longer exist in Superset

  • User Action (Operator): Initiates maintenance, but 3 of 15 dashboards have been deleted from Superset.
  • System Response: HTTP 207 Multi-Status with failed_dashboards field containing id and reason for each problematic dashboard. Remaining 12 dashboards successfully receive banners.
  • Recovery: Operator reviews failed_dashboards and adjusts the exclusion list if needed.

Scenario C: Superset API network timeout

  • User Action (Operator): Initiates maintenance, but Superset responds slowly.
  • System Response: Timeout after 30 seconds. Returns HTTP 207 with status: "partial", listing processed and unprocessed dashboards.
  • Recovery: Operator can re-call /maintenance/start with the same parameters — the system only processes dashboards that don't yet have an active banner.

Scenario D: Multiple maintenance events on one dashboard

  • User Action: Two ETL processes simultaneously update tables affecting dashboard D.
  • System Response: Dashboard D shows a single markdown chart with aggregated text: "⚠️ Multiple data refresh processes in progress. ..."
  • Recovery: The banner is only removed when ALL maintenance events for D are completed. Premature /end for one event updates the banner text but keeps it visible.

Scenario E: Insufficient permissions

  • User Action: API call without operator or admin role.
  • System Response: HTTP 403 {"detail": "Insufficient permissions. Required role: operator or admin"}.
  • Recovery: Request role assignment from administrator.

5. Tone & Voice

  • Style: Technical, concise, informative. No alarmism, but clear indication of possible data issues.
  • Terminology:
    • "Maintenance" (not "downtime" or "outage" — data is still accessible, just potentially stale)
    • "Dashboard" (not "report" or "view")
    • "Banner" (used in API/UI/internal docs for the markdown chart)
    • "Dataset" (not "datasource")
    • "Table" (not "source" or "relation")
  • Default banner template:
    ⚠️ Maintenance in progress
    {message}
    Start: {start_time_formatted}
    End:   {end_time_formatted}
    Data may be incomplete or temporarily unavailable.
    
    If message is not provided, the first body line is omitted.
  • Dashboard Hub tooltip: "Maintenance active: {start} — {end}"