Skip to content

feat(dashboard): add dashboard functions to the exectution plan/workflow feature#183

Merged
SantiagoDePolonia merged 9 commits intomainfrom
feature/execution-plans-ui
Mar 28, 2026
Merged

feat(dashboard): add dashboard functions to the exectution plan/workflow feature#183
SantiagoDePolonia merged 9 commits intomainfrom
feature/execution-plans-ui

Conversation

@SantiagoDePolonia
Copy link
Copy Markdown
Contributor

@SantiagoDePolonia SantiagoDePolonia commented Mar 28, 2026

Summary

  • move workflow async nodes into a dedicated branch under the response node
  • align the inline async L-turn and label so Usage <- Audit Log <-| Async renders consistently
  • add regression coverage for the execution-plan async layout structure and connector geometry

Test Plan

  • node --test internal/admin/dashboard/static/js/modules/execution-plans-layout.test.js internal/admin/dashboard/static/js/modules/execution-plans.test.js
  • go test ./...

Summary by CodeRabbit

  • New Features

    • Workflows page to create, edit, deactivate and filter execution plans with provider/model scoping, guardrail steps, and server-backed APIs
    • Execution pipeline visualization added to workflows and audit details (cache, guardrails, AI, async branches)
    • Configurable feature toggles (cache, audit, usage, guardrails) and guardrail selection list
  • UX / Style

    • Responsive dashboard styles and updated sidebar navigation separating Workflows and Audit Logs
  • Chores

    • Admin API endpoints for managing execution plans and guardrails registered
  • Tests

    • New client and server tests covering layout, module logic, handlers, endpoints, and static assets

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an admin "Workflows / Execution Plans" feature: UI pages, client modules and tests, server admin endpoints and handler wiring, execution-plans service APIs (create/deactivate/list-views), store deactivation methods, guardrails registry listing, and accompanying tests.

Changes

Cohort / File(s) Summary
Dashboard Routing & Tests
internal/admin/dashboard/dashboard_test.go, internal/admin/dashboard/static/js/dashboard.js, internal/admin/dashboard/static/js/modules/usage.js
Remapped routes (execution-plans→workflows, audit→audit-logs), constrained page set, conditional fetches for workflows/audit-logs, and added static-file test.
Dashboard Templates & Layout
internal/admin/dashboard/templates/index.html, internal/admin/dashboard/templates/layout.html
Added Workflows page, execution-plan editor/cards/pipeline runtime view, guardrail datalist, sidebar link changes, and included new client script.
Dashboard Styling
internal/admin/dashboard/static/css/dashboard.css
Large CSS additions for execution-plans UI, pipeline visualization, async branch styles, responsive rules, and editor/toggle layouts.
Client Module & Tests
internal/admin/dashboard/static/js/modules/execution-plans.js, internal/admin/dashboard/static/js/modules/execution-plans.test.js, internal/admin/dashboard/static/js/modules/execution-plans-layout.test.js
New browser module dashboardExecutionPlansModule (state, filtering, form lifecycle, validation, fetch/submit/deactivate, runtime helpers) and comprehensive unit/layout tests.
Dashboard Static Integration
internal/admin/dashboard/static/js/modules/execution-plans-layout.test.js, internal/admin/dashboard/static/js/modules/execution-plans.js
Layout-focused tests added and module factory wired into dashboard modules set.
Admin Handler & Tests
internal/admin/handler.go, internal/admin/handler_executionplans_test.go
Added handler options for execution plans/guardrails, implemented List/Create/Deactivate endpoints, scope/guardrail validation and error mapping; tests with in-memory store.
App Wiring
internal/app/app.go
Passed execution plan service and guardrails registry into admin handler via new options.
Execution Plans Service & Tests
internal/executionplans/service.go, internal/executionplans/service_test.go, internal/executionplans/view.go
Added Create, Deactivate, ListViews, serialized refresh (mutex), view conversion/sorting, validation helpers, and extensive concurrency/refresh tests.
Types & Tests
internal/executionplans/types.go, internal/executionplans/types_test.go
normalizeCreateInput now allows empty plan names; added test verifying behavior.
Store Interface & Backends
internal/executionplans/store.go, internal/executionplans/store_mongodb.go, internal/executionplans/store_postgresql.go, internal/executionplans/store_sqlite.go
Added Store.Deactivate to interface and implemented Deactivate for MongoDB, PostgreSQL, and SQLite.
Execution Plans Tests (store/service)
internal/executionplans/service_test.go
Enhanced test doubles and added tests for activation, deactivation, snapshot refresh, and concurrency scenarios.
Guardrails Registry
internal/guardrails/registry.go
Added Names() to return sorted guardrail IDs; defensive for nil/empty.
HTTP Server Routes & Tests
internal/server/http.go, internal/server/http_test.go
Registered admin API routes for execution-plans and added test to verify route registration.

