diff --git a/src/lib/components/schedule/schedule-view/schedule-view.svelte b/src/lib/components/schedule/schedule-view/schedule-view.svelte index 31dbb4469f..7e7cc3e2b5 100644 --- a/src/lib/components/schedule/schedule-view/schedule-view.svelte +++ b/src/lib/components/schedule/schedule-view/schedule-view.svelte @@ -8,12 +8,12 @@ } from '$lib/components/detail-list'; import DetailListLinkValue from '$lib/components/detail-list/detail-list-link-value.svelte'; import StatusCounts from '$lib/components/status-counts.svelte'; - import WorkflowStatusBadge from '$lib/components/workflow/workflow-status-badge.svelte'; import Alert from '$lib/holocene/alert.svelte'; import Link from '$lib/holocene/link.svelte'; import MenuItem from '$lib/holocene/menu/menu-item.svelte'; import SplitButton from '$lib/holocene/split-button.svelte'; import { translate } from '$lib/i18n/translate'; + import { BadgeStatus } from '$lib/io/badge-status'; import { IconChevronLeft, IconFilter } from '$lib/io/icon'; import { coreUserStore } from '$lib/stores/core-user'; import { @@ -65,8 +65,9 @@ class="flex flex-wrap items-center gap-2 text-3xl" data-testid="schedule-name" > - {scheduleId} @@ -149,7 +150,7 @@
- {translate('schedules.total-workflows-all-time')} + {translate('schedules.total-workflows')}
{:else} - {#await runsPromise} + {#await enrichedRunsPromise} {@render runList(recordedRuns)} {:then runs} {@render runList(runs)} @@ -63,28 +64,38 @@ {/await} {/if} -{#snippet runList(runs: RecentScheduleRun[])} +{#snippet runList(runs: (RecentScheduleRun | RecentScheduleRunEnriched)[])}
    {#each runs as run, i (run.runId || i)} + {@const isEnriched = + 'delayed' in run && 'taskFailure' in run && 'inVisibility' in run}
  • - +
    - + {#if isEnriched && run.inVisibility} + + {run.workflowId} + + {:else} {run.workflowId} - + {/if}

    - + {:else if label === translate('schedules.id')} ([ delayed && { @@ -27,5 +25,5 @@ - + diff --git a/src/lib/components/standalone-nexus-operations/nexus-operation-status-badge.svelte b/src/lib/components/standalone-nexus-operations/nexus-operation-status-badge.svelte index 3910620921..dd0ea76aaf 100644 --- a/src/lib/components/standalone-nexus-operations/nexus-operation-status-badge.svelte +++ b/src/lib/components/standalone-nexus-operations/nexus-operation-status-badge.svelte @@ -2,7 +2,6 @@ import { BadgeStatus } from '$lib/io/badge-status'; import type { NexusOperationExecutionStatus } from '$lib/types/nexus-operation-execution'; import { toNexusOperationStatus } from '$lib/utilities/get-nexus-operation-status-and-count'; - import { getWorkflowStatusLabel } from '$lib/utilities/get-workflow-status-label'; interface Props { status: NexusOperationExecutionStatus; @@ -11,7 +10,6 @@ let { status }: Props = $props(); const badgeStatus = $derived(toNexusOperationStatus(status)); - const text = $derived(getWorkflowStatusLabel(badgeStatus)); - + diff --git a/src/lib/components/status-counts.svelte b/src/lib/components/status-counts.svelte index de80db7d38..0513df0ddb 100644 --- a/src/lib/components/status-counts.svelte +++ b/src/lib/components/status-counts.svelte @@ -12,7 +12,6 @@ refresh as workflowRefresh, } from '$lib/stores/workflows'; import { getStatusAndCountOfGroup } from '$lib/utilities/get-group-status-and-count'; - import { getWorkflowStatusLabel } from '$lib/utilities/get-workflow-status-label'; import { createStatusCountsState, @@ -64,7 +63,6 @@ {#if status} ([ @@ -53,12 +52,15 @@ {#if status && isWorkflowStatusType(status)} {:else} - + {/if} diff --git a/src/lib/i18n/locales/en/schedules.ts b/src/lib/i18n/locales/en/schedules.ts index 678fdb7f94..5d2a977cbb 100644 --- a/src/lib/i18n/locales/en/schedules.ts +++ b/src/lib/i18n/locales/en/schedules.ts @@ -276,5 +276,5 @@ export const Strings = { 'add-another-spec': '+ Add another schedule spec', 'run-time-based-on-timezone': 'Based on the specified timezone ({{- timezoneName}})', - 'total-workflows-all-time': 'Total Workflows (All Time)', + 'total-workflows': 'Total Workflows', } as const; diff --git a/src/lib/io/badge-count/badge-count.svelte b/src/lib/io/badge-count/badge-count.svelte index 833aa75e39..c7182d77d7 100644 --- a/src/lib/io/badge-count/badge-count.svelte +++ b/src/lib/io/badge-count/badge-count.svelte @@ -2,7 +2,7 @@ export type BadgeCountSize = 'sm' | 'md'; const sharedClasses = - 'inline-flex whitespace-nowrap rounded-full border border-secondary bg-surface-tertiary font-mono font-medium leading-none text-primary uppercase'; + 'inline-flex whitespace-nowrap rounded-full border border-secondary bg-surface-tertiary font-sans uppercase font-medium leading-none text-primary'; const segmentClasses = 'inline-flex flex-nowrap items-center justify-center gap-1'; @@ -10,8 +10,14 @@ BadgeCountSize, { badge: string; segment: string } > = { - sm: { badge: 'text-2xs', segment: 'py-0.5 px-1' }, - md: { badge: 'text-xs', segment: 'py-1 px-1.5' }, + sm: { + badge: 'text-2xs leading-none min-h-[16px] py-0.5', + segment: ' px-1', + }, + md: { + badge: 'text-xs leading-none min-h-[20px] py-0.5 ', + segment: 'px-1.5', + }, }; @@ -43,12 +49,15 @@ class={twMerge(sharedClasses, sizeClasses[size].badge, className)} {...rest} > - {value} + + {value} + {#if total !== undefined} - / - {total} + + / + + + {total} + {/if} diff --git a/src/lib/io/badge-status/badge-status.stories.svelte b/src/lib/io/badge-status/badge-status.stories.svelte index 861a88f966..fbb2e409f3 100644 --- a/src/lib/io/badge-status/badge-status.stories.svelte +++ b/src/lib/io/badge-status/badge-status.stories.svelte @@ -42,6 +42,7 @@ size: { control: 'select', options: sizes }, TrailIcon: { control: false }, extensions: { control: 'object' }, + showImpliedExtensions: { control: 'boolean' }, class: { table: { disable: true } }, }, parameters: { @@ -61,6 +62,26 @@ + + + + + + - import type { IconComponent } from '$lib/io/icon'; + import type { I18nKey } from '$lib/i18n'; + import { type IconComponent, IconPause } from '$lib/io/icon'; import type { ConditionalValue } from '$lib/io/types'; export type BadgeStatusValue = @@ -37,16 +38,22 @@ extension !== false && extension !== null && extension !== undefined; const sharedClasses = - 'inline-flex max-w-full items-stretch overflow-hidden whitespace-nowrap rounded-full font-mono font-medium leading-none uppercase tracking-wide'; + 'inline-flex max-w-full items-stretch overflow-hidden whitespace-nowrap rounded-full font-sans uppercase font-medium '; const segmentClasses = - 'inline-flex flex-nowrap items-center justify-center gap-1 border'; + 'inline-flex flex-nowrap items-center justify-center gap-1 border py-0.5'; const sizeClasses: Record< BadgeStatusSize, { badge: string; segment: string } > = { - sm: { badge: 'text-2xs', segment: 'py-0.5 px-1' }, - md: { badge: 'text-xs', segment: 'py-1 px-1.5' }, + sm: { + badge: 'text-2xs leading-none', + segment: ' px-1 min-h-[16px]', + }, + md: { + badge: 'text-xs leading-none', + segment: 'px-1.5 min-h-[20px]', + }, }; const colorSchemeClasses: Record = { @@ -60,16 +67,57 @@ const statusConfiguration: Record< BadgeStatusValue, - { text: string; colorScheme: BadgeStatusColorScheme } + { + translationId: I18nKey; + colorScheme: BadgeStatusColorScheme; + impliedExtensions: BadgeStatusExtensions; + } > = { - Running: { text: 'running', colorScheme: 'info' }, - Paused: { text: 'paused', colorScheme: 'info' }, - Completed: { text: 'complete', colorScheme: 'success' }, - ContinuedAsNew: { text: 'continued as new', colorScheme: 'success' }, - Failed: { text: 'failed', colorScheme: 'danger' }, - TimedOut: { text: 'timed out', colorScheme: 'error' }, - Terminated: { text: 'terminated', colorScheme: 'warning' }, - Canceled: { text: 'cancelled', colorScheme: 'neutral' }, + Running: { + translationId: 'workflows.running', + colorScheme: 'info', + impliedExtensions: [], + }, + Paused: { + translationId: 'workflows.paused', + colorScheme: 'info', + impliedExtensions: [ + { + colorScheme: 'warning', + TrailIcon: IconPause, + }, + ], + }, + Completed: { + translationId: 'workflows.completed', + colorScheme: 'success', + impliedExtensions: [], + }, + ContinuedAsNew: { + translationId: 'workflows.continued-as-new', + colorScheme: 'success', + impliedExtensions: [], + }, + Failed: { + translationId: 'workflows.failed', + colorScheme: 'danger', + impliedExtensions: [], + }, + TimedOut: { + translationId: 'workflows.timed-out', + colorScheme: 'error', + impliedExtensions: [], + }, + Terminated: { + translationId: 'workflows.terminated', + colorScheme: 'warning', + impliedExtensions: [], + }, + Canceled: { + translationId: 'workflows.canceled', + colorScheme: 'neutral', + impliedExtensions: [], + }, }; @@ -78,6 +126,8 @@ import { twMerge } from 'tailwind-merge'; + import { translate } from '$lib/i18n/translate'; + interface Props extends Omit< HTMLAttributes, 'children' | 'class' @@ -87,6 +137,10 @@ text?: string; count?: string | number; TrailIcon?: ConditionalValue; + /** some statuses such as 'Paused' imply an extension (a pause icon) + * this is an escape hatch for when you do not want those extensions. + */ + showImpliedExtensions?: boolean; extensions?: ConditionalValue; class?: string; } @@ -97,19 +151,23 @@ text, count, TrailIcon, + showImpliedExtensions = true, extensions, class: className, ...rest }: Props = $props(); const configuration = $derived(statusConfiguration[status]); - const visibleExtensions = $derived( - extensions ? extensions.filter(isBadgeStatusExtension) : [], - ); + const visibleExtensions = $derived.by(() => { + return [ + ...(showImpliedExtensions ? configuration.impliedExtensions : []), + ...(extensions || []), + ].filter(isBadgeStatusExtension); + }); - + {#if typeof count === 'number'} - {count.toLocaleString()} {text ?? configuration.text} + {count.toLocaleString()} + {text ?? translate(configuration.translationId)} {:else if count != null} - {count} {text ?? configuration.text} + {count} {text ?? translate(configuration.translationId)} {:else} - {text ?? configuration.text} + {text ?? translate(configuration.translationId)} {/if} {#if TrailIcon} diff --git a/src/lib/io/badge/badge.svelte b/src/lib/io/badge/badge.svelte index 916bf9a381..5e37586a7f 100644 --- a/src/lib/io/badge/badge.svelte +++ b/src/lib/io/badge/badge.svelte @@ -20,13 +20,13 @@ }; const sharedClasses = - 'inline-flex whitespace-nowrap divide-x divide-inherit rounded-full border font-mono font-medium leading-none uppercase'; + 'inline-flex whitespace-nowrap divide-x divide-inherit rounded-full border font-sans uppercase font-medium py-0.5'; const segmentClasses = 'inline-flex flex-nowrap items-center justify-center gap-1'; const sizeClasses: Record = { - sm: { badge: 'text-2xs', segment: 'py-0.5 px-1' }, - md: { badge: 'text-xs', segment: 'py-1 px-1.5' }, + sm: { badge: 'text-2xs leading-none min-h-[16px]', segment: 'px-1' }, + md: { badge: 'text-xs leading-none min-h-[20px]', segment: 'px-1.5' }, }; const colorSchemeClasses: Record = { diff --git a/src/lib/io/chip-status/chip-status.svelte b/src/lib/io/chip-status/chip-status.svelte index b6b95f5141..f83bd083af 100644 --- a/src/lib/io/chip-status/chip-status.svelte +++ b/src/lib/io/chip-status/chip-status.svelte @@ -18,9 +18,9 @@ | 'error'; const sharedClasses = - "relative inline-flex appearance-none items-stretch overflow-hidden whitespace-nowrap rounded-sm border p-0 font-mono text-xs leading-none uppercase before:pointer-events-none before:absolute before:inset-0 before:z-0 before:rounded-[inherit] before:bg-transparent before:content-[''] enabled:cursor-pointer enabled:hover:before:bg-surface-overlay-primary enabled:active:before:bg-surface-overlay-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-interactive-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background-primary disabled:cursor-not-allowed disabled:opacity-disabled"; + "relative inline-flex appearance-none items-stretch overflow-hidden whitespace-nowrap rounded-sm border p-0 font-sans uppercase font-medium text-xs leading-none before:pointer-events-none before:absolute before:inset-0 before:z-0 before:rounded-[inherit] before:bg-transparent before:content-[''] enabled:cursor-pointer enabled:hover:before:bg-surface-overlay-primary enabled:active:before:bg-surface-overlay-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-interactive-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background-primary disabled:cursor-not-allowed disabled:opacity-disabled min-h-[20px]"; const segmentClasses = - 'relative z-10 inline-flex flex-nowrap items-center justify-center gap-1 p-1'; + 'relative z-10 inline-flex flex-nowrap items-center justify-center gap-1 py-0.5 px-1'; const colorSchemeClasses: Record< ChipStatusColorScheme, @@ -116,7 +116,9 @@ )} {...rest} > - {label} + + {label} + {#if extension !== undefined} - {#if typeof extension === 'number'} - {extension.toLocaleString(undefined, { signDisplay: 'always' })} - {:else} - {extension} - {/if} + + {#if typeof extension === 'number'} + {extension.toLocaleString(undefined, { signDisplay: 'always' })} + {:else} + {extension} + {/if} + {/if} diff --git a/src/lib/io/chip/chip.svelte b/src/lib/io/chip/chip.svelte index 20df5a3f97..f77c099a76 100644 --- a/src/lib/io/chip/chip.svelte +++ b/src/lib/io/chip/chip.svelte @@ -16,9 +16,9 @@ }; const sharedClasses = - "relative inline-flex appearance-none items-stretch overflow-hidden whitespace-nowrap rounded-sm border p-0 font-mono text-xs font-medium leading-none uppercase before:pointer-events-none before:absolute before:inset-0 before:z-0 before:rounded-[inherit] before:bg-transparent before:content-[''] enabled:cursor-pointer enabled:hover:before:bg-surface-overlay-primary enabled:active:before:bg-surface-overlay-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-interactive-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background-primary disabled:cursor-not-allowed disabled:opacity-disabled"; + "relative inline-flex appearance-none items-stretch overflow-hidden whitespace-nowrap rounded-sm border p-0 font-sans uppercase font-medium text-xs leading-none uppercase before:pointer-events-none before:absolute before:inset-0 before:z-0 before:rounded-[inherit] before:bg-transparent before:content-[''] enabled:cursor-pointer enabled:hover:before:bg-surface-overlay-primary enabled:active:before:bg-surface-overlay-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-interactive-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background-primary disabled:cursor-not-allowed disabled:opacity-disabled min-h-[20px]"; const segmentClasses = - 'relative z-10 inline-flex flex-nowrap items-center justify-center gap-1 p-1'; + 'relative z-10 inline-flex flex-nowrap items-center justify-center gap-1 py-0.5 px-1'; const colorSchemeClasses: Record< ChipColorScheme, diff --git a/src/lib/io/icon/icons/pause.svelte b/src/lib/io/icon/icons/pause.svelte index 225f52ddfd..fb1ae3e6fe 100644 --- a/src/lib/io/icon/icons/pause.svelte +++ b/src/lib/io/icon/icons/pause.svelte @@ -6,5 +6,8 @@ - + diff --git a/src/lib/services/schedule-service.test.ts b/src/lib/services/schedule-service.test.ts index 91dc7360a1..7c31e54cef 100644 --- a/src/lib/services/schedule-service.test.ts +++ b/src/lib/services/schedule-service.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, test, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import type { DescribeFullSchedule } from '$lib/types/schedule'; import type { WorkflowExecution } from '$lib/types/workflows'; @@ -63,6 +63,17 @@ describe('toRecentScheduleRuns', () => { expect(toRecentScheduleRuns(schedule)[0].status).toBe('Running'); }); + test('does not add visibility-derived fields to recorded runs', () => { + const schedule = scheduleWith([ + action('a', '2026-08-01T00:00:00Z', 'WORKFLOW_EXECUTION_STATUS_RUNNING'), + ]); + + const run = toRecentScheduleRuns(schedule)[0]; + expect(run).not.toHaveProperty('delayed'); + expect(run).not.toHaveProperty('taskFailure'); + expect(run).not.toHaveProperty('inVisibility'); + }); + test('does not mutate the schedule response', () => { const actions = [ action('a', '2026-08-01T00:00:00Z'), @@ -90,6 +101,90 @@ describe('withLatestWorkflowStatuses', () => { ]), ); + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2026-08-06T00:00:00Z')); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + test('derives delay flags from execution time', () => { + const originalRuns = structuredClone(runs); + const merged = withLatestWorkflowStatuses(runs, [ + { + ...execution('a', '2026-08-01T00:00:00Z', 'Running'), + executionTime: '2026-08-07T00:00:00Z', + }, + { + ...execution('b', '2026-08-02T00:00:00Z', 'Running'), + executionTime: '2026-08-05T00:00:00Z', + }, + ]); + + expect(merged.map((run) => run.delayed)).toEqual([false, true]); + expect(runs).toEqual(originalRuns); + }); + + test.each(['category=WorkflowTaskFailed', 'category=WorkflowTaskTimedOut'])( + 'sets taskFailure for a running workflow reporting %s', + (problem) => { + const originalRuns = structuredClone(runs); + const merged = withLatestWorkflowStatuses(runs, [ + { + ...execution('a', '2026-08-01T00:00:00Z', 'Running'), + searchAttributes: { + indexedFields: { TemporalReportedProblems: [problem] }, + }, + }, + ]); + + expect(merged.find((run) => run.workflowId === 'a')?.taskFailure).toBe( + true, + ); + expect(runs).toEqual(originalRuns); + }, + ); + + test('does not mark completed workflows as task-failed', () => { + const merged = withLatestWorkflowStatuses(runs, [ + { + ...execution('a', '2026-08-01T00:00:00Z', 'Completed'), + searchAttributes: { + indexedFields: { + TemporalReportedProblems: ['category=WorkflowTaskFailed'], + }, + }, + }, + ]); + + expect(merged.find((run) => run.workflowId === 'a')?.taskFailure).toBe( + false, + ); + }); + + test('derives both modifiers from the newest execution', () => { + const merged = withLatestWorkflowStatuses(runs, [ + { + ...execution('a', '2026-08-05T00:00:00Z', 'Running', 'a-run-2'), + executionTime: '2026-08-07T00:00:00Z', + searchAttributes: { + indexedFields: { + TemporalReportedProblems: ['category=WorkflowTaskFailed'], + }, + }, + }, + execution('a', '2026-08-01T00:00:00Z', 'ContinuedAsNew', 'a-run-1'), + ]); + + expect(merged.find((run) => run.workflowId === 'a')).toMatchObject({ + runId: 'a-run-2', + delayed: true, + taskFailure: true, + }); + }); + test('replaces the recorded status with the live one', () => { const merged = withLatestWorkflowStatuses(runs, [ execution('a', '2026-08-01T00:00:00Z', 'Terminated'), @@ -107,6 +202,15 @@ describe('withLatestWorkflowStatuses', () => { expect(merged.map((run) => run.status)).toEqual(['Completed', 'Running']); }); + test('marks only matched runs as present in visibility', () => { + const merged = withLatestWorkflowStatuses(runs, [ + execution('b', '2026-08-02T00:00:00Z', 'Completed'), + ]); + + expect(merged.map((run) => run.inVisibility)).toEqual([true, false]); + expect(runs.every((run) => !('inVisibility' in run))).toBe(true); + }); + test('uses the newest execution of a continue-as-new chain', () => { const merged = withLatestWorkflowStatuses(runs, [ execution('a', '2026-08-01T00:00:00Z', 'ContinuedAsNew', 'a-run-1'), @@ -117,6 +221,7 @@ describe('withLatestWorkflowStatuses', () => { expect(merged.find((run) => run.workflowId === 'a')).toMatchObject({ status: 'Failed', runId: 'a-run-3', + inVisibility: true, }); }); @@ -131,8 +236,15 @@ describe('withLatestWorkflowStatuses', () => { }); }); - test('leaves runs untouched when visibility returns nothing', () => { - expect(withLatestWorkflowStatuses(runs, [])).toEqual(runs); + test('defaults modifier and visibility flags when visibility returns nothing', () => { + expect(withLatestWorkflowStatuses(runs, [])).toEqual( + runs.map((run) => ({ + ...run, + delayed: false, + taskFailure: false, + inVisibility: false, + })), + ); }); }); diff --git a/src/lib/services/schedule-service.ts b/src/lib/services/schedule-service.ts index c9a7d19559..dbba2a1436 100644 --- a/src/lib/services/schedule-service.ts +++ b/src/lib/services/schedule-service.ts @@ -4,15 +4,18 @@ import type { DescribeFullSchedule, OverlapPolicy, RecentScheduleRun, + RecentScheduleRunEnriched, ScheduleRequestBody, } from '$lib/types/schedule'; import type { WorkflowExecution } from '$lib/types/workflows'; +import { isWorkflowDelayed } from '$lib/utilities/delayed-workflows'; import { getEpochMilliseconds } from '$lib/utilities/format-time'; import { stringifyWithBigInt } from '$lib/utilities/parse-with-big-int'; import type { ErrorCallback } from '$lib/utilities/request-from-api'; import { requestFromAPI } from '$lib/utilities/request-from-api'; import { routeForApi } from '$lib/utilities/route-for-api'; import { toWorkflowStatusReadable } from '$lib/utilities/screaming-enums'; +import { isWorkflowTaskFailure } from '$lib/utilities/workflow-task-failures'; import { fetchWorkflowsForQuery } from './workflow-service'; @@ -133,7 +136,7 @@ export const toUpcomingScheduleRuns = ( export const withLatestWorkflowStatuses = ( runs: RecentScheduleRun[], workflows: WorkflowExecution[], -): RecentScheduleRun[] => { +): RecentScheduleRunEnriched[] => { const latest = new Map(); for (const workflow of workflows) { const current = latest.get(workflow.id); @@ -149,13 +152,21 @@ export const withLatestWorkflowStatuses = ( return runs.map((run) => { const latestWorkflow = latest.get(run.workflowId); if (!latestWorkflow) { - return run; + return { + ...run, + delayed: false, + taskFailure: false, + inVisibility: false, + }; } return { actualTime: run.actualTime, workflowId: latestWorkflow.id || run.workflowId, status: latestWorkflow.status || run.status, runId: latestWorkflow.runId || run.runId, + delayed: isWorkflowDelayed(latestWorkflow), + taskFailure: isWorkflowTaskFailure(latestWorkflow), + inVisibility: true, }; }); }; @@ -179,12 +190,12 @@ type RecentRunStatusParams = { export const fetchRecentScheduleRunStatuses = async ( { namespace, scheduleId, runs }: RecentRunStatusParams, request = fetch, -): Promise => { +): Promise => { const workflowIds = [ ...new Set(runs.map((run) => run.workflowId).filter(Boolean)), ]; if (!workflowIds.length) { - return runs; + return withLatestWorkflowStatuses(runs, []); } // Backslashes first: the query tokenizer treats them as escape characters, so diff --git a/src/lib/types/schedule.ts b/src/lib/types/schedule.ts index b9a775033e..80bccc204f 100644 --- a/src/lib/types/schedule.ts +++ b/src/lib/types/schedule.ts @@ -54,6 +54,12 @@ export type RecentScheduleRun = { status: WorkflowStatus; }; +export type RecentScheduleRunEnriched = RecentScheduleRun & { + delayed: boolean; + taskFailure: boolean; + inVisibility: boolean; +}; + /** * Schedule create/edit request bodies. The Temporal HTTP API encodes durations * and timestamps as strings, enums as their string names and 64-bit integers as