pg-workflows

Custom UI with hooks

Build your own run views on the @pg-workflows/ui hooks.

Wrap your tree in WorkflowRunsProvider once. Every hook reads from it and returns a plain TanStack Query result, so loading, error, and refetch states work as usual.

'use client'
import { createFetchClient, WorkflowRunsProvider } from '@pg-workflows/ui'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import type { ReactNode } from 'react'

const queryClient = new QueryClient()
const client = createFetchClient({ baseUrl: '/workflow-runs' })

export function Providers({ children }: { children: ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      <WorkflowRunsProvider client={client}>{children}</WorkflowRunsProvider>
    </QueryClientProvider>
  )
}

useWorkflowRuns(): list runs

import { useWorkflowRuns } from '@pg-workflows/ui'

export function FailedRuns() {
  const { data, isLoading } = useWorkflowRuns({ limit: 20, statuses: ['failed'] })

  if (isLoading) return <p>Loading…</p>

  return (
    <ul>
      {data?.items.map((run) => (
        <li key={run.id}>
          {run.workflowId}: {run.status}
        </li>
      ))}
    </ul>
  )
}

useWorkflowRun(): one run

Polls until the run reaches a terminal status.

import { useWorkflowRun } from '@pg-workflows/ui'

export function RunStatus({ runId }: { runId: string }) {
  const { data: run } = useWorkflowRun(runId)

  return (
    <p>
      {run?.workflowId} is {run?.status}
    </p>
  )
}

useWorkflowRunStats(): counts by status

import { useWorkflowRunStats } from '@pg-workflows/ui'

export function RunCounts() {
  const { data: stats } = useWorkflowRunStats()

  return (
    <p>
      {stats?.failed ?? 0} failed, {stats?.running ?? 0} running
    </p>
  )
}

useRunActions(): control a run

import { useRunActions } from '@pg-workflows/ui'

export function RunControls({ runId }: { runId: string }) {
  const { cancel, resume, trigger } = useRunActions()

  return (
    <>
      <button
        type="button"
        onClick={() => resume.mutate({ id: runId })}
        disabled={resume.isPending}
      >
        Resume
      </button>
      <button type="button" onClick={() => cancel.mutate({ id: runId })}>
        Cancel
      </button>
      <button
        type="button"
        onClick={() =>
          trigger.mutate({ id: runId, eventName: 'payment-confirmed', data: { ok: true } })
        }
      >
        Confirm payment
      </button>
    </>
  )
}

After each successful action, the run and every runs list refetch.

useRunFilters(): filter state

Holds the filter, sort, and cursor state. Pass serverParams to useWorkflowRuns.

import { useRunFilters, useWorkflowRuns } from '@pg-workflows/ui'

export function RunsByWorkflow() {
  const { filters, setFilters, serverParams } = useRunFilters()
  const runs = useWorkflowRuns(serverParams)

  return (
    <>
      <select
        value={filters.workflowId ?? ''}
        onChange={(e) =>
          setFilters({
            workflowId: e.target.value || undefined,
            startingAfter: undefined,
            endingBefore: undefined,
          })
        }
      >
        <option value="">All workflows</option>
        <option value="nightly-report">nightly-report</option>
      </select>
      <p>{runs.data?.items.length ?? 0} runs on this page</p>
    </>
  )
}

On this page