Sequence Diagram(s)

sequenceDiagram
  actor Browser
  participant UI as DashboardModule
  participant Admin as AdminAPI
  participant EPService as ExecutionPlansService
  participant Store as Store/DB
  participant Guardrails as GuardrailsRegistry

  Browser->>UI: navigate('workflows') / fetchAll
  UI->>Admin: GET /admin/api/v1/execution-plans
  Admin->>EPService: ListViews(ctx)
  EPService->>Store: ListActive(ctx)
  Store-->>EPService: active versions
  EPService->>Guardrails: (optional) validate refs / compute hashes
  EPService-->>Admin: []View (200)
  Admin-->>UI: JSON views
  UI-->>Browser: render list + pipeline UI

  Browser->>UI: submitExecutionPlanForm(payload)
  UI->>Admin: POST /admin/api/v1/execution-plans
  Admin->>EPService: Create(ctx, input)
  EPService->>Guardrails: validate guardrail refs (if enabled)
  EPService->>Store: Create(ctx, createInput)
  Store-->>EPService: stored Version
  EPService->>EPService: refreshLocked()
  EPService-->>Admin: 201 Created
  Admin-->>UI: success -> refresh lists

  Browser->>UI: deactivateExecutionPlan(id)
  UI->>Admin: POST /admin/api/v1/execution-plans/:id/deactivate
  Admin->>EPService: Deactivate(ctx, id)
  EPService->>Store: Deactivate(ctx, id)
  Store-->>EPService: success / ErrNotFound
  EPService->>EPService: refreshLocked()
  EPService-->>Admin: 204 No Content
  Admin-->>UI: success -> refresh lists
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

release:internal

Poem

