Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ src/routeTree.gen.ts

# Other files to ignore
package-lock.json
.claude/settings.local.json
.npmrc
19 changes: 19 additions & 0 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Decorator, Preview } from "@storybook/react-vite";

import { withThemeByClassName } from "@storybook/addon-themes";
import { ReactRenderer } from "@storybook/react-vite";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import {
createMemoryHistory,
createRootRoute,
Expand Down Expand Up @@ -82,6 +83,23 @@ export const withThemeProvider: Decorator = (StoryFn) => (
</ThemeProvider>
);

const storybookQueryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});

/**
* Decorator that provides a React Query client for stories that use hooks.
*/
export const withQueryClient: Decorator = (StoryFn) => (
<QueryClientProvider client={storybookQueryClient}>
<StoryFn />
</QueryClientProvider>
);

/**
* Decorator that sets user settings for stories
* Can be overridden per story using parameters.settings
Expand Down Expand Up @@ -113,6 +131,7 @@ declare module "@storybook/react-vite" {

const preview: Preview = {
decorators: [
withQueryClient,
withFeatures,
withSettings,
withRouter,
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [v0.16.0] - 2026-05-19

Version 0.16.0 includes support for the all new workflow engine in River Pro v0.24.0, including signals, timers, and greater introspection capabilities.

⚠️ Workflow features in v0.16.0 rely on new workflow-related tables that are part of the riverpro v0.24.0 release. River UI's workflow queries may be disabled or poorly performing until the River Pro migration v006 has been applied in your database.

### Added

- Workflow detail: add support for new River Pro workflow features, including signals and timers. [PR #567](https://github.com/riverqueue/riverui/pull/567).

### Fixed

- Workflow detail: add on-canvas zoom controls for click/touch navigation and improve controls styling for dark mode. [PR #524](https://github.com/riverqueue/riverui/pull/524).
Expand All @@ -17,6 +27,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Job delete actions: require confirmation before deleting a single job or selected jobs in bulk. [Fixes #545](https://github.com/riverqueue/riverui/issues/545). [PR #546](https://github.com/riverqueue/riverui/pull/546).
- Workflow detail: show the backend's not-found message instead of crashing when a workflow ID does not exist. [PR #564](https://github.com/riverqueue/riverui/pull/564).
- Job detail: render a dedicated `Snoozed` timeline step for scheduled jobs with prior attempts so snoozed jobs no longer show negative wait durations. [PR #565](https://github.com/riverqueue/riverui/pull/565).
- Workflow detail: source wait phases, timers, signal evidence, and task wait reasons from backend workflow wait metadata instead of frontend-derived placeholders. [PR #567](https://github.com/riverqueue/riverui/pull/567).
- Workflow detail: redesign wait inspection around a denser task-side summary, structured term/timer/signal cards, reliable full-node selection, Storybook workflow detail coverage below the diagram, and a tighter details layout that keeps job metadata with the main job card. [PR #567](https://github.com/riverqueue/riverui/pull/567).
- Workflow detail: add a task-signal debugger backed by task-scoped River Pro signal reads while keeping workflow detail wait data summary-only. [PR #567](https://github.com/riverqueue/riverui/pull/567).
- Workflow detail: clarify wait inspection with a compact condition matrix, phase-aware match summaries, explicit signal scopes, condition-type icons, per-term CEL definitions, and denser timer/dependency/signal evidence. [PR #567](https://github.com/riverqueue/riverui/pull/567).
- Workflow detail: replace the flat narrative event list with a task timeline that keeps dependencies in one place, emphasizes wait evidence and durations, avoids showing staged events before a task is actually runnable, and uses a lower-noise milestone hierarchy. [PR #567](https://github.com/riverqueue/riverui/pull/567).
- Workflow detail: remove the not-started wait preview from dependency progress milestones. [PR #567](https://github.com/riverqueue/riverui/pull/567).

## [v0.15.0] - 2026-02-26

Expand Down
2 changes: 1 addition & 1 deletion handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func TestNewHandlerIntegration(t *testing.T) {
return server
}

testRunner := func(exec riverdriver.Executor, makeAPICall handlertest.APICallFunc) {
testRunner := func(exec riverdriver.Executor, _ riverdriver.Driver[pgx.Tx], makeAPICall handlertest.APICallFunc) {
ctx := context.Background()

makeURL := fmt.Sprintf
Expand Down
4 changes: 2 additions & 2 deletions internal/handlertest/handlertest.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (

type APICallFunc = func(t *testing.T, testCaseName, method, path string, payload []byte)

func RunIntegrationTest[TClient any](t *testing.T, createClient func(ctx context.Context, tb testing.TB, logger *slog.Logger) (TClient, riverdriver.Driver[pgx.Tx], pgx.Tx), createBundle func(client TClient, tx pgx.Tx) uiendpoints.Bundle, createHandler func(t *testing.T, bundle uiendpoints.Bundle) http.Handler, testRunner func(exec riverdriver.Executor, makeAPICall APICallFunc)) {
func RunIntegrationTest[TClient any](t *testing.T, createClient func(ctx context.Context, tb testing.TB, logger *slog.Logger) (TClient, riverdriver.Driver[pgx.Tx], pgx.Tx), createBundle func(client TClient, tx pgx.Tx) uiendpoints.Bundle, createHandler func(t *testing.T, bundle uiendpoints.Bundle) http.Handler, testRunner func(exec riverdriver.Executor, dbDriver riverdriver.Driver[pgx.Tx], makeAPICall APICallFunc)) {
t.Helper()

var (
Expand Down Expand Up @@ -70,5 +70,5 @@ func RunIntegrationTest[TClient any](t *testing.T, createClient func(ctx context
})
}

testRunner(exec, makeAPICall)
testRunner(exec, driver, makeAPICall)
}
15 changes: 4 additions & 11 deletions riverproui/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,27 +122,18 @@ func (e *endpoints[TTx]) Extensions(ctx context.Context) (map[string]bool, error
return nil, err
}

indexResults, err := execTx.IndexesExist(ctx, &riverdriver.IndexesExistParams{
IndexNames: []string{
"river_job_workflow_list_active",
"river_job_workflow_scheduling",
},
Schema: schema,
})
hasWorkflowV2Tables, err := prohandler.HasWorkflowV2Tables(ctx, execTx, schema)
if err != nil {
return nil, err
}

hasWorkflows := indexResults["river_job_workflow_list_active"] || indexResults["river_job_workflow_scheduling"]

return map[string]bool{
"durable_periodic_jobs": hasPeriodicJobTable,
"producer_queries": true,
"workflow_queries": true,
"workflow_queries": hasWorkflowV2Tables,
"has_client_table": hasClientTable,
"has_producer_table": hasProducerTable,
"has_sequence_table": hasSequenceTable,
"has_workflows": hasWorkflows,
}, nil
}

Expand Down Expand Up @@ -181,6 +172,8 @@ func (e *endpoints[TTx]) MountEndpoints(archetype *baseservice.Archetype, logger
apiendpoint.Mount(mux, prohandler.NewWorkflowGetEndpoint(bundle), mountOpts),
apiendpoint.Mount(mux, prohandler.NewWorkflowListEndpoint(bundle), mountOpts),
apiendpoint.Mount(mux, prohandler.NewWorkflowRetryEndpoint(bundle), mountOpts),
apiendpoint.Mount(mux, prohandler.NewWorkflowTaskSignalsEndpoint(bundle), mountOpts),
apiendpoint.Mount(mux, prohandler.NewWorkflowTaskWaitDiagnosticsEndpoint(bundle), mountOpts),
)

return endpoints
Expand Down
49 changes: 44 additions & 5 deletions riverproui/endpoints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"riverqueue.com/riverpro/driver/riverpropgxv5"

"riverqueue.com/riverui/internal/uicommontest"
"riverqueue.com/riverui/riverproui/internal/prohandler"
)

func TestProEndpointsExtensions(t *testing.T) {
Expand Down Expand Up @@ -184,26 +185,64 @@ func TestProEndpointsExtensions(t *testing.T) {
})
})

t.Run("WorkflowsDetection", func(t *testing.T) {
t.Run("WorkflowQueryDetection", func(t *testing.T) {
t.Parallel()

t.Run("NoWorkflowIndexes", func(t *testing.T) {
t.Run("WorkflowV2TablesPresent", func(t *testing.T) {
t.Parallel()

bundle := setup(ctx, t)

_, err := bundle.tx.Exec(ctx, `DROP INDEX IF EXISTS river_job_workflow_list_active;`)
ext, err := bundle.endpoint.Extensions(ctx)
require.NoError(t, err)
require.True(t, ext["workflow_queries"])
})

t.Run("LegacyIndexesWithoutV2Tables", func(t *testing.T) {
t.Parallel()

bundle := setup(ctx, t)

for _, table := range prohandler.WorkflowV2TableNames {
_, err := bundle.tx.Exec(ctx, `DROP TABLE IF EXISTS `+table+` CASCADE;`)
require.NoError(t, err)
}
_, err := bundle.tx.Exec(ctx, `DROP INDEX IF EXISTS river_job_workflow_active_idx;`)
require.NoError(t, err)
_, err = bundle.tx.Exec(ctx, `CREATE INDEX river_job_workflow_active_idx ON river_job (workflow_id) WHERE workflow_id IS NOT NULL;`)
require.NoError(t, err)

ext, err := bundle.endpoint.Extensions(ctx)
require.NoError(t, err)
require.False(t, ext["workflow_queries"])
})

t.Run("NoWorkflowV2Tables", func(t *testing.T) {
t.Parallel()

bundle := setup(ctx, t)

var err error
for _, table := range prohandler.WorkflowV2TableNames {
_, err = bundle.tx.Exec(ctx, `DROP TABLE IF EXISTS `+table+` CASCADE;`)
require.NoError(t, err)
}
_, err = bundle.tx.Exec(ctx, `DROP INDEX IF EXISTS river_job_workflow_list_active;`)
require.NoError(t, err)
_, err = bundle.tx.Exec(ctx, `DROP INDEX IF EXISTS river_job_workflow_scheduling;`)
require.NoError(t, err)
_, err = bundle.tx.Exec(ctx, `DROP INDEX IF EXISTS river_job_workflow_active_idx;`)
require.NoError(t, err)
_, err = bundle.tx.Exec(ctx, `DROP INDEX IF EXISTS river_job_workflow_inactive_idx;`)
require.NoError(t, err)

ext, err := bundle.endpoint.Extensions(ctx)
require.NoError(t, err)
require.False(t, ext["has_workflows"])
require.False(t, ext["workflow_queries"])
})
})

t.Run("StaticAttributesAlwaysTrue", func(t *testing.T) {
t.Run("QueryAttributes", func(t *testing.T) {
t.Parallel()

bundle := setup(ctx, t)
Expand Down
41 changes: 24 additions & 17 deletions riverproui/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,51 @@ toolchain go1.25.7
require (
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.9.2
github.com/riverqueue/apiframe v0.0.0-20260203020001-b4fe7e4d61bc
github.com/riverqueue/river v0.35.1
github.com/riverqueue/river/riverdriver v0.35.1
github.com/riverqueue/river/rivershared v0.35.1
github.com/riverqueue/river/rivertype v0.35.1
github.com/riverqueue/apiframe v0.0.0-20260428012848-22cd8d31a740
github.com/riverqueue/river v0.37.1
github.com/riverqueue/river/riverdriver v0.37.1
github.com/riverqueue/river/rivershared v0.37.1
github.com/riverqueue/river/rivertype v0.37.1
github.com/stretchr/testify v1.11.1
riverqueue.com/riverpro v0.23.2
riverqueue.com/riverpro/driver v0.23.2
riverqueue.com/riverpro/driver/riverpropgxv5 v0.23.2
riverqueue.com/riverui v0.15.0
riverqueue.com/riverpro v0.24.0
riverqueue.com/riverpro/driver v0.24.0
riverqueue.com/riverpro/driver/riverpropgxv5 v0.24.0
riverqueue.com/riverui v0.16.0-rc.1
)

require (
cel.dev/expr v0.25.1 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.1 // indirect
github.com/go-playground/validator/v10 v10.30.2 // indirect
github.com/google/cel-go v0.27.0 // indirect
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.35.1 // indirect
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.37.1 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/samber/slog-http v1.12.1 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/gjson v1.19.0 // indirect
github.com/tidwall/match v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect
go.uber.org/goleak v1.3.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/crypto v0.51.0 // indirect
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.36.0 // indirect
golang.org/x/sys v0.44.0 // indirect
golang.org/x/text v0.37.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

Expand All @@ -53,10 +60,10 @@ retract (
v0.12.0 // Improper release process, not fully usable
)

// replace riverqueue.com/riverui => ../

// replace riverqueue.com/riverpro => ../../riverpro

// replace riverqueue.com/riverpro/driver => ../../riverpro/driver

// replace riverqueue.com/riverpro/driver/riverpropgxv5 => ../../riverpro/driver/riverpropgxv5

// replace riverqueue.com/riverui => ../
Loading
Loading