From a37ee57cc11a6035c5cccee653cd11250fc876f4 Mon Sep 17 00:00:00 2001 From: Gabe Ruttner Date: Tue, 14 May 2024 14:47:30 -0700 Subject: [PATCH] feat: workflow run cancel (#489) * feat: workflow run cancel endpoint * feat: cancel all running button * feat: select and cancel runs * chore: error handling * fix: remove ani * fix: error toast * chore: gen * fix: feedback * fix: redundant method * Update frontend/app/src/pages/main/workflow-runs/$run/index.tsx Co-authored-by: abelanger5 * fix: response obj * fix: cancel by job run * fix: lint * chore: generate --------- Co-authored-by: abelanger5 Co-authored-by: Alexander Belanger --- .../openapi/components/schemas/_index.yaml | 2 + .../components/schemas/workflow_run.yaml | 13 + api-contracts/openapi/openapi.yaml | 2 + .../openapi/paths/workflow/workflow.yaml | 54 +++ api/v1/server/handlers/workflows/cancel.go | 76 ++++ api/v1/server/oas/gen/openapi.gen.go | 403 +++++++++++------- frontend/app/src/lib/api/generated/Api.ts | 25 ++ .../src/lib/api/generated/data-contracts.ts | 4 + .../pages/main/workflow-runs/$run/index.tsx | 78 +++- .../components/workflow-runs-columns.tsx | 25 ++ .../components/workflow-runs-table.tsx | 58 ++- .../services/controllers/jobs/controller.go | 8 +- .../services/controllers/workflows/queue.go | 4 +- internal/services/shared/tasktypes/job.go | 6 +- pkg/client/rest/gen.go | 174 ++++++++ 15 files changed, 772 insertions(+), 160 deletions(-) create mode 100644 api/v1/server/handlers/workflows/cancel.go diff --git a/api-contracts/openapi/components/schemas/_index.yaml b/api-contracts/openapi/components/schemas/_index.yaml index 9b85ee19c..54cee030e 100644 --- a/api-contracts/openapi/components/schemas/_index.yaml +++ b/api-contracts/openapi/components/schemas/_index.yaml @@ -124,6 +124,8 @@ WorkflowRunStatus: $ref: "./workflow_run.yaml#/WorkflowRunStatus" WorkflowRunStatusList: $ref: "./workflow_run.yaml#/WorkflowRunStatusList" +WorkflowRunsCancelRequest: + $ref: "./workflow_run.yaml#/WorkflowRunsCancelRequest" JobRunStatus: $ref: "./workflow_run.yaml#/JobRunStatus" StepRunStatus: diff --git a/api-contracts/openapi/components/schemas/workflow_run.yaml b/api-contracts/openapi/components/schemas/workflow_run.yaml index 04f6336f7..2035b5652 100644 --- a/api-contracts/openapi/components/schemas/workflow_run.yaml +++ b/api-contracts/openapi/components/schemas/workflow_run.yaml @@ -118,6 +118,19 @@ WorkflowRunStatusList: items: $ref: "#/WorkflowRunStatus" +WorkflowRunsCancelRequest: + type: object + properties: + workflowRunIds: + type: array + items: + type: string + format: uuid + minLength: 36 + maxLength: 36 + required: + - workflowRunIds + WorkflowRunTriggeredBy: type: object properties: diff --git a/api-contracts/openapi/openapi.yaml b/api-contracts/openapi/openapi.yaml index bd9b2ed72..4433c16d4 100644 --- a/api-contracts/openapi/openapi.yaml +++ b/api-contracts/openapi/openapi.yaml @@ -106,6 +106,8 @@ paths: $ref: "./paths/event/event.yaml#/keys" /api/v1/tenants/{tenant}/workflows: $ref: "./paths/workflow/workflow.yaml#/withTenant" + /api/v1/tenants/{tenant}/workflows/cancel: + $ref: "./paths/workflow/workflow.yaml#/cancelWorkflowRuns" /api/v1/workflows/{workflow}: $ref: "./paths/workflow/workflow.yaml#/withWorkflow" /api/v1/workflows/{workflow}/versions: diff --git a/api-contracts/openapi/paths/workflow/workflow.yaml b/api-contracts/openapi/paths/workflow/workflow.yaml index d133a407e..12e9ed325 100644 --- a/api-contracts/openapi/paths/workflow/workflow.yaml +++ b/api-contracts/openapi/paths/workflow/workflow.yaml @@ -273,6 +273,60 @@ triggerWorkflow: summary: Trigger workflow run tags: - Workflow Run + +cancelWorkflowRuns: + post: + x-resources: ["tenant"] + description: Cancel a batch of workflow runs + operationId: workflow-run:cancel + parameters: + - description: The tenant id + in: path + name: tenant + required: true + schema: + type: string + format: uuid + minLength: 36 + maxLength: 36 + requestBody: + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/WorkflowRunsCancelRequest" + description: The input to cancel the workflow runs + required: true + responses: + "200": + content: + application/json: + schema: + type: object + properties: + workflowRunIds: + type: array + items: + type: string + format: uuid + minLength: 36 + maxLength: 36 + description: Successfully cancelled the workflow runs + "400": + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/APIErrors" + description: A malformed or bad request + "403": + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/APIErrors" + description: Forbidden + summary: Cancel workflow runs + tags: + - Workflow Run + workflowRuns: get: x-resources: ["tenant"] diff --git a/api/v1/server/handlers/workflows/cancel.go b/api/v1/server/handlers/workflows/cancel.go new file mode 100644 index 000000000..203e483aa --- /dev/null +++ b/api/v1/server/handlers/workflows/cancel.go @@ -0,0 +1,76 @@ +package workflows + +import ( + "fmt" + "sync" + + "github.com/labstack/echo/v4" + + "github.com/google/uuid" + "github.com/hashicorp/go-multierror" + "github.com/hatchet-dev/hatchet/api/v1/server/oas/gen" + "github.com/hatchet-dev/hatchet/internal/msgqueue" + "github.com/hatchet-dev/hatchet/internal/repository/prisma/db" + "github.com/hatchet-dev/hatchet/internal/repository/prisma/sqlchelpers" + "github.com/hatchet-dev/hatchet/internal/services/shared/tasktypes" +) + +func (t *WorkflowService) WorkflowRunCancel(ctx echo.Context, request gen.WorkflowRunCancelRequestObject) (gen.WorkflowRunCancelResponseObject, error) { + tenant := ctx.Get("tenant").(*db.TenantModel) + runIds := request.Body.WorkflowRunIds + + var wg sync.WaitGroup + var mu sync.Mutex + var cancelledWorkflowRunIds []uuid.UUID + var returnErr error + + for _, runId := range runIds { + wg.Add(1) + go func(runId uuid.UUID) { + defer wg.Done() + + // Lookup step runs for the workflow run + runIdStr := runId.String() + jobRun, err := t.config.EngineRepository.JobRun().ListJobRunsForWorkflowRun(ctx.Request().Context(), tenant.ID, runIdStr) + if err != nil { + returnErr = multierror.Append(err, fmt.Errorf("failed to list job runs for workflow run %s", runIdStr)) + return + } + + for _, jobRun := range jobRun { + // If the step run is not in a final state, send a task to the taskqueue to cancel it + var reason = "CANCELLED_BY_USER" + // send a task to the taskqueue + jobRunId := sqlchelpers.UUIDToStr(jobRun.ID) + err = t.config.MessageQueue.AddMessage( + ctx.Request().Context(), + msgqueue.JOB_PROCESSING_QUEUE, + tasktypes.JobRunCancelledToTask(tenant.ID, jobRunId, &reason), + ) + if err != nil { + returnErr = multierror.Append(err, fmt.Errorf("failed to send cancel task for job run %s", jobRunId)) + continue + } + } + + // Add the canceled workflow run ID to the slice + mu.Lock() + cancelledWorkflowRunIds = append(cancelledWorkflowRunIds, runId) + mu.Unlock() + }(runId) + } + + wg.Wait() + + if returnErr != nil { + return nil, returnErr + } + + // Create a new instance of gen.WorkflowRunCancel200JSONResponse and assign canceledWorkflowRunUUIDs to its WorkflowRunIds field + response := gen.WorkflowRunCancel200JSONResponse{ + WorkflowRunIds: &cancelledWorkflowRunIds, + } + + // Return the response + return response, nil +} diff --git a/api/v1/server/oas/gen/openapi.gen.go b/api/v1/server/oas/gen/openapi.gen.go index d89f2f61e..a5c4df08f 100644 --- a/api/v1/server/oas/gen/openapi.gen.go +++ b/api/v1/server/oas/gen/openapi.gen.go @@ -945,6 +945,11 @@ type WorkflowRunTriggeredBy struct { ParentId string `json:"parentId"` } +// WorkflowRunsCancelRequest defines model for WorkflowRunsCancelRequest. +type WorkflowRunsCancelRequest struct { + WorkflowRunIds []openapi_types.UUID `json:"workflowRunIds"` +} + // WorkflowRunsMetrics defines model for WorkflowRunsMetrics. type WorkflowRunsMetrics struct { Counts *WorkflowRunsMetricsCounts `json:"counts,omitempty"` @@ -1192,6 +1197,9 @@ type SnsCreateJSONRequestBody = CreateSNSIntegrationRequest // StepRunUpdateRerunJSONRequestBody defines body for StepRunUpdateRerun for application/json ContentType. type StepRunUpdateRerunJSONRequestBody = RerunStepRunRequest +// WorkflowRunCancelJSONRequestBody defines body for WorkflowRunCancel for application/json ContentType. +type WorkflowRunCancelJSONRequestBody = WorkflowRunsCancelRequest + // TenantInviteAcceptJSONRequestBody defines body for TenantInviteAccept for application/json ContentType. type TenantInviteAcceptJSONRequestBody = AcceptInviteRequest @@ -1356,6 +1364,9 @@ type ServerInterface interface { // Get workflows // (GET /api/v1/tenants/{tenant}/workflows) WorkflowList(ctx echo.Context, tenant openapi_types.UUID) error + // Cancel workflow runs + // (POST /api/v1/tenants/{tenant}/workflows/cancel) + WorkflowRunCancel(ctx echo.Context, tenant openapi_types.UUID) error // Get workflow runs // (GET /api/v1/tenants/{tenant}/workflows/runs) WorkflowRunList(ctx echo.Context, tenant openapi_types.UUID, params WorkflowRunListParams) error @@ -2522,6 +2533,26 @@ func (w *ServerInterfaceWrapper) WorkflowList(ctx echo.Context) error { return err } +// WorkflowRunCancel converts echo context to params. +func (w *ServerInterfaceWrapper) WorkflowRunCancel(ctx echo.Context) error { + var err error + // ------------- Path parameter "tenant" ------------- + var tenant openapi_types.UUID + + err = runtime.BindStyledParameterWithLocation("simple", false, "tenant", runtime.ParamLocationPath, ctx.Param("tenant"), &tenant) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter tenant: %s", err)) + } + + ctx.Set(BearerAuthScopes, []string{}) + + ctx.Set(CookieAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.WorkflowRunCancel(ctx, tenant) + return err +} + // WorkflowRunList converts echo context to params. func (w *ServerInterfaceWrapper) WorkflowRunList(ctx echo.Context) error { var err error @@ -3103,6 +3134,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/api/v1/tenants/:tenant/workflow-runs/:workflow-run", wrapper.WorkflowRunGet) router.GET(baseURL+"/api/v1/tenants/:tenant/workflow-runs/:workflow-run/prs", wrapper.WorkflowRunListPullRequests) router.GET(baseURL+"/api/v1/tenants/:tenant/workflows", wrapper.WorkflowList) + router.POST(baseURL+"/api/v1/tenants/:tenant/workflows/cancel", wrapper.WorkflowRunCancel) router.GET(baseURL+"/api/v1/tenants/:tenant/workflows/runs", wrapper.WorkflowRunList) router.GET(baseURL+"/api/v1/tenants/:tenant/workflows/runs/metrics", wrapper.WorkflowRunGetMetrics) router.GET(baseURL+"/api/v1/users/current", wrapper.UserGetCurrent) @@ -4862,6 +4894,44 @@ func (response WorkflowList403JSONResponse) VisitWorkflowListResponse(w http.Res return json.NewEncoder(w).Encode(response) } +type WorkflowRunCancelRequestObject struct { + Tenant openapi_types.UUID `json:"tenant"` + Body *WorkflowRunCancelJSONRequestBody +} + +type WorkflowRunCancelResponseObject interface { + VisitWorkflowRunCancelResponse(w http.ResponseWriter) error +} + +type WorkflowRunCancel200JSONResponse struct { + WorkflowRunIds *[]openapi_types.UUID `json:"workflowRunIds,omitempty"` +} + +func (response WorkflowRunCancel200JSONResponse) VisitWorkflowRunCancelResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type WorkflowRunCancel400JSONResponse APIErrors + +func (response WorkflowRunCancel400JSONResponse) VisitWorkflowRunCancelResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type WorkflowRunCancel403JSONResponse APIErrors + +func (response WorkflowRunCancel403JSONResponse) VisitWorkflowRunCancelResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(403) + + return json.NewEncoder(w).Encode(response) +} + type WorkflowRunListRequestObject struct { Tenant openapi_types.UUID `json:"tenant"` Params WorkflowRunListParams @@ -5869,6 +5939,8 @@ type StrictServerInterface interface { WorkflowList(ctx echo.Context, request WorkflowListRequestObject) (WorkflowListResponseObject, error) + WorkflowRunCancel(ctx echo.Context, request WorkflowRunCancelRequestObject) (WorkflowRunCancelResponseObject, error) + WorkflowRunList(ctx echo.Context, request WorkflowRunListRequestObject) (WorkflowRunListResponseObject, error) WorkflowRunGetMetrics(ctx echo.Context, request WorkflowRunGetMetricsRequestObject) (WorkflowRunGetMetricsResponseObject, error) @@ -7175,6 +7247,37 @@ func (sh *strictHandler) WorkflowList(ctx echo.Context, tenant openapi_types.UUI return nil } +// WorkflowRunCancel operation middleware +func (sh *strictHandler) WorkflowRunCancel(ctx echo.Context, tenant openapi_types.UUID) error { + var request WorkflowRunCancelRequestObject + + request.Tenant = tenant + + var body WorkflowRunCancelJSONRequestBody + if err := ctx.Bind(&body); err != nil { + return err + } + request.Body = &body + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.WorkflowRunCancel(ctx, request.(WorkflowRunCancelRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "WorkflowRunCancel") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(WorkflowRunCancelResponseObject); ok { + return validResponse.VisitWorkflowRunCancelResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + // WorkflowRunList operation middleware func (sh *strictHandler) WorkflowRunList(ctx echo.Context, tenant openapi_types.UUID, params WorkflowRunListParams) error { var request WorkflowRunListRequestObject @@ -7844,155 +7947,157 @@ func (sh *strictHandler) WorkflowVersionGetDefinition(ctx echo.Context, workflow // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/frontend/app/src/lib/api/generated/Api.ts b/frontend/app/src/lib/api/generated/Api.ts index c7fe2933f..c0d14e029 100644 --- a/frontend/app/src/lib/api/generated/Api.ts +++ b/frontend/app/src/lib/api/generated/Api.ts @@ -76,6 +76,7 @@ import { WorkflowRunList, WorkflowRunStatus, WorkflowRunStatusList, + WorkflowRunsCancelRequest, WorkflowRunsMetrics, WorkflowVersion, WorkflowVersionDefinition, @@ -941,6 +942,30 @@ export class Api extends HttpClient + this.request< + { + workflowRunIds?: string[]; + }, + APIErrors + >({ + path: `/api/v1/tenants/${tenant}/workflows/cancel`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params, + }); /** * @description Get a workflow for a tenant * diff --git a/frontend/app/src/lib/api/generated/data-contracts.ts b/frontend/app/src/lib/api/generated/data-contracts.ts index 516463689..7a67e7a0c 100644 --- a/frontend/app/src/lib/api/generated/data-contracts.ts +++ b/frontend/app/src/lib/api/generated/data-contracts.ts @@ -589,6 +589,10 @@ export enum WorkflowRunStatus { export type WorkflowRunStatusList = WorkflowRunStatus[]; +export interface WorkflowRunsCancelRequest { + workflowRunIds: string[]; +} + export enum JobRunStatus { PENDING = "PENDING", RUNNING = "RUNNING", diff --git a/frontend/app/src/pages/main/workflow-runs/$run/index.tsx b/frontend/app/src/pages/main/workflow-runs/$run/index.tsx index c90406358..a45e00aff 100644 --- a/frontend/app/src/pages/main/workflow-runs/$run/index.tsx +++ b/frontend/app/src/pages/main/workflow-runs/$run/index.tsx @@ -1,7 +1,12 @@ import { Separator } from '@/components/ui/separator'; -import { StepRun, StepRunStatus, queries } from '@/lib/api'; +import api, { + StepRun, + StepRunStatus, + WorkflowRunStatus, + queries, +} from '@/lib/api'; import CronPrettifier from 'cronstrue'; -import { useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery } from '@tanstack/react-query'; import { Link, useOutletContext, useParams } from 'react-router-dom'; import invariant from 'tiny-invariant'; import { timeBetween } from '@/lib/utils'; @@ -13,6 +18,22 @@ import { StepRunPlayground } from './components/step-run-playground'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import RelativeDate from '@/components/molecules/relative-date'; import { RunStatus } from '../components/run-statuses'; +import { Button } from '@/components/ui/button'; + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { BiDotsVertical } from 'react-icons/bi'; +import { useApiError } from '@/lib/hooks'; + +export const WORKFLOW_RUN_TERMINAL_STATUSES = [ + WorkflowRunStatus.CANCELLED, + WorkflowRunStatus.FAILED, + WorkflowRunStatus.SUCCEEDED, +]; export default function ExpandedWorkflowRun() { const [selectedStepRun, setSelectedStepRun] = useState(); @@ -68,6 +89,30 @@ export default function ExpandedWorkflowRun() { } }, [runQuery.data, params.run, selectedStepRun]); + const { handleApiError } = useApiError({}); + + const cancelWorkflowRunMutation = useMutation({ + mutationKey: [ + 'workflow-run:cancel', + runQuery?.data?.tenantId, + runQuery?.data?.metadata.id, + ], + mutationFn: async () => { + const tenantId = runQuery?.data?.tenantId; + const workflowRunId = runQuery?.data?.metadata.id; + + invariant(tenantId, 'has tenantId'); + invariant(workflowRunId, 'has tenantId'); + + const res = await api.workflowRunCancel(tenantId, { + workflowRunIds: [workflowRunId], + }); + + return res.data; + }, + onError: handleApiError, + }); + if (runQuery.isLoading || !runQuery.data) { return ; } @@ -105,7 +150,34 @@ export default function ExpandedWorkflowRun() { /{selectedStepRun?.step?.readableId || '*'} - +
+ + + + + + + + { + cancelWorkflowRunMutation.mutate(); + }} + > + Cancel all running steps + + + +
diff --git a/frontend/app/src/pages/main/workflow-runs/components/workflow-runs-columns.tsx b/frontend/app/src/pages/main/workflow-runs/components/workflow-runs-columns.tsx index 00b9ae00b..8998e9756 100644 --- a/frontend/app/src/pages/main/workflow-runs/components/workflow-runs-columns.tsx +++ b/frontend/app/src/pages/main/workflow-runs/components/workflow-runs-columns.tsx @@ -5,8 +5,33 @@ import { Link } from 'react-router-dom'; import { RunStatus } from './run-statuses'; import { AdditionalMetadata } from '../../events/components/additional-metadata'; import RelativeDate from '@/components/molecules/relative-date'; +import { Checkbox } from '@/components/ui/checkbox'; export const columns: ColumnDef[] = [ + { + id: 'select', + header: ({ table }) => ( + table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + className="translate-y-[2px]" + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!value)} + aria-label="Select row" + className="translate-y-[2px]" + /> + ), + enableSorting: false, + enableHiding: false, + }, { accessorKey: 'id', header: ({ column }) => ( diff --git a/frontend/app/src/pages/main/workflow-runs/components/workflow-runs-table.tsx b/frontend/app/src/pages/main/workflow-runs/components/workflow-runs-table.tsx index 39d75f6f1..c11f2c3a4 100644 --- a/frontend/app/src/pages/main/workflow-runs/components/workflow-runs-table.tsx +++ b/frontend/app/src/pages/main/workflow-runs/components/workflow-runs-table.tsx @@ -4,12 +4,13 @@ import { useMemo, useState } from 'react'; import { ColumnFiltersState, PaginationState, + RowSelectionState, SortingState, VisibilityState, } from '@tanstack/react-table'; -import { useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery } from '@tanstack/react-query'; import invariant from 'tiny-invariant'; -import { WorkflowRunStatus, queries } from '@/lib/api'; +import api, { WorkflowRunStatus, queries } from '@/lib/api'; import { Loading } from '@/components/ui/loading.tsx'; import { TenantContextType } from '@/lib/outlet'; import { useOutletContext } from 'react-router-dom'; @@ -19,8 +20,10 @@ import { ToolbarType, } from '@/components/molecules/data-table/data-table-toolbar'; import { Button } from '@/components/ui/button'; -import { ArrowPathIcon } from '@heroicons/react/24/outline'; +import { ArrowPathIcon, XMarkIcon } from '@heroicons/react/24/outline'; import { WorkflowRunsMetricsView } from './workflow-runs-metrics'; +import queryClient from '@/query-client'; +import { useApiError } from '@/lib/hooks'; export interface WorkflowRunsTableProps { workflowId?: string; @@ -127,6 +130,39 @@ export function WorkflowRunsTable({ ...queries.workflows.list(tenant.metadata.id), }); + const [rowSelection, setRowSelection] = useState({}); + + const selectedRuns = useMemo(() => { + return Object.entries(rowSelection) + .filter(([_, selected]) => !!selected) + .map(([id]) => (listWorkflowRunsQuery.data?.rows || [])[Number(id)]); + }, [listWorkflowRunsQuery.data?.rows, rowSelection]); + + const { handleApiError } = useApiError({}); + + const cancelWorkflowRunMutation = useMutation({ + mutationKey: ['workflow-run:cancel', tenant.metadata.id, selectedRuns], + mutationFn: async () => { + const tenantId = tenant.metadata.id; + const workflowRunIds = selectedRuns.map((wr) => wr.metadata.id); + + invariant(tenantId, 'has tenantId'); + invariant(workflowRunIds, 'has runIds'); + + const res = await api.workflowRunCancel(tenantId, { + workflowRunIds, + }); + + return res.data; + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: queries.workflowRuns.list(tenant.metadata.id, {}).queryKey, + }); + }, + onError: handleApiError, + }); + const workflowKeyFilters = useMemo((): FilterOption[] => { return ( workflowKeys?.rows?.map((key) => ({ @@ -188,6 +224,20 @@ export function WorkflowRunsTable({ }; const actions = [ + ,