🐰 I hopped through nodes and pipelines bright,
Cards and guardrails dancing left and right,
Forms pop open, tests all green and neat,
Create, deactivate — a rhythmic beat,
A rabbit cheers: hop, deploy, delight!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title mentions 'dashboard functions' and 'execution plan/workflow feature', but the actual scope of changes is significantly broader, including backend service APIs, database store implementations, routing, handler logic, and comprehensive test coverage. Revise the title to be more accurate and specific. Consider: 'feat: implement execution plans/workflows management with admin dashboard UI, APIs, and storage' or similar to reflect the full scope of backend and frontend changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 11.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/execution-plans-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/admin/dashboard/static/css/dashboard.css`:
- Line 2487: The CSS declaration uses the incorrect casing for the keyword
value: change the stroke property value in the rule that currently reads
"stroke: currentColor;" to use the lowercase keyword "currentcolor" to satisfy
the stylelint `value-keyword-case` rule; locate the CSS rule containing the
stroke property (the line with stroke: currentColor) and update the value casing
accordingly.

In `@internal/admin/dashboard/templates/index.html`:
- Line 638: The filter input bound to x-model="executionPlanFilter" (class
"filter-input") and the guardrail inputs (the two inputs added on lines
referenced) rely solely on placeholders and therefore lose accessible names once
populated; fix this by adding stable accessible names—either add visible <label
for="..."> elements tied to unique id attributes on each input or add clear
aria-label attributes (e.g., aria-label="Filter workflows by scope, name, hash,
or guardrail" for the executionPlanFilter input and aria-labels like "Guardrail
name" and "Guardrail value" for the guardrail inputs) so screen readers always
have a persistent name; ensure ids used in labels are unique per repeated
guardrail row (generate row-specific ids) or use aria-labelledby pointing to a
stable label element for each row.

In `@internal/admin/handler_executionplans_test.go`:
- Around line 384-390: Replace the loose map unmarshalling in the test
(currently decoding into body map[string]map[string]any in
handler_executionplans_test.go) with a typed struct that models the
OpenAI-compatible error envelope (fields: type, message, param *string or
interface{} nullable, code *string or interface{} nullable) and assert against
those typed fields (e.g., check errEnvelope.Error.Message equals "unknown
guardrail ref: missing-guardrail" and that param/code are nil or expected).
Update the same pattern in the other failing assertions referenced (around the
blocks at the other occurrences you noted) so tests validate the full error
shape rather than a generic map.

In `@internal/admin/handler.go`:
- Around line 681-699: The request handler currently skips validating
scope_provider/scope_model against the server-side registry before creating
plans; update the handler (the block that binds createExecutionPlanRequest and
calls h.plans.Create) to validate req.ScopeProvider and req.ScopeModel against
h.registry (use whatever registry lookup method exists on h.registry) and return
a clear invalid-request error via handleError if the provider or model is
unknown, before calling h.validateExecutionPlanGuardrails and h.plans.Create;
ensure the check references the same request fields (req.ScopeProvider,
req.ScopeModel) and short-circuits with an error response when validation fails
to prevent creating plans with dead/unknown scopes.

In `@internal/executionplans/service.go`:
- Around line 176-182: Concurrent writes can cause an older Refresh() to
overwrite a newer persisted snapshot because Create()/Deactivate() call
s.store.Create(...) (and s.store.Deactivate(...)) then call s.Refresh() with no
ordering guard; serialize the mutation+snapshot rebuild by adding a mutex or
generation/CAS around the write+refresh sequence so snapshots only advance:
acquire the lock (or read current generation), perform s.store.Create(...) (or
s.store.Deactivate(...)), then call s.Refresh() while still holding the lock (or
verifying/incrementing the generation/CAS) and release the lock only after
successful refresh; apply the same protection to the other write path mentioned
(the block around lines 218-225) and ensure StartBackgroundRefresh() respects
the same lock/generation so background refreshes cannot interleave and roll back
newer snapshots.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 35d4338d-eab3-49eb-afb0-2f6462cf1e3d

📥 Commits

Reviewing files that changed from the base of the PR and between c4bb2eb and 6bfdfbe.

📒 Files selected for processing (24)
  • internal/admin/dashboard/dashboard_test.go
  • internal/admin/dashboard/static/css/dashboard.css
  • internal/admin/dashboard/static/js/dashboard.js
  • internal/admin/dashboard/static/js/modules/execution-plans-layout.test.js
  • internal/admin/dashboard/static/js/modules/execution-plans.js
  • internal/admin/dashboard/static/js/modules/execution-plans.test.js
  • internal/admin/dashboard/static/js/modules/usage.js
  • internal/admin/dashboard/templates/index.html
  • internal/admin/dashboard/templates/layout.html
  • internal/admin/handler.go
  • internal/admin/handler_executionplans_test.go
  • internal/app/app.go
  • internal/executionplans/service.go
  • internal/executionplans/service_test.go
  • internal/executionplans/store.go
  • internal/executionplans/store_mongodb.go
  • internal/executionplans/store_postgresql.go
  • internal/executionplans/store_sqlite.go
  • internal/executionplans/types.go
  • internal/executionplans/types_test.go
  • internal/executionplans/view.go
  • internal/guardrails/registry.go
  • internal/server/http.go
  • internal/server/http_test.go
💤 Files with no reviewable changes (1)
  • internal/executionplans/types.go

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (3)
internal/admin/handler_executionplans_test.go (1)

212-245: ⚠️ Potential issue | 🟡 Minor

Assert the 503 error envelope here, not just the status code.

These three branches are new public API error paths, but the test only checks 503. Since this file already has executionPlanErrorEnvelope, decode the body here too and assert type/message/param/code so a broken error shape or missing feature_unavailable code does not slip through.

As per coding guidelines, public error responses must use the OpenAI-compatible shape with fields: type, message, param (null if unavailable), and code (null if unavailable).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/admin/handler_executionplans_test.go` around lines 212 - 245, The
tests currently only assert HTTP 503 but must also decode and assert the
OpenAI-compatible error envelope; after calling ListExecutionPlans,
CreateExecutionPlan, and DeactivateExecutionPlan, unmarshal the response body
into the existing executionPlanErrorEnvelope type and assert its fields (Type,
Message, Param == nil, Code == "feature_unavailable" or expected code) to ensure
the public error shape is correct; update the assertions for listRec, create
rec, and deactivate rec to validate
envelope.type/envelope.message/envelope.param/envelope.code alongside the status
checks.
internal/executionplans/service.go (1)

183-193: ⚠️ Potential issue | 🟠 Major

Don’t reuse the request context for the post-commit refresh.

Both write paths commit to storage first and then call refreshLocked(ctx) with the same caller-scoped context. If that context is canceled or times out after the write succeeds, the API returns an error but current stays on the old snapshot, so Match() can keep serving the stale plan until a later refresh and callers may retry an already-applied mutation.

Also applies to: 228-239

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/executionplans/service.go` around lines 183 - 193, The post-commit
refresh is being invoked with the incoming request context (see s.store.Create
and the call to s.refreshLocked(ctx)), which can be canceled after the write
succeeds and cause the API to return an error while leaving s.current stale;
change the refresh calls to use a non-request context (e.g.,
context.Background() or a background-derived timeout) instead of the
caller-provided ctx when calling refreshLocked (and similarly for the other
occurrence around the other write path), ensuring you still hold s.refreshMu
while invoking s.refreshLocked but do not propagate the request-scoped context
to the refresh.
internal/admin/dashboard/templates/index.html (1)

730-733: ⚠️ Potential issue | 🟡 Minor

Make each guardrail row distinguishable to assistive tech.

The stable aria-labels fix the placeholder-only issue, but every repeated row still exposes the same “Guardrail reference” / “Guardrail step” pair. Once multiple rows exist, screen readers cannot tell which step is being edited without row context.

Suggested fix
-                                <input type="text" class="filter-input execution-plan-input" list="execution-plan-guardrail-options" placeholder="Guardrail ref" x-model="step.ref" aria-label="Guardrail reference">
-                                <input type="number" class="filter-input execution-plan-step-input" min="0" step="10" placeholder="Step" x-model.number="step.step" aria-label="Guardrail step">
+                                <input type="text" class="filter-input execution-plan-input" list="execution-plan-guardrail-options" placeholder="Guardrail ref" x-model="step.ref" :aria-label="'Guardrail reference for row ' + (index + 1)">
+                                <input type="number" class="filter-input execution-plan-step-input" min="0" step="10" placeholder="Step" x-model.number="step.step" :aria-label="'Guardrail step for row ' + (index + 1)">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/admin/dashboard/templates/index.html` around lines 730 - 733, The
repeated guardrail inputs inside the template x-for
(executionPlanForm.guardrails) need unique accessible names per row; update the
input attributes bound to x-model="step.ref" and x-model.number="step.step" to
include row-specific labels (for example set :aria-label="'Guardrail reference '
+ (index + 1)" and :aria-label="'Guardrail step ' + (index + 1)" or generate a
unique id per row and reference it via aria-labelledby on each input) so screen
readers can distinguish each guardrail's reference and step by index or by a
corresponding hidden label element.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/admin/dashboard/static/js/modules/execution-plans.js`:
- Around line 201-217: The form hydration manually rebuilds features and
guardrails instead of using the shared normalizers; replace the inline parsing
of plan.plan_payload.features and the map over this.planGuardrails(plan) when
setting this.executionPlanForm.features and this.executionPlanForm.guardrails by
calling the existing helpers executionPlanSourceFeatures(plan) and
executionPlanSourceGuardrails(plan) (or the appropriate exported names) so the
form uses the same normalization path as the editor and renderer; keep the other
fields (scope_provider, scope_model, name, description) as-is and populate
features/guardrails from those helper return values.
- Around line 385-396: Add a reentrancy guard to submitExecutionPlanForm: at the
top of the method check the boolean executionPlanSubmitting and immediately
return if true to short-circuit duplicate clicks, then set
executionPlanSubmitting = true only when proceeding; ensure the create
POST/async flow in submitExecutionPlanForm is wrapped with try/catch/finally (or
equivalent) so executionPlanSubmitting is cleared in the finally block on both
success and error to avoid getting stuck in a submitting state.
- Around line 255-265: The guardrail mapping in buildExecutionPlanRequest
currently coerces blank/whitespace step inputs to 0 via Number(), letting them
pass validateExecutionPlanRequest; change the mapping so you first read the raw
step value (e.g., const raw = step && step.step), treat empty string / null /
whitespace as invalid (set step to NaN or undefined) and only convert to a
Number when raw is non-empty and numeric (use a strict parse check like
String(raw).trim() !== '' && Number.isFinite(Number(raw))). This ensures
validateExecutionPlanRequest (which checks Number.isInteger(step.step)) will
reject blank steps; update buildExecutionPlanRequest (and optionally
validateExecutionPlanRequest) to rely on that and fail submissions with blank
guardrail steps.

In `@internal/executionplans/service.go`:
- Around line 396-399: The validation currently only checks the error from
s.compiler.Compile(version) but must also reject empty compiled plans; update
validateCreateCandidate (the block calling s.compiler.Compile) to capture the
compiled result (e.g., compiled, err := s.compiler.Compile(version)), then if
err != nil return newValidationError(err.Error(), err), and additionally if
compiled == nil || compiled.Policy == nil return newValidationError("compiled
plan is empty or missing policy", nil) (or similar descriptive message) so empty
compiled results are rejected during preflight validation.

---

Duplicate comments:
In `@internal/admin/dashboard/templates/index.html`:
- Around line 730-733: The repeated guardrail inputs inside the template x-for
(executionPlanForm.guardrails) need unique accessible names per row; update the
input attributes bound to x-model="step.ref" and x-model.number="step.step" to
include row-specific labels (for example set :aria-label="'Guardrail reference '
+ (index + 1)" and :aria-label="'Guardrail step ' + (index + 1)" or generate a
unique id per row and reference it via aria-labelledby on each input) so screen
readers can distinguish each guardrail's reference and step by index or by a
corresponding hidden label element.

In `@internal/admin/handler_executionplans_test.go`:
- Around line 212-245: The tests currently only assert HTTP 503 but must also
decode and assert the OpenAI-compatible error envelope; after calling
ListExecutionPlans, CreateExecutionPlan, and DeactivateExecutionPlan, unmarshal
the response body into the existing executionPlanErrorEnvelope type and assert
its fields (Type, Message, Param == nil, Code == "feature_unavailable" or
expected code) to ensure the public error shape is correct; update the
assertions for listRec, create rec, and deactivate rec to validate
envelope.type/envelope.message/envelope.param/envelope.code alongside the status
checks.

In `@internal/executionplans/service.go`:
- Around line 183-193: The post-commit refresh is being invoked with the
incoming request context (see s.store.Create and the call to
s.refreshLocked(ctx)), which can be canceled after the write succeeds and cause
the API to return an error while leaving s.current stale; change the refresh
calls to use a non-request context (e.g., context.Background() or a
background-derived timeout) instead of the caller-provided ctx when calling
refreshLocked (and similarly for the other occurrence around the other write
path), ensuring you still hold s.refreshMu while invoking s.refreshLocked but do
not propagate the request-scoped context to the refresh.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cbe16ff4-c9d9-4ea7-9029-97da64617062

📥 Commits

Reviewing files that changed from the base of the PR and between 6bfdfbe and 8cbd30c.

📒 Files selected for processing (9)
  • internal/admin/dashboard/static/css/dashboard.css
  • internal/admin/dashboard/static/js/modules/execution-plans-layout.test.js
  • internal/admin/dashboard/static/js/modules/execution-plans.js
  • internal/admin/dashboard/static/js/modules/execution-plans.test.js
  • internal/admin/dashboard/templates/index.html
  • internal/admin/handler.go
  • internal/admin/handler_executionplans_test.go
  • internal/executionplans/service.go
  • internal/executionplans/service_test.go

Comment on lines +385 to +396
async submitExecutionPlanForm() {
this.executionPlanFormError = '';
this.executionPlanNotice = '';

const payload = this.buildExecutionPlanRequest();
const validationError = this.validateExecutionPlanRequest(payload);
if (validationError) {
this.executionPlanFormError = validationError;
return;
}

this.executionPlanSubmitting = true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Short-circuit reentrant submits before the create POST.

executionPlanSubmitting is set here but never checked. Two queued clicks can still dispatch duplicate create requests before the UI disables, and this write is not idempotent.

🛡️ Proposed fix
 async submitExecutionPlanForm() {
+    if (this.executionPlanSubmitting) {
+        return;
+    }
     this.executionPlanFormError = '';
     this.executionPlanNotice = '';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/admin/dashboard/static/js/modules/execution-plans.js` around lines
385 - 396, Add a reentrancy guard to submitExecutionPlanForm: at the top of the
method check the boolean executionPlanSubmitting and immediately return if true
to short-circuit duplicate clicks, then set executionPlanSubmitting = true only
when proceeding; ensure the create POST/async flow in submitExecutionPlanForm is
wrapped with try/catch/finally (or equivalent) so executionPlanSubmitting is
cleared in the finally block on both success and error to avoid getting stuck in
a submitting state.

@SantiagoDePolonia SantiagoDePolonia changed the title fix(dashboard): align workflow async branch feat(dashboard): align workflow async branch Mar 28, 2026
@SantiagoDePolonia SantiagoDePolonia changed the title feat(dashboard): align workflow async branch feat(dashboard): add dashboard functions to the exectution plan/workflow feature Mar 28, 2026
@SantiagoDePolonia SantiagoDePolonia self-assigned this Mar 28, 2026
@SantiagoDePolonia
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 28, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
internal/executionplans/service.go (1)

186-194: ⚠️ Potential issue | 🔴 Critical

Don’t report these writes as failed after the store commit succeeds.

Both paths mutate storage first and only then rebuild the in-memory snapshot. If refreshLocked() fails, the caller still sees an error even though the new version/deactivation is already persisted, so a retry can create an extra version or report a false failed deactivate while ListViews() can already observe the committed state. Please either update s.current incrementally from the committed change under refreshMu, or add a compensating rollback before returning an error.

Also applies to: 233-242

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/executionplans/service.go` around lines 186 - 194, The current code
writes to the persistent store via s.store.Create (and similarly in the
deactivate path) and then calls s.refreshLocked; if refreshLocked fails we must
not surface a failure because the store commit already succeeded. Fix by either
(preferred) acquiring refreshMu and update s.current incrementally with the
committed change (e.g., insert the new execution plan/version or apply the
deactivation) under the same lock before returning, so the in-memory snapshot
matches the persisted state, or (alternative) perform a compensating rollback to
the store (undo the Create/deactivate) before returning an error. Locate the
flows around s.store.Create / s.store.Deactivate and s.refreshLocked and ensure
updates to s.current occur while holding refreshMu (or roll back the store
write) so callers don’t see a false failure after persistence succeeded.
internal/admin/dashboard/static/js/modules/execution-plans.js (1)

525-539: ⚠️ Potential issue | 🔴 Critical

Short-circuit reentrant deactivation before the POST.

executionPlanDeactivatingID is written here but never read. A queued second click can still dispatch another /deactivate request before the UI disables, and the loser can surface as a false error after the first request already succeeded.

🛡️ Proposed fix
             async deactivateExecutionPlan(plan) {
                 const planID = String(plan && plan.id || '').trim();
-                if (!planID || !this.canDeactivateExecutionPlan(plan)) {
+                if (!planID || !this.canDeactivateExecutionPlan(plan) || this.executionPlanDeactivatingID) {
                     return;
                 }
                 const workflowName = this.workflowDisplayName(plan);
                 if (typeof global.confirm === 'function' && !global.confirm(
                     'Deactivate workflow "' + workflowName + '"? Requests will fall back to the next active workflow for this scope.'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/admin/dashboard/static/js/modules/execution-plans.js` around lines
525 - 539, The deactivateExecutionPlan function sets
this.executionPlanDeactivatingID but never uses it to prevent reentrant
requests; add a guard at the top of deactivateExecutionPlan(plan) that returns
early if this.executionPlanDeactivatingID is truthy (or equals the same planID)
to short-circuit duplicate clicks, then set this.executionPlanDeactivatingID =
planID before sending the POST and clear it (set to '') in both the success and
error paths (use a finally block or ensure both then/catch clear it) so only one
/deactivate request per plan runs at a time.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/admin/dashboard/static/js/modules/execution-plans.js`:
- Around line 320-325: In validateExecutionPlanRequest(), the guardrail
validation currently only checks Number.isInteger(step.step) which allows
negative values like -1; update the guardrails loop (the code that iterates the
guardrails array and checks step.ref and step.step) to also require step.step >=
0 by changing the validation to reject non-integers or negative integers and
return an error message such as "Each guardrail step must use a non-negative
integer step number." Ensure the check references the guardrails loop and the
step.step field so negative step numbers are rejected.

In `@internal/admin/dashboard/templates/index.html`:
- Around line 870-876: Add unique, plan-specific accessible names to the two
action buttons so screen readers and keyboard users can distinguish them: update
the Deactivate button (where executionPlanDeactivatingID,
canDeactivateExecutionPlan(plan), and deactivateExecutionPlan(plan) are used) to
include an aria-label that incorporates plan identifier or name (e.g.,
"Deactivate workflow {plan.name || plan.id}") and update the Edit button
(openExecutionPlanCreate(plan)) to include an aria-label like "Edit workflow
{plan.name || plan.id}"; keep the existing title/disabled logic and dynamic text
but ensure the aria-labels are set per-plan.

---

Duplicate comments:
In `@internal/admin/dashboard/static/js/modules/execution-plans.js`:
- Around line 525-539: The deactivateExecutionPlan function sets
this.executionPlanDeactivatingID but never uses it to prevent reentrant
requests; add a guard at the top of deactivateExecutionPlan(plan) that returns
early if this.executionPlanDeactivatingID is truthy (or equals the same planID)
to short-circuit duplicate clicks, then set this.executionPlanDeactivatingID =
planID before sending the POST and clear it (set to '') in both the success and
error paths (use a finally block or ensure both then/catch clear it) so only one
/deactivate request per plan runs at a time.

In `@internal/executionplans/service.go`:
- Around line 186-194: The current code writes to the persistent store via
s.store.Create (and similarly in the deactivate path) and then calls
s.refreshLocked; if refreshLocked fails we must not surface a failure because
the store commit already succeeded. Fix by either (preferred) acquiring
refreshMu and update s.current incrementally with the committed change (e.g.,
insert the new execution plan/version or apply the deactivation) under the same
lock before returning, so the in-memory snapshot matches the persisted state, or
(alternative) perform a compensating rollback to the store (undo the
Create/deactivate) before returning an error. Locate the flows around
s.store.Create / s.store.Deactivate and s.refreshLocked and ensure updates to
s.current occur while holding refreshMu (or roll back the store write) so
callers don’t see a false failure after persistence succeeded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 991a5b8e-d139-4a5e-8060-6cac8dfe70f4

📥 Commits

Reviewing files that changed from the base of the PR and between 8cbd30c and 014085d.

📒 Files selected for processing (11)
  • internal/admin/dashboard/static/js/modules/execution-plans-layout.test.js
  • internal/admin/dashboard/static/js/modules/execution-plans.js
  • internal/admin/dashboard/static/js/modules/execution-plans.test.js
  • internal/admin/dashboard/templates/index.html
  • internal/admin/handler_executionplans_test.go
  • internal/app/app.go
  • internal/executionplans/service.go
  • internal/executionplans/service_test.go
  • internal/executionplans/types.go
  • internal/executionplans/types_test.go
  • internal/server/http.go
💤 Files with no reviewable changes (1)
  • internal/executionplans/types.go

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/admin/dashboard/static/js/modules/execution-plans.js`:
- Around line 74-98: The option builders executionPlanProviderOptions and
executionPlanModelOptions (and the similar blocks around lines 189-219 and
294-308) currently only include providers/models present in this.models, which
drops a plan's stored scope if that provider/model is retired; modify these
functions to also include the plan's current scoped value when building the
options: accept (or read) the preserved provider and model IDs from the hydrated
plan (e.g., a parameter like currentProvider/currentModel or
this.hydratedScope), add those strings into the options Set before returning,
and update the form validator to allow the preserved provider/model values
(i.e., treat them as valid if they match the hydrated plan scope) so cloned
workflows remain valid even when the provider/model is no longer in this.models.

In `@internal/executionplans/service.go`:
- Around line 255-259: ListViews currently aborts the entire listing when
service.viewForVersion(version) returns an error; change ListViews to treat
compile/validation failures as row-level errors instead of returning nil for the
whole call: iterate versions, call s.viewForVersion(version), and on error
capture the persisted row metadata (the version/ID and any stored fields) and
attach a per-row compile error field or status rather than returning the error;
return the full slice of rows where successful rows contain their view and
failed rows contain their persisted metadata plus the compile error string so
the caller can display/inspect bad workflows (apply same change for the similar
loop around the 415-421 block).
- Around line 179-193: ListViews currently aborts the entire listing when
compiling an active execution plan fails; change ListViews to skip any versions
that fail compilation instead of returning early: when iterating versions call
the compilation step (the code path that uses compiledPlanForVersion/compile
logic) inside a try/if err != nil block, append the error to a warnings slice or
log it, and continue to the next version so the rest of the plans are returned;
alternatively return the successful results plus a non-fatal aggregated warning
(e.g., as part of the response or metadata) rather than failing the whole
request.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a2ee0243-5e08-4433-92d0-1d2056065e2a

📥 Commits

Reviewing files that changed from the base of the PR and between 014085d and dcdfef5.

📒 Files selected for processing (6)
  • internal/admin/dashboard/static/js/modules/execution-plans-layout.test.js
  • internal/admin/dashboard/static/js/modules/execution-plans.js
  • internal/admin/dashboard/static/js/modules/execution-plans.test.js
  • internal/admin/dashboard/templates/index.html
  • internal/executionplans/service.go
  • internal/executionplans/service_test.go

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/admin/dashboard/static/js/modules/execution-plans.js`:
- Around line 379-403: fetchExecutionPlans currently awaits fetch with no
timeout so a hung request can leave executionPlansLoading true; wrap the fetch
in an AbortController with a timeout (e.g. setTimeout -> controller.abort()) and
pass signal to fetch (use this.headers() as before), handle the abort exception
in the existing catch to set executionPlans = [] and this.executionPlanError to
a descriptive message, clear the timeout on success/failure, and ensure finally
still sets this.executionPlansLoading = false; reference fetchExecutionPlans,
this.headers(), and this.handleFetchResponse when making the changes.

In `@internal/executionplans/service_test.go`:
- Around line 27-35: The variable name `copy` in staticStore.Get and
concurrentStore.Get shadows Go's built-in copy function; rename that local
variable (e.g., to `vCopy`, `versionCopy`, or `ver`) in both methods so you
return &versionCopy instead of &copy, updating both occurrences in
staticStore.Get and concurrentStore.Get to avoid shadowing the built-in.

In `@internal/executionplans/service.go`:
- Around line 498-507: Remove the dead function scopeSpecificity: delete the
unused function declaration for scopeSpecificity (which returns int based on
scopeType) since viewScopeSpecificity is the actual implementation used by
ListViews; ensure no other references to scopeSpecificity remain and run
tests/static analysis to confirm removal is safe.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6516c403-10e9-4d5e-b4d0-30913d688e9f

📥 Commits

Reviewing files that changed from the base of the PR and between dcdfef5 and dc940c1.

📒 Files selected for processing (5)
  • internal/admin/dashboard/static/js/modules/execution-plans.js
  • internal/admin/dashboard/static/js/modules/execution-plans.test.js
  • internal/executionplans/service.go
  • internal/executionplans/service_test.go
  • internal/executionplans/view.go

@SantiagoDePolonia SantiagoDePolonia merged commit 180fc9d into main Mar 28, 2026
15 checks passed
@SantiagoDePolonia SantiagoDePolonia deleted the feature/execution-plans-ui branch April 1, 2026 21:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant