From b3f5e953e47463f9559e3eb3b31af267ffb86d40 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 9 Oct 2025 22:32:52 -0500 Subject: [PATCH 01/40] Testing out api --- server/go.mod | 2 +- server/go.sum | 4 +- src/lib/pages/task-queue.svelte | 80 ++++++++++++++++++++++++++++++ src/lib/services/worker-service.ts | 60 ++++++++++++++++++++++ src/lib/types/api.ts | 11 +++- src/lib/utilities/route-for-api.ts | 5 +- 6 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 src/lib/services/worker-service.ts diff --git a/server/go.mod b/server/go.mod index be9f002afb..03061d7fa8 100644 --- a/server/go.mod +++ b/server/go.mod @@ -11,7 +11,7 @@ require ( github.com/labstack/echo/v4 v4.13.4 github.com/stretchr/testify v1.10.0 github.com/urfave/cli/v2 v2.3.0 - go.temporal.io/api v1.51.0 + go.temporal.io/api v1.54.0 golang.org/x/net v0.40.0 golang.org/x/oauth2 v0.30.0 google.golang.org/grpc v1.66.0 diff --git a/server/go.sum b/server/go.sum index 875a544786..82da900640 100644 --- a/server/go.sum +++ b/server/go.sum @@ -68,8 +68,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -go.temporal.io/api v1.51.0 h1:9+e14GrIa7nWoWoudqj/PSwm33yYjV+u8TAR9If7s/g= -go.temporal.io/api v1.51.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/api v1.54.0 h1:/sy8rYZEykgmXRjeiv1PkFHLXIus5n6FqGhRtCl7Pc0= +go.temporal.io/api v1.54.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= diff --git a/src/lib/pages/task-queue.svelte b/src/lib/pages/task-queue.svelte index e0f5e5634f..1b60eb348f 100644 --- a/src/lib/pages/task-queue.svelte +++ b/src/lib/pages/task-queue.svelte @@ -2,9 +2,50 @@ import { page } from '$app/state'; import WorkerTable from '$lib/components/worker-table.svelte'; + import CodeBlock from '$lib/holocene/code-block.svelte'; import { getPollers } from '$lib/services/pollers-service'; + import { + type DescribeWorkerResponse, + listWorkers, + type ListWorkersResponse, + // describeWorker, + } from '$lib/services/worker-service'; + import { stringifyWithBigInt } from '$lib/utilities/parse-with-big-int'; const { queue: taskQueue, namespace } = $derived(page.params); + + let listWorkersResponse = $state(null); + let describeWorkerResponses = $state>( + {}, + ); + + async function loadWorkers() { + try { + const response = await listWorkers({ queue: taskQueue, namespace }); + console.log('Workers response:', response); + // listWorkersResponse = response; + + // const describePromises = response.workers.map(async (worker) => { + // const describeResponse = await describeWorker({ + // queue: taskQueue, + // namespace, + // identity: worker.identity, + // }); + // return { identity: worker.identity, response: describeResponse }; + // }); + + // const results = await Promise.all(describePromises); + // describeWorkerResponses = results.reduce( + // (acc, { identity, response }) => { + // acc[identity] = response; + // return acc; + // }, + // {} as Record, + // ); + } catch (error) { + console.error('Error loading workers:', error); + } + } {#await getPollers({ queue: taskQueue, namespace }) then workers} @@ -13,5 +54,44 @@ {taskQueue} + +
+

Worker API Responses

+ + {#await loadWorkers()} +

Loading workers...

+ {:then} + {#if listWorkersResponse} +
+

List Workers Response

+ +
+ + {#if listWorkersResponse.workers.length > 0} +
+

Describe Worker Responses

+ {#each listWorkersResponse.workers as worker (worker.identity)} +
+

Worker: {worker.identity}

+ {#if describeWorkerResponses[worker.identity]} + + {/if} +
+ {/each} +
+ {/if} + {/if} + {:catch error} +

Error loading workers: {error.message}

+ {/await} +
{/await} diff --git a/src/lib/services/worker-service.ts b/src/lib/services/worker-service.ts new file mode 100644 index 0000000000..6819527473 --- /dev/null +++ b/src/lib/services/worker-service.ts @@ -0,0 +1,60 @@ +import type { NamespaceScopedRequest } from '$lib/types/global'; +import { requestFromAPI } from '$lib/utilities/request-from-api'; +import { routeForApi } from '$lib/utilities/route-for-api'; + +export type ListWorkersRequest = NamespaceScopedRequest & { + queue: string; +}; + +export type DescribeWorkerRequest = NamespaceScopedRequest & { + queue: string; + identity: string; +}; + +export type WorkerInfo = { + identity: string; + buildId: string; + lastAccessTime: string; + taskQueueTypes: number[]; + rateLimitPerSecond: number; +}; + +export type ListWorkersResponse = { + workers: WorkerInfo[]; +}; + +export type DescribeWorkerResponse = { + buildId: string; + capabilities: { + useVersioning: boolean; + activityOnly: boolean; + }; + lastAccessTime: string; + rateLimitPerSecond: number; + taskQueues: { + name: string; + kind: number; + }[]; +}; + +export async function listWorkers( + parameters: ListWorkersRequest, + request = fetch, +): Promise { + const route = routeForApi('workers', parameters); + return await requestFromAPI(route, { + request, + params: { taskQueue: parameters.queue }, + }); +} + +export async function describeWorker( + parameters: DescribeWorkerRequest, + request = fetch, +): Promise { + const route = routeForApi('worker', parameters); + return await requestFromAPI(route, { + request, + params: { identity: parameters.identity }, + }); +} diff --git a/src/lib/types/api.ts b/src/lib/types/api.ts index 480da8c8be..73cf5bfc0b 100644 --- a/src/lib/types/api.ts +++ b/src/lib/types/api.ts @@ -43,7 +43,10 @@ export type ParameterlessAPIRoutePath = | 'user' | 'nexus-endpoints' | 'namespaces'; -export type WorkerAPIRoutePath = 'worker-task-reachability'; +export type WorkerAPIRoutePath = + | 'worker-task-reachability' + | 'workers' + | 'worker'; export type SchedulesAPIRoutePath = 'schedules'; export type ScheduleAPIRoutePath = | 'schedule' @@ -90,6 +93,7 @@ export type APIRouteParameters = { endpointId: string; deploymentName: string; version: string; + workerInstanceKey: string; }; export type WorkflowListRouteParameters = Pick; @@ -140,6 +144,11 @@ export type TaskQueueRouteParameters = Pick< 'namespace' | 'queue' >; +export type WorkerRouteParameters = Pick< + APIRouteParameters, + 'namespace' | 'queue' | 'workerInstanceKey' +>; + export type ValidWorkflowEndpoints = WorkflowsAPIRoutePath; export type ValidWorkflowParameters = diff --git a/src/lib/utilities/route-for-api.ts b/src/lib/utilities/route-for-api.ts index 142e83f4b3..33c67d23b7 100644 --- a/src/lib/utilities/route-for-api.ts +++ b/src/lib/utilities/route-for-api.ts @@ -28,6 +28,7 @@ import type { WorkerDeploymentsAPIRoutePath, WorkerDeploymentVersionAPIRoutePath, WorkerDeploymentVersionRouteParameters, + WorkerRouteParameters, WorkflowActivitiesAPIRoutePath, WorkflowActivitiesRouteParameters, WorkflowAPIRoutePath, @@ -145,6 +146,8 @@ export function pathForApi( 'task-queue.compatibility': `/namespaces/${parameters?.namespace}/task-queues/${parameters?.queue}/worker-build-id-compatibility`, 'task-queue.rules': `/namespaces/${parameters?.namespace}/task-queues/${parameters?.queue}/worker-versioning-rules`, user: '/me', + workers: `/namespaces/${parameters?.namespace}/workers`, + worker: `/namespaces/${parameters?.namespace}/workers/describe/${parameters?.workerInstanceKey}`, 'worker-task-reachability': `/namespaces/${parameters?.namespace}/worker-task-reachability`, 'workflow.terminate': `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/terminate`, 'workflow.cancel': `/namespaces/${parameters.namespace}/workflows/${parameters.workflowId}/cancel`, @@ -190,7 +193,7 @@ export function routeForApi( ): string; export function routeForApi( route: WorkerAPIRoutePath, - parameters: NamespaceRouteParameters, + parameters: NamespaceRouteParameters | WorkerRouteParameters, shouldEncode?: boolean, ): string; export function routeForApi( From 2b15a344af8526fac892d8264b92e0bc004e7ba6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 5 Dec 2025 14:18:46 -0600 Subject: [PATCH 02/40] Add worker-info to Workers tab --- .../components/task-queue/worker-info.svelte | 435 +++++++++--------- src/lib/pages/task-queue.svelte | 46 +- src/lib/pages/workflow-workers.svelte | 43 +- src/lib/services/worker-service.ts | 8 +- .../[namespace]/task-queues/+layout.svelte | 4 - 5 files changed, 261 insertions(+), 275 deletions(-) diff --git a/src/lib/components/task-queue/worker-info.svelte b/src/lib/components/task-queue/worker-info.svelte index 5f52d611ac..2192da924c 100644 --- a/src/lib/components/task-queue/worker-info.svelte +++ b/src/lib/components/task-queue/worker-info.svelte @@ -3,12 +3,19 @@ import Timestamp from '$lib/components/timestamp.svelte'; import Badge from '$lib/holocene/badge.svelte'; + import Card from '$lib/holocene/card.svelte'; import Icon from '$lib/holocene/icon/icon.svelte'; import Link from '$lib/holocene/link.svelte'; - import { type WorkerInfo } from '$lib/services/worker-service'; + import { + type WorkerInfo, + type WorkerPollerInfo, + type WorkerSlotsInfo, + } from '$lib/services/worker-service'; import { routeForTaskQueue } from '$lib/utilities/route-for'; import { fromScreamingEnum } from '$lib/utilities/screaming-enums'; + import SdkLogo from '../lines-and-dots/sdk-logo.svelte'; + type Props = { worker: WorkerInfo; }; @@ -63,7 +70,7 @@
-
+
Active Workers
@@ -74,13 +81,13 @@ ? 'bg-green-500' : 'bg-gray-400'}" > - + {status}
-
+ -
+
Total Processed Tasks
@@ -88,19 +95,19 @@
{workflowProcessedTasks} workflow · {activityProcessedTasks} activity
-
+ -
+
Available Slots
{totalAvailableSlots}
-
+
{capacityPercent}% capacity free
-
+
-
+
Sticky Cache Hit
@@ -108,18 +115,17 @@
{currentStickyCacheSize} cached workflows
-
+
-
+
-
-
+ +
-

{heartbeat.workerIdentity}

{#if isRunning} @@ -129,29 +135,14 @@ {/if} {status} -
-
-
- Instance: - {heartbeat.workerInstanceKey} -
-
- Group: - {heartbeat.hostInfo.workerGroupingKey} -
+

{heartbeat.workerIdentity}

-
+
-
+
Task Queue
{heartbeat.taskQueue}
-
+
SDK
-
{heartbeat.sdkName}
-
-
-
- SDK Version -
-
{heartbeat.sdkVersion}
-
-
-
- Host Name -
-
{heartbeat.hostInfo.hostName}
-
-
-
- Process ID -
-
{heartbeat.hostInfo.processId}
-
-
-
- Last Heartbeat -
-
- ~ ago -
+
- -
+

Task Slots

@@ -221,9 +188,9 @@
-
+

Pollers

@@ -243,195 +210,217 @@ {@render pollerCard('Nexus Poller', heartbeat.nexusPollerInfo)}
-
+
-
-
-
- -

Host Information

-
-
-
- Hostname - {heartbeat.hostInfo.hostName} -
-
- Process ID - {heartbeat.hostInfo.processId} -
-
- Worker Grouping Key - - {heartbeat.hostInfo.workerGroupingKey} - -
-
- CPU Usage -
-
-
-
- {heartbeat.hostInfo.currentHostCpuUsage.toFixed(1)}% -
-
-
- Memory Usage -
-
-
-
- {heartbeat.hostInfo.currentHostMemUsage.toFixed(1)}% -
-
-
-
- - {#if heartbeat.deploymentVersion?.deploymentName} -
-
- -

Deployment Version

-
-
-
-
Build ID
- - {heartbeat.deploymentVersion.buildId} - -
-
- SDK - {heartbeat.sdkName} -
-
- Version - {heartbeat.sdkVersion} -
-
-
- {/if} - -
-
- -

Sticky Cache

-
-
-
- {totalStickyCacheHit} - Cache Hits -
-
- {currentStickyCacheSize} - Cache Size -
-
-
- -
-
- -

Timestamps

-
-
-
-
Start Time
- -
-
-
Last Heartbeat
- -
- ~ ago -
-
-
-
+
+ {@render timestamps()} + {@render hostInfo()} + {@render deploymentInfo()} + {@render usage()} + {@render cache()}
-{#snippet slotCard( - title: string, - slots: import('$lib/services/worker-service').WorkerSlotsInfo, -)} -
+{#snippet slotCard(title: string, slots: WorkerSlotsInfo)} +
{title} {slots.slotSupplierKind}
- - {slots.currentUsedSlots} + + {slots.currentUsedSlots ?? 0} Used
- {slots.currentAvailableSlots}{slots.currentAvailableSlots ?? 0} Available
- - {slots.totalProcessedTasks} + + {slots.totalProcessedTasks ?? 0} Processed
-
- -
-
+ {#if slots.currentAvailableSlots} +
+ {#each new Array(slots.currentAvailableSlots) as _, i} +
+ {/each} +
+ {/if} +
{/snippet} -{#snippet pollerCard( - title: string, - poller: import('$lib/services/worker-service').WorkerPollerInfo, -)} -
+{#snippet pollerCard(title: string, poller: WorkerPollerInfo)} +
{title} - - {poller.currentPollers} + + {poller.currentPollers ?? 0}
-
+
{#if poller.lastSuccessfulPollTime} Last poll: {:else} No activity {/if}
-
+
+{/snippet} + +{#snippet hostInfo()} + +
+ +

Host Information

+
+
+
+ Hostname + {heartbeat.hostInfo.hostName} +
+
+ Process ID + {heartbeat.hostInfo.processId} +
+
+ Instance Key + + {heartbeat.workerInstanceKey} + +
+
+ Worker Grouping Key + + {heartbeat.hostInfo.workerGroupingKey} + +
+
+
+{/snippet} + +{#snippet deploymentInfo()} + {#if heartbeat.deploymentVersion?.deploymentName} + +
+ +

Deployment Version

+
+
+
+
Deployment
+ + {heartbeat.deploymentVersion.deploymentName} + +
+
+ Build ID + {heartbeat.deploymentVersion.buildId} +
+
+
+ {/if} +{/snippet} + +{#snippet usage()} + +
+ +

Usage

+
+
+ +
+

+ {heartbeat.hostInfo.currentHostCpuUsage.toFixed(1)}% +

+

+ CPU Usage +

+
+
+ +
+

+ {heartbeat.hostInfo.currentHostMemUsage.toFixed(1)}% +

+

+ Memory Usage +

+
+
+
+
+{/snippet} + +{#snippet cache()} + +
+ +

Sticky Cache

+
+
+ +
+

+ {totalStickyCacheHit} +

+

+ Cache Hits +

+
+
+ +
+

{currentStickyCacheSize}

+

+ Cache Size +

+
+
+
+
+{/snippet} + +{#snippet timestamps()} + +
+ +

Timestamps

+
+
+
+
Start Time
+ +
+
+
Last Heartbeat
+ +
+ ~{heartbeat.elapsedSinceLastHeartbeat} ago +
+
+
+
{/snippet} diff --git a/src/lib/pages/task-queue.svelte b/src/lib/pages/task-queue.svelte index dac731f51d..0890e2c727 100644 --- a/src/lib/pages/task-queue.svelte +++ b/src/lib/pages/task-queue.svelte @@ -3,34 +3,32 @@ import WorkerInfo from '$lib/components/task-queue/worker-info.svelte'; import WorkerTable from '$lib/components/worker-table.svelte'; - import CodeBlock from '$lib/holocene/code-block.svelte'; import Skeleton from '$lib/holocene/skeleton/index.svelte'; import { getPollers } from '$lib/services/pollers-service'; - import { listWorkers } from '$lib/services/worker-service'; - import { stringifyWithBigInt } from '$lib/utilities/parse-with-big-int'; + import { listWorkersForTaskQueue } from '$lib/services/worker-service'; const { queue: taskQueue, namespace } = $derived(page.params); -{#await getPollers({ queue: taskQueue, namespace }) then workers} -
-

- {taskQueue} -

- -
-{/await} +
+

Task Queue

+

+ {taskQueue} +

-{#await listWorkers({ queue: taskQueue, namespace })} - -{:then response} - {#each response?.workersInfo as worker} - - {/each} -
-

List Workers Response

- -
-{:catch error} -

Error loading workers: {error.message}

-{/await} + {#await listWorkersForTaskQueue({ queue: taskQueue, namespace })} + + {:then response} + {#if !response?.workersInfo} + {#await getPollers({ queue: taskQueue, namespace }) then workers} + + {/await} + {:else} + {#each response.workersInfo as worker} + + {/each} + {/if} + {:catch error} +

Error loading workers: {error.message}

+ {/await} +
diff --git a/src/lib/pages/workflow-workers.svelte b/src/lib/pages/workflow-workers.svelte index c0f52460bd..66e4c791ff 100644 --- a/src/lib/pages/workflow-workers.svelte +++ b/src/lib/pages/workflow-workers.svelte @@ -1,28 +1,31 @@
- -

- {translate('common.task-queue')}: - {taskQueue} -

-
+ {#await listWorkersForTaskQueue({ queue: taskQueue, namespace })} + + {:then response} + {#if !response?.workersInfo} + {#await getPollers({ queue: taskQueue, namespace }) then workers} + + {/await} + {:else} + {#each response.workersInfo as worker} + + {/each} + {/if} + {:catch error} +

Error loading workers: {error.message}

+ {/await}
diff --git a/src/lib/services/worker-service.ts b/src/lib/services/worker-service.ts index c5dc04d9b6..adc22f4075 100644 --- a/src/lib/services/worker-service.ts +++ b/src/lib/services/worker-service.ts @@ -1,4 +1,4 @@ -import type { Timestamp } from '@temporalio/common'; +import type { Duration, Timestamp } from '@temporalio/common'; import type { WorkerDeploymentVersion } from '$lib/types/deployments'; import type { NamespaceScopedRequest } from '$lib/types/global'; @@ -54,7 +54,7 @@ export type WorkerHeartbeat = { status: string; startTime: Timestamp; heartbeatTime: Timestamp; - elapsedSinceLastHeartbeat: Timestamp; + elapsedSinceLastHeartbeat: Duration; workflowTaskSlotsInfo: WorkerSlotsInfo; activityTaskSlotsInfo: WorkerSlotsInfo; nexusTaskSlotsInfo: WorkerSlotsInfo; @@ -91,14 +91,14 @@ export type DescribeWorkerResponse = { }[]; }; -export async function listWorkers( +export async function listWorkersForTaskQueue( parameters: ListWorkersRequest, request = fetch, ): Promise { const route = routeForApi('workers', parameters); const response = await requestFromAPI(route, { request, - params: { taskQueue: parameters.queue }, + params: { query: `TaskQueue="${parameters.queue}"` }, }); return response; } diff --git a/src/routes/(app)/namespaces/[namespace]/task-queues/+layout.svelte b/src/routes/(app)/namespaces/[namespace]/task-queues/+layout.svelte index 45155471cd..78635ae367 100644 --- a/src/routes/(app)/namespaces/[namespace]/task-queues/+layout.svelte +++ b/src/routes/(app)/namespaces/[namespace]/task-queues/+layout.svelte @@ -1,14 +1,10 @@ -

- {translate('common.task-queue')} -

{@render children()} From e992c5031f3d8ad1a5f2dffa41228bc61f42e96d Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 5 Dec 2025 15:02:38 -0600 Subject: [PATCH 03/40] Add worker-table if new api fails --- .../components/task-queue/worker-info.svelte | 104 +++++------------- src/lib/pages/task-queue.svelte | 6 +- src/lib/pages/workflow-workers.svelte | 6 +- 3 files changed, 38 insertions(+), 78 deletions(-) diff --git a/src/lib/components/task-queue/worker-info.svelte b/src/lib/components/task-queue/worker-info.svelte index 2192da924c..dc4855fec8 100644 --- a/src/lib/components/task-queue/worker-info.svelte +++ b/src/lib/components/task-queue/worker-info.svelte @@ -25,12 +25,6 @@ const { namespace } = $derived(page.params); const heartbeat = $derived(worker.workerHeartbeat); const status = $derived(fromScreamingEnum(heartbeat.status, 'WorkerStatus')); - const totalAvailableSlots = $derived( - (heartbeat.workflowTaskSlotsInfo?.currentAvailableSlots ?? 0) + - (heartbeat.activityTaskSlotsInfo?.currentAvailableSlots ?? 0) + - (heartbeat.nexusTaskSlotsInfo?.currentAvailableSlots ?? 0) + - (heartbeat.localActivitySlotsInfo?.currentAvailableSlots ?? 0), - ); const totalProcessedTasks = $derived( (heartbeat.workflowTaskSlotsInfo?.totalProcessedTasks ?? 0) + @@ -51,27 +45,13 @@ heartbeat?.currentStickyCacheSize ?? 0, ); - const capacityPercent = $derived( - totalAvailableSlots > 0 - ? Math.round( - (totalAvailableSlots / - (totalAvailableSlots + - (heartbeat.workflowTaskSlotsInfo?.currentUsedSlots ?? 0) + - (heartbeat.activityTaskSlotsInfo?.currentUsedSlots ?? 0) + - (heartbeat.nexusTaskSlotsInfo?.currentUsedSlots ?? 0) + - (heartbeat.localActivitySlotsInfo?.currentUsedSlots ?? 0))) * - 100, - ) - : 0, - ); - const isRunning = $derived(status === 'Running');
-
+
-
+
Active Workers
1
@@ -88,7 +68,7 @@ -
+
Total Processed Tasks
{totalProcessedTasks}
@@ -96,26 +76,6 @@ {workflowProcessedTasks} workflow · {activityProcessedTasks} activity
- - -
- Available Slots -
-
{totalAvailableSlots}
-
- {capacityPercent}% capacity free -
-
- - -
- Sticky Cache Hit -
-
{totalStickyCacheHit}
-
- {currentStickyCacheSize} cached workflows -
-
@@ -281,6 +241,32 @@ {/snippet} +{#snippet timestamps()} + +
+
+
Start Time
+ +
+
+
Last Heartbeat
+ +
+ ~{heartbeat.elapsedSinceLastHeartbeat} ago +
+
+
+
+{/snippet} + {#snippet hostInfo()}
@@ -339,7 +325,7 @@
-

Usage

+

Host Usage

@@ -394,33 +380,3 @@
{/snippet} - -{#snippet timestamps()} - -
- -

Timestamps

-
-
-
-
Start Time
- -
-
-
Last Heartbeat
- -
- ~{heartbeat.elapsedSinceLastHeartbeat} ago -
-
-
-
-{/snippet} diff --git a/src/lib/pages/task-queue.svelte b/src/lib/pages/task-queue.svelte index 0890e2c727..f305a79573 100644 --- a/src/lib/pages/task-queue.svelte +++ b/src/lib/pages/task-queue.svelte @@ -28,7 +28,9 @@ {/each} {/if} - {:catch error} -

Error loading workers: {error.message}

+ {:catch _} + {#await getPollers({ queue: taskQueue, namespace }) then workers} + + {/await} {/await} diff --git a/src/lib/pages/workflow-workers.svelte b/src/lib/pages/workflow-workers.svelte index 66e4c791ff..0f236e256b 100644 --- a/src/lib/pages/workflow-workers.svelte +++ b/src/lib/pages/workflow-workers.svelte @@ -25,7 +25,9 @@ {/each} {/if} - {:catch error} -

Error loading workers: {error.message}

+ {:catch _} + {#await getPollers({ queue: taskQueue, namespace }) then workers} + + {/await} {/await} From 1dfd237f8d1edd507d98437534f92c33713d4fd5 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 8 Dec 2025 15:39:46 -0600 Subject: [PATCH 04/40] Add workers list page --- .../components/task-queue/worker-info.svelte | 48 +-------- .../task-queue/worker-insights.svelte | 11 ++ .../task-queue/worker-table-cell.svelte | 87 +++++++++++++++ .../task-queue/worker-table-row.svelte | 85 +++++++++++++++ .../components/task-queue/worker-table.svelte | 67 ++++++++++++ .../task-queue/worker-totals.svelte | 84 +++++++++++++++ .../workers/worker-search-table.svelte | 54 ++++++++++ .../workflow/workflow-summary.svelte | 4 +- .../holocene/filter-or-copy-buttons.svelte | 4 +- src/lib/i18n/locales/en/workers.ts | 12 +++ src/lib/layouts/workflow-header.svelte | 6 +- src/lib/pages/task-queue.svelte | 27 +---- src/lib/pages/workflow-workers.svelte | 32 +----- src/lib/services/worker-service.ts | 101 +++++++++++++----- src/lib/types/index.ts | 1 + src/lib/utilities/route-for.test.ts | 4 +- src/lib/utilities/route-for.ts | 22 +++- src/lib/utilities/screaming-enums.ts | 6 ++ src/lib/utilities/update-query-parameters.ts | 1 + .../[namespace]/workers/+page.svelte | 17 +++ .../workers/[instance]/+page.svelte | 34 ++++++ 21 files changed, 571 insertions(+), 136 deletions(-) create mode 100644 src/lib/components/task-queue/worker-insights.svelte create mode 100644 src/lib/components/task-queue/worker-table-cell.svelte create mode 100644 src/lib/components/task-queue/worker-table-row.svelte create mode 100644 src/lib/components/task-queue/worker-table.svelte create mode 100644 src/lib/components/task-queue/worker-totals.svelte create mode 100644 src/lib/components/workers/worker-search-table.svelte create mode 100644 src/routes/(app)/namespaces/[namespace]/workers/+page.svelte create mode 100644 src/routes/(app)/namespaces/[namespace]/workers/[instance]/+page.svelte diff --git a/src/lib/components/task-queue/worker-info.svelte b/src/lib/components/task-queue/worker-info.svelte index dc4855fec8..80060d218d 100644 --- a/src/lib/components/task-queue/worker-info.svelte +++ b/src/lib/components/task-queue/worker-info.svelte @@ -12,7 +12,7 @@ type WorkerSlotsInfo, } from '$lib/services/worker-service'; import { routeForTaskQueue } from '$lib/utilities/route-for'; - import { fromScreamingEnum } from '$lib/utilities/screaming-enums'; + import { toWorkerStatusReadable } from '$lib/utilities/screaming-enums'; import SdkLogo from '../lines-and-dots/sdk-logo.svelte'; @@ -24,60 +24,16 @@ const { namespace } = $derived(page.params); const heartbeat = $derived(worker.workerHeartbeat); - const status = $derived(fromScreamingEnum(heartbeat.status, 'WorkerStatus')); - - const totalProcessedTasks = $derived( - (heartbeat.workflowTaskSlotsInfo?.totalProcessedTasks ?? 0) + - (heartbeat.activityTaskSlotsInfo?.totalProcessedTasks ?? 0) + - (heartbeat.nexusTaskSlotsInfo?.totalProcessedTasks ?? 0) + - (heartbeat.localActivitySlotsInfo?.totalProcessedTasks ?? 0), - ); - - const workflowProcessedTasks = $derived( - heartbeat.workflowTaskSlotsInfo?.totalProcessedTasks ?? 0, - ); - const activityProcessedTasks = $derived( - heartbeat.activityTaskSlotsInfo?.totalProcessedTasks ?? 0, - ); + const status = $derived(toWorkerStatusReadable(heartbeat.status)); const totalStickyCacheHit = $derived(heartbeat?.totalStickyCacheHit ?? 0); const currentStickyCacheSize = $derived( heartbeat?.currentStickyCacheSize ?? 0, ); - const isRunning = $derived(status === 'Running');
-
- -
- Active Workers -
-
1
-
- - - {status} - -
-
- - -
- Total Processed Tasks -
-
{totalProcessedTasks}
-
- {workflowProcessedTasks} workflow · {activityProcessedTasks} activity -
-
-
-
diff --git a/src/lib/components/task-queue/worker-insights.svelte b/src/lib/components/task-queue/worker-insights.svelte new file mode 100644 index 0000000000..1f31b2f976 --- /dev/null +++ b/src/lib/components/task-queue/worker-insights.svelte @@ -0,0 +1,11 @@ + + + diff --git a/src/lib/components/task-queue/worker-table-cell.svelte b/src/lib/components/task-queue/worker-table-cell.svelte new file mode 100644 index 0000000000..7338e8add1 --- /dev/null +++ b/src/lib/components/task-queue/worker-table-cell.svelte @@ -0,0 +1,87 @@ + + + + {#if href} + {value} + {:else if children} + {@render children?.()} + {:else} + {value} + {/if} + + diff --git a/src/lib/components/task-queue/worker-table-row.svelte b/src/lib/components/task-queue/worker-table-row.svelte new file mode 100644 index 0000000000..94d43db339 --- /dev/null +++ b/src/lib/components/task-queue/worker-table-row.svelte @@ -0,0 +1,85 @@ + + + + {#each columns as { label } (label)} + {#if label === translate('workers.identity')} + {worker.workerHeartbeat.workerIdentity} + {:else if label === translate('workers.task-queue')} + + {:else if label === translate('workers.instance')} + + {:else if label === translate('workers.status')} + + + {#if isRunning} + + {/if} + {status} + + + {:else if label === translate('workers.sdk')} + + + + {:else if label === translate('workers.workflow-task-slots')} + {worker.workerHeartbeat?.workflowTaskSlotsInfo?.currentUsedSlots ?? 0} / + {worker.workerHeartbeat?.workflowTaskSlotsInfo?.currentAvailableSlots ?? + 0} + {:else if label === translate('workers.activity-task-slots')} + {worker.workerHeartbeat?.activityTaskSlotsInfo?.currentUsedSlots ?? 0} / + {worker.workerHeartbeat?.activityTaskSlotsInfo?.currentAvailableSlots ?? + 0} + {:else if label === translate('workers.nexus-task-slots')} + {worker.workerHeartbeat?.nexusTaskSlotsInfo?.currentUsedSlots ?? 0} / {worker + .workerHeartbeat?.nexusTaskSlotsInfo?.currentAvailableSlots ?? 0} + {/if} + {/each} + diff --git a/src/lib/components/task-queue/worker-table.svelte b/src/lib/components/task-queue/worker-table.svelte new file mode 100644 index 0000000000..0da6de37df --- /dev/null +++ b/src/lib/components/task-queue/worker-table.svelte @@ -0,0 +1,67 @@ + + +{#if !error} + + {translate('workers.workers')} + + + {#each columns as { label }} + {label} + {/each} + + {#each visibleItems as worker} + + {/each} + + + + + +{:else} + {#await getPollers({ queue: taskQueue, namespace }) then workers} + + {/await} +{/if} diff --git a/src/lib/components/task-queue/worker-totals.svelte b/src/lib/components/task-queue/worker-totals.svelte new file mode 100644 index 0000000000..e5e8459f2c --- /dev/null +++ b/src/lib/components/task-queue/worker-totals.svelte @@ -0,0 +1,84 @@ + + +
+ +
+ Active Workers +
+
{totalActive}
+
+ + Running +
+
+ +
+ Inactive Workers +
+
{totalInactive}
+
+ Inactive +
+
+ + +
+ Total Processed Tasks +
+
{totalProcessedTasks}
+
+ {workflowProcessedTasks} workflow · {activityProcessedTasks} activity +
+
+
diff --git a/src/lib/components/workers/worker-search-table.svelte b/src/lib/components/workers/worker-search-table.svelte new file mode 100644 index 0000000000..6a4bf3bb50 --- /dev/null +++ b/src/lib/components/workers/worker-search-table.svelte @@ -0,0 +1,54 @@ + + + + {translate('workers.workers')} + + + {#each columns as { label }} + {label} + {/each} + + {#each visibleItems as worker} + + {/each} + + + + + diff --git a/src/lib/components/workflow/workflow-summary.svelte b/src/lib/components/workflow/workflow-summary.svelte index c09ba4db64..80c9ffaa3a 100644 --- a/src/lib/components/workflow/workflow-summary.svelte +++ b/src/lib/components/workflow/workflow-summary.svelte @@ -15,7 +15,7 @@ } from '$lib/stores/workflow-run'; import { formatDate } from '$lib/utilities/format-date'; import { formatDistanceAbbreviated } from '$lib/utilities/format-time'; - import { routeForWorkers } from '$lib/utilities/route-for'; + import { routeForWorkflowWorkers } from '$lib/utilities/route-for'; $: ({ workflow } = $workflowRun); $: elapsedTime = formatDistanceAbbreviated({ @@ -73,7 +73,7 @@
.copy-or-filter { - @apply absolute bottom-0 right-0 top-0 inline-flex gap-2 px-2; + @apply absolute bottom-0 right-0 top-0 inline-flex gap-1 px-1; } .copy-or-filter-button { - @apply surface-primary relative top-[50%] h-fit translate-y-[-50%] rounded-full p-1 text-primary hover:surface-inverse; + @apply surface-primary relative top-[40%] h-7 w-7 translate-y-[-40%] rounded-full p-0.5 text-primary hover:surface-inverse; } .filtered { diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index 887059633e..f68ca8bc61 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -1,7 +1,9 @@ export const Namespace = 'workers' as const; export const Strings = { + worker: 'Worker', workers: 'Workers', + 'back-to-workers': 'Back to Workers', version: 'Version', versioning: 'Versioning', retirability: 'Retirability', @@ -10,6 +12,16 @@ export const Strings = { 'redirect-rules': 'Redirect Rules', default: 'Default', overall: 'Overall', + identity: 'Identity', + instance: 'Instance', + 'task-queue': 'Task Queue', + status: 'Status', + sdk: 'SDK', + 'workflow-task-slots': 'Workflow Task Slots', + 'activity-task-slots': 'Activity Task Slots', + 'nexus-task-slots': 'Nexus Task Slots', + 'empty-state-title': 'No Workers Found', + 'error-message-fetching': 'An error occurred while fetching workers.', 'compatible-build-ids': 'Compatible Build IDs', 'version-sets': 'Version Sets', 'no-version-sets-found': 'No Version Sets found', diff --git a/src/lib/layouts/workflow-header.svelte b/src/lib/layouts/workflow-header.svelte index d85823287c..acb1208fd3 100644 --- a/src/lib/layouts/workflow-header.svelte +++ b/src/lib/layouts/workflow-header.svelte @@ -36,11 +36,11 @@ routeForPendingActivities, routeForRelationships, routeForUserMetadata, - routeForWorkers, routeForWorkflowMemo, routeForWorkflowQuery, routeForWorkflows, routeForWorkflowSearchAttributes, + routeForWorkflowWorkers, } from '$lib/utilities/route-for'; import { isWorkflowTaskFailure } from '$lib/utilities/workflow-task-failures'; @@ -243,10 +243,10 @@ diff --git a/src/lib/pages/task-queue.svelte b/src/lib/pages/task-queue.svelte index f305a79573..b9297f4008 100644 --- a/src/lib/pages/task-queue.svelte +++ b/src/lib/pages/task-queue.svelte @@ -1,13 +1,9 @@
@@ -15,22 +11,5 @@

{taskQueue}

- - {#await listWorkersForTaskQueue({ queue: taskQueue, namespace })} - - {:then response} - {#if !response?.workersInfo} - {#await getPollers({ queue: taskQueue, namespace }) then workers} - - {/await} - {:else} - {#each response.workersInfo as worker} - - {/each} - {/if} - {:catch _} - {#await getPollers({ queue: taskQueue, namespace }) then workers} - - {/await} - {/await} +
diff --git a/src/lib/pages/workflow-workers.svelte b/src/lib/pages/workflow-workers.svelte index 0f236e256b..0a041979a0 100644 --- a/src/lib/pages/workflow-workers.svelte +++ b/src/lib/pages/workflow-workers.svelte @@ -1,33 +1,5 @@ -
- {#await listWorkersForTaskQueue({ queue: taskQueue, namespace })} - - {:then response} - {#if !response?.workersInfo} - {#await getPollers({ queue: taskQueue, namespace }) then workers} - - {/await} - {:else} - {#each response.workersInfo as worker} - - {/each} - {/if} - {:catch _} - {#await getPollers({ queue: taskQueue, namespace }) then workers} - - {/await} - {/await} -
+ diff --git a/src/lib/services/worker-service.ts b/src/lib/services/worker-service.ts index adc22f4075..90cd9d538a 100644 --- a/src/lib/services/worker-service.ts +++ b/src/lib/services/worker-service.ts @@ -1,17 +1,35 @@ import type { Duration, Timestamp } from '@temporalio/common'; +import type { WorkerStatus } from '$lib/types'; import type { WorkerDeploymentVersion } from '$lib/types/deployments'; import type { NamespaceScopedRequest } from '$lib/types/global'; import { requestFromAPI } from '$lib/utilities/request-from-api'; import { routeForApi } from '$lib/utilities/route-for-api'; +export type WorkerQueryParams = { + workerInstanceKey?: string; + workerIdentity?: string; + hostName?: string; + taskQueue?: string; + deploymentName?: string; + buildId?: string; + sdkName?: string; + sdkVersion?: string; + startTime?: Timestamp; + lastHeartbeatTime?: Timestamp; + status?: WorkerStatus; +}; + export type ListWorkersRequest = NamespaceScopedRequest & { - queue: string; + query?: string; +}; + +export type ListTaskQueueWorkersRequest = NamespaceScopedRequest & { + taskQueue: string; }; export type DescribeWorkerRequest = NamespaceScopedRequest & { - queue: string; - identity: string; + workerInstanceKey: string; }; export type HostInfo = { @@ -51,7 +69,7 @@ export type WorkerHeartbeat = { deploymentVersion: WorkerDeploymentVersion; sdkName: string; sdkVersion: string; - status: string; + status: WorkerStatus; startTime: Timestamp; heartbeatTime: Timestamp; elapsedSinceLastHeartbeat: Duration; @@ -75,33 +93,65 @@ export type WorkerInfo = { export type ListWorkersResponse = { workersInfo: WorkerInfo[]; + nextPageToken?: string; }; -export type DescribeWorkerResponse = { - buildId: string; - capabilities: { - useVersioning: boolean; - activityOnly: boolean; +type PaginatedWorkerListPromise = ( + pageSize: number, + token: string, +) => Promise<{ items: WorkerInfo[]; nextPageToken: string }>; + +export const fetchPaginatedWorkers = async ( + parameters: ListWorkersRequest, + request = fetch, +): Promise => { + return (pageSize = 100, token = '') => { + const route = routeForApi('workers', parameters); + return requestFromAPI(route, { + request, + params: { + maximumPageSize: String(pageSize), + nextPageToken: token, + query: parameters.query, + }, + }).then(({ workersInfo, nextPageToken }) => { + if (!workersInfo) { + throw new Error('No workers info in response'); + } + return { + items: workersInfo, + nextPageToken: nextPageToken ? String(nextPageToken) : '', + }; + }); }; - lastAccessTime: string; - rateLimitPerSecond: number; - taskQueues: { - name: string; - kind: number; - }[]; }; -export async function listWorkersForTaskQueue( - parameters: ListWorkersRequest, +export const fetchPaginatedWorkersForTaskQueue = async ( + parameters: ListTaskQueueWorkersRequest, request = fetch, -): Promise { - const route = routeForApi('workers', parameters); - const response = await requestFromAPI(route, { - request, - params: { query: `TaskQueue="${parameters.queue}"` }, - }); - return response; -} +): Promise => { + return (pageSize = 100, token = '') => { + const route = routeForApi('workers', parameters); + return requestFromAPI(route, { + request, + params: { + query: `TaskQueue="${parameters.taskQueue}"`, + maximumPageSize: String(pageSize), + nextPageToken: token, + }, + }).then(({ workersInfo, nextPageToken }) => { + if (!workersInfo) { + throw new Error('No workers info in response'); + } + return { + items: workersInfo, + nextPageToken: nextPageToken ? String(nextPageToken) : '', + }; + }); + }; +}; + +export type DescribeWorkerResponse = { workerInfo: WorkerInfo }; export async function describeWorker( parameters: DescribeWorkerRequest, @@ -110,6 +160,5 @@ export async function describeWorker( const route = routeForApi('worker', parameters); return await requestFromAPI(route, { request, - params: { identity: parameters.identity }, }); } diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index 5070d8a159..879987dc56 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -181,6 +181,7 @@ export type PendingNexusOperationState = export type CallbackState = temporal.api.enums.v1.CallbackState; export type VersioningBehavior = temporal.api.enums.v1.VersioningBehavior; export type EventType = temporal.api.enums.v1.EventType; +export type WorkerStatus = temporal.api.enums.v1.WorkerStatus; // temporal.api.enums.v1.ResetReapplyExcludeType export enum ResetReapplyExcludeType { diff --git a/src/lib/utilities/route-for.test.ts b/src/lib/utilities/route-for.test.ts index 8204b1b8d3..f77ee3a2a0 100644 --- a/src/lib/utilities/route-for.test.ts +++ b/src/lib/utilities/route-for.test.ts @@ -19,11 +19,11 @@ import { routeForScheduleCreate, routeForSchedules, routeForTaskQueue, - routeForWorkers, routeForWorkflow, routeForWorkflowQuery, routeForWorkflows, routeForWorkflowsWithQuery, + routeForWorkflowWorkers, } from './route-for'; describe('routeFor', () => { @@ -117,7 +117,7 @@ describe('routeFor', () => { }); it('should route to "workers"', () => { - const path = routeForWorkers({ + const path = routeForWorkflowWorkers({ namespace: 'default', workflow: 'abc', run: 'def', diff --git a/src/lib/utilities/route-for.ts b/src/lib/utilities/route-for.ts index bcfd0c98ae..dd1c9f7552 100644 --- a/src/lib/utilities/route-for.ts +++ b/src/lib/utilities/route-for.ts @@ -202,7 +202,13 @@ export const routeForEventHistoryEvent = ({ return `${routeForWorkflow(parameters)}/history/events/${eventId || requestId}`; }; -export const routeForWorkers = (parameters: WorkflowParameters): string => { +export const routeForWorkers = (parameters: NamespaceParameter): string => { + return `${routeForNamespace({ namespace: parameters.namespace })}/workers`; +}; + +export const routeForWorkflowWorkers = ( + parameters: WorkflowParameters, +): string => { return `${routeForWorkflow(parameters)}/workers`; }; @@ -214,6 +220,20 @@ export const routeForWorkerDeployments = ({ return resolve('/namespaces/[namespace]/worker-deployments', { namespace }); }; +export const routeForWorkerInstance = ({ + namespace, + workerInstanceKey, +}: { + namespace: string; + workerInstanceKey: string; +}) => { + const workerInstanceKeyEncoded = encodeURIForSvelte(workerInstanceKey); + return resolve('/namespaces/[namespace]/workers/[workerInstanceKey]', { + namespace, + workerInstanceKey: workerInstanceKeyEncoded, + }); +}; + export const routeForWorkerDeployment = ({ namespace, deployment, diff --git a/src/lib/utilities/screaming-enums.ts b/src/lib/utilities/screaming-enums.ts index 48b442f1fa..db9a4e3ac3 100644 --- a/src/lib/utilities/screaming-enums.ts +++ b/src/lib/utilities/screaming-enums.ts @@ -3,6 +3,7 @@ import type { CallbackState, NamespaceState, PendingNexusOperationState, + WorkerStatus, WorkflowExecutionStatus, } from '$lib/types'; import type { BatchOperationState, BatchOperationType } from '$lib/types/batch'; @@ -98,3 +99,8 @@ export const toCallbackStateReadable = ( if (!state) return state; return fromScreamingEnum(state, 'CallbackState'); }; + +export const toWorkerStatusReadable = (state?: WorkerStatus): string => { + if (!state) return 'Unknown'; + return fromScreamingEnum(state, 'WorkerStatus') as unknown as string; +}; diff --git a/src/lib/utilities/update-query-parameters.ts b/src/lib/utilities/update-query-parameters.ts index 4d3b47512f..56b44e64d7 100644 --- a/src/lib/utilities/update-query-parameters.ts +++ b/src/lib/utilities/update-query-parameters.ts @@ -31,6 +31,7 @@ export const updateQueryParameters = async ({ const params = {}; let replaced = false; + console.log('Updating query parameter:'); url.searchParams.forEach((value, key) => { if (key !== parameter) { params[key] = value; diff --git a/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte new file mode 100644 index 0000000000..8a7bb98b8a --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte @@ -0,0 +1,17 @@ + + + +
+

+ {translate('workers.workers')} +

+
+ diff --git a/src/routes/(app)/namespaces/[namespace]/workers/[instance]/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workers/[instance]/+page.svelte new file mode 100644 index 0000000000..08b0f7b86d --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workers/[instance]/+page.svelte @@ -0,0 +1,34 @@ + + + + +
+
+
+ + {translate('workers.back-to-workers')} + +
+
+
+{#await describeWorker({ namespace, workerInstanceKey }) then data} + +{:catch error} +

Error loading worker info: {error.message}

+{/await} From 1eadb997a313994e86f79ff0f609769061f935ab Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 8 Dec 2025 16:10:40 -0600 Subject: [PATCH 05/40] Make filterable a prop --- .../task-queue/worker-table-cell.svelte | 11 +++- .../task-queue/worker-table-row.svelte | 22 ++++++-- .../components/task-queue/worker-table.svelte | 3 +- .../workers/worker-search-table.svelte | 55 ++++++++++--------- src/lib/i18n/locales/en/workers.ts | 7 ++- 5 files changed, 62 insertions(+), 36 deletions(-) diff --git a/src/lib/components/task-queue/worker-table-cell.svelte b/src/lib/components/task-queue/worker-table-cell.svelte index 7338e8add1..53ef0c00c6 100644 --- a/src/lib/components/task-queue/worker-table-cell.svelte +++ b/src/lib/components/task-queue/worker-table-cell.svelte @@ -11,11 +11,17 @@ type Props = { attribute?: string; value: string; + filterable?: boolean; href?: string; children?: Snippet; }; - let { attribute, value, href, children }: Props = $props(); - + let { + attribute, + value, + filterable = false, + href, + children, + }: Props = $props(); const query = $derived(page.url.searchParams.get('query') || ''); const onRowFilterClick = () => { @@ -82,6 +88,7 @@ show={filterOrCopyButtonsVisible} content={value} onFilter={onRowFilterClick} + {filterable} filtered={query.includes(`${attribute}=`)} /> diff --git a/src/lib/components/task-queue/worker-table-row.svelte b/src/lib/components/task-queue/worker-table-row.svelte index 94d43db339..bb8307191b 100644 --- a/src/lib/components/task-queue/worker-table-row.svelte +++ b/src/lib/components/task-queue/worker-table-row.svelte @@ -13,9 +13,10 @@ worker: WorkerInfo; namespace: string; columns: { label: string }[]; + filterable?: boolean; }; - let { columns, worker, namespace }: Props = $props(); + let { columns, worker, namespace, filterable = false }: Props = $props(); const status = $derived( toWorkerStatusReadable(worker.workerHeartbeat.status), ); @@ -25,11 +26,22 @@ {#each columns as { label } (label)} {#if label === translate('workers.identity')} - {worker.workerHeartbeat.workerIdentity} + {:else if label === translate('workers.task-queue')} + {:else if label === translate('workers.host-name')} + {:else if label === translate('workers.instance')} {:else if label === translate('workers.status')} - + - + {:else if label === translate('workers.sdk')} fetchPaginatedWorkers({ namespace, query })); - - {translate('workers.workers')} - - - {#each columns as { label }} - {label} + {translate('workers.workers')} + + + {#each columns as { label }} + {label} + {/each} + + {#each visibleItems as worker} + {/each} - - {#each visibleItems as worker} - - {/each} - - - - - + + + + + +{/key} diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index f68ca8bc61..aea191072e 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -12,14 +12,15 @@ export const Strings = { 'redirect-rules': 'Redirect Rules', default: 'Default', overall: 'Overall', + 'host-name': 'Host Name', identity: 'Identity', instance: 'Instance', 'task-queue': 'Task Queue', status: 'Status', sdk: 'SDK', - 'workflow-task-slots': 'Workflow Task Slots', - 'activity-task-slots': 'Activity Task Slots', - 'nexus-task-slots': 'Nexus Task Slots', + 'workflow-task-slots': 'Workflow Slots', + 'activity-task-slots': 'Activity Slots', + 'nexus-task-slots': 'Nexus Slots', 'empty-state-title': 'No Workers Found', 'error-message-fetching': 'An error occurred while fetching workers.', 'compatible-build-ids': 'Compatible Build IDs', From 567aa2d5b748b5120a075b9b56a6133da25ecce9 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 12 Jan 2026 12:04:41 -0600 Subject: [PATCH 06/40] Refactor workers page to designs --- .../components/task-queue/worker-info.svelte | 472 +++++++++--------- .../task-queue/worker-table-row.svelte | 14 +- .../components/workers/worker-status.svelte | 51 ++ src/lib/holocene/badge.svelte | 1 + src/lib/i18n/locales/en/workers.ts | 1 + src/lib/utilities/route-for-api.ts | 1 + src/lib/utilities/screaming-enums.ts | 17 +- .../workers/[instance]/+page.svelte | 4 +- .../[workflow]/[run]/workers/+page.svelte | 10 +- 9 files changed, 302 insertions(+), 269 deletions(-) create mode 100644 src/lib/components/workers/worker-status.svelte diff --git a/src/lib/components/task-queue/worker-info.svelte b/src/lib/components/task-queue/worker-info.svelte index 80060d218d..8e0a5c77cd 100644 --- a/src/lib/components/task-queue/worker-info.svelte +++ b/src/lib/components/task-queue/worker-info.svelte @@ -11,10 +11,12 @@ type WorkerPollerInfo, type WorkerSlotsInfo, } from '$lib/services/worker-service'; + import { formatDurationAbbreviated } from '$lib/utilities/format-time'; import { routeForTaskQueue } from '$lib/utilities/route-for'; import { toWorkerStatusReadable } from '$lib/utilities/screaming-enums'; import SdkLogo from '../lines-and-dots/sdk-logo.svelte'; + import WorkerStatus from '../workers/worker-status.svelte'; type Props = { worker: WorkerInfo; @@ -27,226 +29,197 @@ const status = $derived(toWorkerStatusReadable(heartbeat.status)); const totalStickyCacheHit = $derived(heartbeat?.totalStickyCacheHit ?? 0); + const totalStickyCacheMiss = $derived(heartbeat?.totalStickyCacheMiss ?? 0); const currentStickyCacheSize = $derived( heartbeat?.currentStickyCacheSize ?? 0, ); - const isRunning = $derived(status === 'Running'); + + const cacheHitRate = $derived(() => { + const total = totalStickyCacheHit + totalStickyCacheMiss; + if (total === 0) return 0; + return ((totalStickyCacheHit / total) * 100).toFixed(1); + });
-
-
- -
-
-
- - {#if isRunning} - - {/if} - {status} - -

{heartbeat.workerIdentity}

-
-
-
+
+
+ +

{heartbeat.workerIdentity}

+
-
-
-
- Task Queue -
- - {heartbeat.taskQueue} - -
-
-
- SDK -
- -
-
+
+
+ Task Queue + + {heartbeat.taskQueue} + +
+
+ Start + +
+
+ Last Heartbeat
-

- Task Slots -

-
- {@render slotCard( - 'Workflow Tasks', - heartbeat.workflowTaskSlotsInfo, - )} - {@render slotCard( - 'Activity Tasks', - heartbeat.activityTaskSlotsInfo, - )} - {@render slotCard('Nexus Tasks', heartbeat.nexusTaskSlotsInfo)} - {@render slotCard( - 'Local Activities', - heartbeat.localActivitySlotsInfo, - )} -
+ + + {formatDurationAbbreviated( + String(heartbeat.elapsedSinceLastHeartbeat), + )} ago +
+
+
+ SDK + +
+
+
-
-

- Pollers -

-
- {@render pollerCard( - 'Workflow Poller', - heartbeat.workflowPollerInfo, - )} - {@render pollerCard( - 'Workflow Sticky Poller', - heartbeat.workflowStickyPollerInfo, - )} - {@render pollerCard( - 'Activity Poller', - heartbeat.activityPollerInfo, - )} - {@render pollerCard('Nexus Poller', heartbeat.nexusPollerInfo)} -
-
-
+
+
+ {@render taskSlotCard( + 'Workflow', + heartbeat.workflowTaskSlotsInfo, + heartbeat.workflowPollerInfo, + )} + {@render taskSlotCard( + 'Activity', + heartbeat.activityTaskSlotsInfo, + heartbeat.activityPollerInfo, + )} + {@render taskSlotCard( + 'Nexus Tasks', + heartbeat.nexusTaskSlotsInfo, + heartbeat.nexusPollerInfo, + )} + {@render taskSlotCard( + 'Local Activities', + heartbeat.localActivitySlotsInfo, + null, + )}
-
- {@render timestamps()} +
{@render hostInfo()} - {@render deploymentInfo()} - {@render usage()} - {@render cache()} + {@render hostUsage()} + {@render workflowCache()} + {@render diagnostics()}
-{#snippet slotCard(title: string, slots: WorkerSlotsInfo)} - -
- {title} - {slots.slotSupplierKind} +{#snippet taskSlotCard( + title: string, + slots: WorkerSlotsInfo, + poller: WorkerPollerInfo | null, +)} + +
+

{title}

+ {slots.slotSupplierKind}
-
-
- - {slots.currentUsedSlots ?? 0} - - Used -
+ +
- {slots.currentAvailableSlots ?? 0} - Available +
Slots
+
+

+ {slots.currentUsedSlots ?? 0} +

+

+ {#if slots.currentAvailableSlots} + {slots.currentAvailableSlots - slots.currentUsedSlots || 0} + {:else} + - + {/if} +

+
+
+

Used

+

+ {#if slots.currentAvailableSlots} + Available out of {slots.currentAvailableSlots} + {:else}None Available + {/if} +

+
+
- - {slots.totalProcessedTasks ?? 0} +
Tasks Processed
+ + {(slots.totalProcessedTasks ?? 0).toLocaleString()} - Processed
-
- {#if slots.currentAvailableSlots} -
- {#each new Array(slots.currentAvailableSlots) as _, i} -
- {/each} -
- {/if} - -{/snippet} - -{#snippet pollerCard(title: string, poller: WorkerPollerInfo)} - -
- {title} - - {poller.currentPollers ?? 0} - -
-
- {#if poller.lastSuccessfulPollTime} - Last poll: - {:else} - No activity - {/if} -
-
-{/snippet} -{#snippet timestamps()} - -
-
-
Start Time
- -
-
-
Last Heartbeat
- -
- ~{heartbeat.elapsedSinceLastHeartbeat} ago + {#if poller} +
+
+ Poller + + {poller.isAutoscaling ? 'Autoscaling' : 'Manual'} + +
+ + {poller.currentPollers ?? 0} + +
+ {#if poller.lastSuccessfulPollTime} + Last Poll + {:else} + No Activity + {/if} +
-
+ {/if}
{/snippet} {#snippet hostInfo()} -
- -

Host Information

-
-
-
- Hostname - {heartbeat.hostInfo.hostName} +

Host Info

+
+
+ Host Name + + {heartbeat.hostInfo.hostName} +
-
+
Process ID - {heartbeat.hostInfo.processId} + {heartbeat.hostInfo.processId}
-
+
Instance Key - + {heartbeat.workerInstanceKey}
-
+
Worker Grouping Key - + {heartbeat.hostInfo.workerGroupingKey}
@@ -254,85 +227,92 @@ {/snippet} -{#snippet deploymentInfo()} - {#if heartbeat.deploymentVersion?.deploymentName} - -
- -

Deployment Version

+{#snippet hostUsage()} + +
+

Host Usage

+
+
+
+
+ + + CPU Usage + + {heartbeat.hostInfo.currentHostCpuUsage.toFixed(0)}% +
+
+
+
-
-
-
Deployment
- - {heartbeat.deploymentVersion.deploymentName} - +
+
+ + + Memory Usage + + {heartbeat.hostInfo.currentHostMemUsage.toFixed(0)}%
-
- Build ID - {heartbeat.deploymentVersion.buildId} +
+
- - {/if} +
+ {/snippet} -{#snippet usage()} +{#snippet workflowCache()} -
- -

Host Usage

-
+

Workflow Cache

- -
-

- {heartbeat.hostInfo.currentHostCpuUsage.toFixed(1)}% -

-

- CPU Usage -

-
-
- -
-

- {heartbeat.hostInfo.currentHostMemUsage.toFixed(1)}% -

-

- Memory Usage -

-
-
+
+ + {currentStickyCacheSize.toLocaleString()} + +
Cache size
+
+
+ + {cacheHitRate()}% + +
Cache hits
+
+
+
+ + {totalStickyCacheHit.toLocaleString()} + +
Active thread count
{/snippet} -{#snippet cache()} +{#snippet diagnostics()} -
- -

Sticky Cache

-
+

Diagnostics

- -
-

- {totalStickyCacheHit} -

-

- Cache Hits -

-
-
- -
-

{currentStickyCacheSize}

-

- Cache Size -

-
-
+
+ + {cacheHitRate()}% + +
Poll success rate
+
+
+ None +
Rate limit
+
{/snippet} diff --git a/src/lib/components/task-queue/worker-table-row.svelte b/src/lib/components/task-queue/worker-table-row.svelte index bb8307191b..05f44cfe9a 100644 --- a/src/lib/components/task-queue/worker-table-row.svelte +++ b/src/lib/components/task-queue/worker-table-row.svelte @@ -1,11 +1,11 @@ @@ -55,16 +54,7 @@ /> {:else if label === translate('workers.status')} - - {#if isRunning} - - {/if} - {status} - + {:else if label === translate('workers.sdk')} + import { cva } from 'class-variance-authority'; + + import HeartBeat from '$lib/components/heart-beat-indicator.svelte'; + import type { ReadableWorkerStatus } from '$lib/utilities/screaming-enums'; + + interface Props { + delay?: number; + status?: ReadableWorkerStatus; + 'test-id'?: string; + } + + let { delay = 0, status = 'Running', 'test-id': testId }: Props = $props(); + + const label: Record = { + Unspecified: 'Unspecified', + Running: 'Running', + 'Shutting Down': 'Shutting Down', + Shutdown: 'Shutdown', + }; + + const workerStatus = cva( + [ + 'flex items-center rounded-sm px-1 py-0.5 h-5 whitespace-nowrap text-black gap-1 font-medium', + ], + { + variants: { + status: { + Unspecified: 'bg-slate-100', + Running: 'bg-blue-300', + 'Shutting Down': 'bg-yellow-200', + Shutdown: 'bg-red-200', + }, + }, + }, + ); + + const isRunning = $derived(status === 'Running'); + + +
+ + {label[status]} + {#if isRunning} + + {/if} + +
diff --git a/src/lib/holocene/badge.svelte b/src/lib/holocene/badge.svelte index 949160f488..11f8744a52 100644 --- a/src/lib/holocene/badge.svelte +++ b/src/lib/holocene/badge.svelte @@ -12,6 +12,7 @@ danger: 'bg-red-200', count: 'h-6 w-6 min-w-max rounded-full bg-blue-300', subtle: 'surface-subtle dark:text-white font-normal select-all', + ghost: 'surface-primary text-primary border border-subtle', }; const types = cva( diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index aea191072e..993ec8e881 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -3,6 +3,7 @@ export const Namespace = 'workers' as const; export const Strings = { worker: 'Worker', workers: 'Workers', + 'view-all-workers': 'View All Workers', 'back-to-workers': 'Back to Workers', version: 'Version', versioning: 'Versioning', diff --git a/src/lib/utilities/route-for-api.ts b/src/lib/utilities/route-for-api.ts index 20e7d8fd03..b3140034f1 100644 --- a/src/lib/utilities/route-for-api.ts +++ b/src/lib/utilities/route-for-api.ts @@ -117,6 +117,7 @@ const encode = ( endpointId: '', deploymentName: '', version: '', + workerInstanceKey: '', }, ); }; diff --git a/src/lib/utilities/screaming-enums.ts b/src/lib/utilities/screaming-enums.ts index db9a4e3ac3..e3e0b6549c 100644 --- a/src/lib/utilities/screaming-enums.ts +++ b/src/lib/utilities/screaming-enums.ts @@ -100,7 +100,18 @@ export const toCallbackStateReadable = ( return fromScreamingEnum(state, 'CallbackState'); }; -export const toWorkerStatusReadable = (state?: WorkerStatus): string => { - if (!state) return 'Unknown'; - return fromScreamingEnum(state, 'WorkerStatus') as unknown as string; +export type ReadableWorkerStatus = + | 'Unspecified' + | 'Running' + | 'Shutting Down' + | 'Shutdown'; + +export const toWorkerStatusReadable = ( + state?: WorkerStatus, +): ReadableWorkerStatus => { + if (!state) return 'Unspecified'; + return fromScreamingEnum( + state, + 'WorkerStatus', + ) as unknown as ReadableWorkerStatus; }; diff --git a/src/routes/(app)/namespaces/[namespace]/workers/[instance]/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workers/[instance]/+page.svelte index 08b0f7b86d..d3eaa0ac75 100644 --- a/src/routes/(app)/namespaces/[namespace]/workers/[instance]/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workers/[instance]/+page.svelte @@ -19,10 +19,10 @@
- {translate('workers.back-to-workers')} + {translate('workers.view-all-workers')}
diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte index 44a0dc5f6a..2464a7bb63 100644 --- a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte @@ -1,17 +1,15 @@ - + From 48ef22af1796f1f4960a4d1e9373070bcc0f2a47 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 5 Feb 2026 14:20:46 -0600 Subject: [PATCH 07/40] New worker nav items --- .gitignore | 3 +- .serena/project.yml | 61 ++++++++++++++++--- src/lib/i18n/locales/en/workers.ts | 1 + src/lib/pages/deployments.svelte | 20 ++++-- src/routes/(app)/+layout.svelte | 34 +++++++---- .../worker-deployments/+page.server.ts | 11 ++++ .../worker-deployments/+page.svelte | 17 ++++-- .../[namespace]/workers/+page.svelte | 55 +++++++++++++++-- 8 files changed, 165 insertions(+), 37 deletions(-) create mode 100644 src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.server.ts diff --git a/.gitignore b/.gitignore index 3bc16487b4..f53556b113 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ server/ui/assets go.work go.work.sum .claude/* -!.claude/skills/ \ No newline at end of file +!.claude/skills/ +.omc diff --git a/.serena/project.yml b/.serena/project.yml index e827832722..fd03e14574 100644 --- a/.serena/project.yml +++ b/.serena/project.yml @@ -1,9 +1,3 @@ -# language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby) -# * For C, use cpp -# * For JavaScript, use typescript -# Special requirements: -# * csharp: Requires the presence of a .sln file in the project folder. -language: typescript # whether to use the project's gitignore file to ignore files # Added on 2025-04-07 @@ -63,5 +57,58 @@ excluded_tools: [] # initial prompt for the project. It will always be given to the LLM upon activating the project # (contrary to the memories, which are loaded on demand). initial_prompt: '' - +# the name by which the project can be referenced within Serena project_name: 'ui' + +# list of mode names to that are always to be included in the set of active modes +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this setting overrides the global configuration. +# Set this to [] to disable base modes for this project. +# Set this to a list of mode names to always include the respective modes for this project. +base_modes: + +# list of mode names that are to be activated by default. +# The full set of modes to be activated is base_modes + default_modes. +# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. +# Otherwise, this overrides the setting from the global configuration (serena_config.yml). +# This setting can, in turn, be overridden by CLI parameters (--mode). +default_modes: + +# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default) +included_optional_tools: [] + +# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. +# This cannot be combined with non-empty excluded_tools or included_optional_tools. +fixed_tools: [] + +# the encoding used by text files in the project +# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings +encoding: utf-8 + + +# list of languages for which language servers are started; choose from: +# al bash clojure cpp csharp +# csharp_omnisharp dart elixir elm erlang +# fortran fsharp go groovy haskell +# java julia kotlin lua markdown +# matlab nix pascal perl php +# powershell python python_jedi r rego +# ruby ruby_solargraph rust scala swift +# terraform toml typescript typescript_vts vue +# yaml zig +# (This list may be outdated. For the current list, see values of Language enum here: +# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py +# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) +# Note: +# - For C, use cpp +# - For JavaScript, use typescript +# - For Free Pascal/Lazarus, use pascal +# Special requirements: +# Some languages require additional setup/installations. +# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers +# When using multiple languages, the first language server that supports a given file will be used for that file. +# The first language is the default language and the respective language server will be used as a fallback. +# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. +languages: +- typescript diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index 993ec8e881..87ef81221b 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -3,6 +3,7 @@ export const Namespace = 'workers' as const; export const Strings = { worker: 'Worker', workers: 'Workers', + 'worker-views': 'Worker Views', 'view-all-workers': 'View All Workers', 'back-to-workers': 'Back to Workers', version: 'Version', diff --git a/src/lib/pages/deployments.svelte b/src/lib/pages/deployments.svelte index bbb6f13b19..4756524e07 100644 --- a/src/lib/pages/deployments.svelte +++ b/src/lib/pages/deployments.svelte @@ -11,6 +11,12 @@ import { fetchPaginatedDeployments } from '$lib/services/deployments-service'; import type { APIErrorResponse } from '$lib/utilities/request-from-api'; + interface Props { + hideHeader?: boolean; + } + + let { hideHeader = false }: Props = $props(); + let error = $state(''); const namespace = $derived(page.params.namespace); @@ -39,12 +45,14 @@ ]; -
-

- {translate('deployments.worker-deployments')} -

- Public Preview -
+{#if !hideHeader} +
+

+ {translate('deployments.worker-deployments')} +

+ Public Preview +
+{/if} {#key [namespace]} path.includes(batchOperationsRoute), }, { - href: workerDeploymentsRoute, + href: workersRoute, icon: 'merge', label: translate('deployments.deployments'), tooltip: translate('deployments.worker-deployments'), - isActive: (path) => path.includes(workerDeploymentsRoute), + isActive: (path) => path.includes(workersRoute), }, { href: nexusRoute, @@ -182,14 +182,14 @@ workflowsRoute, schedulesRoute, batchOperationsRoute, - workerDeploymentsRoute, + workersRoute, archivalRoute, } = $derived(routes); let showNamespacePicker = $derived( [ workflowsRoute, schedulesRoute, - workerDeploymentsRoute, + workersRoute, batchOperationsRoute, archivalRoute, ].some((route) => page.url.href.includes(route)), @@ -205,10 +205,6 @@ subPath: 'batch-operations', fullRoute: routeForBatchOperations({ namespace }), }, - { - subPath: 'worker-deployments', - fullRoute: routeForWorkerDeployments({ namespace }), - }, ]; for (const { subPath, fullRoute } of namespacePages) { @@ -217,6 +213,18 @@ } } + // Handle workers page with view query param preservation + if (page.url.pathname.endsWith('workers')) { + const view = page.url.searchParams.get('view'); + const base = routeForWorkers({ namespace }); + return view ? `${base}?view=${view}` : base; + } + + // Handle legacy worker-deployments path during redirect + if (page.url.pathname.includes('worker-deployments')) { + return `${routeForWorkers({ namespace })}?view=deployments`; + } + return routeForWorkflows({ namespace }); } diff --git a/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.server.ts b/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.server.ts new file mode 100644 index 0000000000..c6f373a121 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.server.ts @@ -0,0 +1,11 @@ +import type { RequestEvent } from '@sveltejs/kit'; +import { redirect } from '@sveltejs/kit'; + +export const load = ({ params, url }: RequestEvent) => { + const { namespace } = params; + + const queryString = url.searchParams.toString(); + const redirectUrl = `/namespaces/${namespace}/workers?view=deployments${queryString ? `&${queryString}` : ''}`; + + redirect(301, redirectUrl); +}; diff --git a/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte b/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte index da3d7ee384..1d0ed6446d 100644 --- a/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte @@ -1,9 +1,16 @@ - - +

Redirecting...

diff --git a/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte index 8a7bb98b8a..27c8272cd4 100644 --- a/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte @@ -3,15 +3,60 @@ import PageTitle from '$lib/components/page-title.svelte'; import WorkerSearchTable from '$lib/components/workers/worker-search-table.svelte'; + import Badge from '$lib/holocene/badge.svelte'; + import TabList from '$lib/holocene/tab/tab-list.svelte'; + import Tab from '$lib/holocene/tab/tab.svelte'; + import Tabs from '$lib/holocene/tab/tabs.svelte'; import { translate } from '$lib/i18n/translate'; + import WorkerDeployments from '$lib/pages/deployments.svelte'; + import { routeForWorkers } from '$lib/utilities/route-for'; const { namespace } = $derived(page.params); + const view = $derived(page.url.searchParams.get('view')); + const isDeploymentsView = $derived(view === 'deployments'); + + const workersHref = $derived(routeForWorkers({ namespace })); + const deploymentsHref = $derived( + `${routeForWorkers({ namespace })}?view=deployments`, + ); + + const pageTitle = $derived( + isDeploymentsView + ? translate('deployments.worker-deployments') + : translate('workers.workers'), + ); - +
-

- {translate('workers.workers')} -

+
+

+ {pageTitle} +

+ {#if isDeploymentsView} + Public Preview + {/if} +
+ + + + + +
- + +{#if isDeploymentsView} + +{:else} + +{/if} From 89985d6aab6e3c003cc3177280b758eec0c7f47b Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 5 Feb 2026 14:27:17 -0600 Subject: [PATCH 08/40] Dont use url params --- src/routes/(app)/+layout.svelte | 11 ++-- .../worker-deployments/+page.server.ts | 11 ---- .../worker-deployments/+page.svelte | 52 +++++++++++++++---- .../[namespace]/workers/+page.svelte | 32 ++++-------- 4 files changed, 57 insertions(+), 49 deletions(-) delete mode 100644 src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.server.ts diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 5c9b2e75dc..0d419c58d8 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -32,6 +32,7 @@ routeForNamespaces, routeForNexus, routeForSchedules, + routeForWorkerDeployments, routeForWorkers, routeForWorkflows, } from '$lib/utilities/route-for'; @@ -213,16 +214,14 @@ } } - // Handle workers page with view query param preservation + // Handle workers page if (page.url.pathname.endsWith('workers')) { - const view = page.url.searchParams.get('view'); - const base = routeForWorkers({ namespace }); - return view ? `${base}?view=${view}` : base; + return routeForWorkers({ namespace }); } - // Handle legacy worker-deployments path during redirect + // Handle worker-deployments page if (page.url.pathname.includes('worker-deployments')) { - return `${routeForWorkers({ namespace })}?view=deployments`; + return routeForWorkerDeployments({ namespace }); } return routeForWorkflows({ namespace }); diff --git a/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.server.ts b/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.server.ts deleted file mode 100644 index c6f373a121..0000000000 --- a/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.server.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { RequestEvent } from '@sveltejs/kit'; -import { redirect } from '@sveltejs/kit'; - -export const load = ({ params, url }: RequestEvent) => { - const { namespace } = params; - - const queryString = url.searchParams.toString(); - const redirectUrl = `/namespaces/${namespace}/workers?view=deployments${queryString ? `&${queryString}` : ''}`; - - redirect(301, redirectUrl); -}; diff --git a/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte b/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte index 1d0ed6446d..85ecfbbb0e 100644 --- a/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte @@ -1,16 +1,50 @@ -

Redirecting...

+ +
+
+

+ {pageTitle} +

+ Public Preview +
+ + + + + + +
+ + diff --git a/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte index 27c8272cd4..2f34f7efbc 100644 --- a/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte @@ -3,28 +3,21 @@ import PageTitle from '$lib/components/page-title.svelte'; import WorkerSearchTable from '$lib/components/workers/worker-search-table.svelte'; - import Badge from '$lib/holocene/badge.svelte'; import TabList from '$lib/holocene/tab/tab-list.svelte'; import Tab from '$lib/holocene/tab/tab.svelte'; import Tabs from '$lib/holocene/tab/tabs.svelte'; import { translate } from '$lib/i18n/translate'; - import WorkerDeployments from '$lib/pages/deployments.svelte'; - import { routeForWorkers } from '$lib/utilities/route-for'; + import { + routeForWorkerDeployments, + routeForWorkers, + } from '$lib/utilities/route-for'; const { namespace } = $derived(page.params); - const view = $derived(page.url.searchParams.get('view')); - const isDeploymentsView = $derived(view === 'deployments'); const workersHref = $derived(routeForWorkers({ namespace })); - const deploymentsHref = $derived( - `${routeForWorkers({ namespace })}?view=deployments`, - ); + const deploymentsHref = $derived(routeForWorkerDeployments({ namespace })); - const pageTitle = $derived( - isDeploymentsView - ? translate('deployments.worker-deployments') - : translate('workers.workers'), - ); + const pageTitle = $derived(translate('workers.workers')); @@ -33,9 +26,6 @@

{pageTitle}

- {#if isDeploymentsView} - Public Preview - {/if}
@@ -43,20 +33,16 @@ label={translate('workers.workers')} id="workers-tab" href={workersHref} - active={!isDeploymentsView} + active={true} /> -{#if isDeploymentsView} - -{:else} - -{/if} + From 722bd8125bda9ae43367428ff3751094e14e7954 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Fri, 20 Feb 2026 17:38:33 -0700 Subject: [PATCH 09/40] Clean up types and remove Shutdown status --- .gitignore | 3 - package.json | 14 +- pnpm-lock.yaml | 236 ++++++++++++------ .../components/task-queue/worker-info.svelte | 12 +- .../task-queue/worker-table-cell.svelte | 23 +- .../task-queue/worker-table-row.svelte | 2 +- .../task-queue/worker-totals.svelte | 2 +- .../workers/worker-search-table.svelte | 8 +- .../components/workers/worker-status.svelte | 2 - src/lib/services/worker-service.ts | 107 +------- src/lib/types/api.ts | 5 - src/lib/types/index.ts | 22 ++ src/lib/utilities/route-for-api.ts | 4 +- src/lib/utilities/screaming-enums.ts | 6 +- src/lib/utilities/update-query-parameters.ts | 1 - src/routes/(app)/+layout.svelte | 10 - .../worker-deployments/+page.svelte | 9 +- .../[namespace]/workers/+page.svelte | 6 +- 18 files changed, 222 insertions(+), 250 deletions(-) diff --git a/.gitignore b/.gitignore index 311d881a10..751271e0cf 100644 --- a/.gitignore +++ b/.gitignore @@ -27,8 +27,5 @@ go.work.sum .claude/settings.local.json .claude/* !.claude/skills/ -<<<<<<< HEAD .omc -======= .sisyphus/* ->>>>>>> main diff --git a/package.json b/package.json index b9f88a1bd5..69108d8bc3 100644 --- a/package.json +++ b/package.json @@ -145,13 +145,13 @@ "@sveltejs/adapter-vercel": "^4.0.0", "@sveltejs/kit": "2.49.5", "@sveltejs/vite-plugin-svelte": "^5.0.3", - "@temporalio/activity": "1.14.1", - "@temporalio/client": "1.14.1", - "@temporalio/common": "1.14.1", - "@temporalio/proto": "1.14.1", - "@temporalio/testing": "1.14.1", - "@temporalio/worker": "1.14.1", - "@temporalio/workflow": "1.14.1", + "@temporalio/activity": "1.15.0", + "@temporalio/client": "1.15.0", + "@temporalio/common": "1.15.0", + "@temporalio/proto": "1.15.0", + "@temporalio/testing": "1.15.0", + "@temporalio/worker": "1.15.0", + "@temporalio/workflow": "1.15.0", "@types/base-64": "^1.0.0", "@types/cors": "^2.8.13", "@types/express": "^4.17.17", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba7a94adf1..67cb495cf9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -209,26 +209,26 @@ importers: specifier: ^5.0.3 version: 5.1.1(svelte@5.46.4)(vite@6.4.1(@types/node@18.19.130)(jiti@1.21.7)(terser@5.44.1)(yaml@2.8.2)) '@temporalio/activity': - specifier: 1.14.1 - version: 1.14.1 + specifier: 1.15.0 + version: 1.15.0 '@temporalio/client': - specifier: 1.14.1 - version: 1.14.1 + specifier: 1.15.0 + version: 1.15.0 '@temporalio/common': - specifier: 1.14.1 - version: 1.14.1 + specifier: 1.15.0 + version: 1.15.0 '@temporalio/proto': - specifier: 1.14.1 - version: 1.14.1 + specifier: 1.15.0 + version: 1.15.0 '@temporalio/testing': - specifier: 1.14.1 - version: 1.14.1(esbuild@0.25.0) + specifier: 1.15.0 + version: 1.15.0(esbuild@0.25.0) '@temporalio/worker': - specifier: 1.14.1 - version: 1.14.1(esbuild@0.25.0) + specifier: 1.15.0 + version: 1.15.0(esbuild@0.25.0) '@temporalio/workflow': - specifier: 1.14.1 - version: 1.14.1 + specifier: 1.15.0 + version: 1.15.0 '@types/base-64': specifier: ^1.0.0 version: 1.0.2 @@ -1987,41 +1987,41 @@ packages: '@swc/types@0.1.25': resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} - '@temporalio/activity@1.14.1': - resolution: {integrity: sha512-wG2fTNgomhcKOzPY7mqhKqe8scawm4BvUYdgX1HJouHmVNRgtZurf2xQWJZQOTxWrsXfdoYqzohZLzxlNtcC5A==} - engines: {node: '>= 18.0.0'} + '@temporalio/activity@1.15.0': + resolution: {integrity: sha512-kKEIrHMTsANiEpDVZd+v3OT6kU20UtcvAK7iibJNzQnoZUGC7XnqdKQlBuGYBxgpJzD9ppGgskXRczW+XU5Kng==} + engines: {node: '>= 20.0.0'} - '@temporalio/client@1.14.1': - resolution: {integrity: sha512-AfWSA0LYzBvDLFiFgrPWqTGGq1NGnF3d4xKnxf0PGxSmv5SLb/aqQ9lzHg4DJ5UNkHO4M/NwzdxzzoaR1J5F8Q==} - engines: {node: '>= 18.0.0'} + '@temporalio/client@1.15.0': + resolution: {integrity: sha512-SxTGqRIa2+Vy4P9+06ZpUf4u7ZZmOXfx/kr9XvNqAApLxTMKjTQIg5OH5Wt4JLUtIR7dFkuHIyhewdRyG+hSsQ==} + engines: {node: '>= 20.0.0'} - '@temporalio/common@1.14.1': - resolution: {integrity: sha512-y49wOm3AIEKZufIQ/QU5JhTSaHJIEkiUt5bGB0/uSzCg8P4g8Cz0XoVPSbDwuCix533O9cOKcliYq7Gzjt/sIA==} - engines: {node: '>= 18.0.0'} + '@temporalio/common@1.15.0': + resolution: {integrity: sha512-tBfC3fdOExsNoS5krkMUXnaMtCRKj05Jts4+TH+cgHpbys68nslFvUQLqwPIw2x6155Divb9MF219a/75itbTg==} + engines: {node: '>= 20.0.0'} - '@temporalio/core-bridge@1.14.1': - resolution: {integrity: sha512-mrXXIFK5yNvsSZsTejLnL64JMuMliQjFKktSGITm2Ci7cWZ/ZTOVN6u+hCsUKfadYYv83jSuOC9Xe3z3RK273w==} - engines: {node: '>= 18.0.0'} + '@temporalio/core-bridge@1.15.0': + resolution: {integrity: sha512-Qdrs5zju5MiOwmERCWzQ6uHZwn1JaQk/ppS2UHbxqZndjbAEFPDU9KQqFLkxWAicHMy7+LGPnA4DpVOANGlTZA==} + engines: {node: '>= 20.0.0'} - '@temporalio/nexus@1.14.1': - resolution: {integrity: sha512-51oTeJ8nntAMF8boFSlzVdHlyC7y/LaLQPZMjEEOV2pi8O9yOI7GZvYDIAHhY8Z8AcDVgbXb8x0BbkjkwNiUiQ==} - engines: {node: '>= 18.0.0'} + '@temporalio/nexus@1.15.0': + resolution: {integrity: sha512-E6CdIjskkbK2aObxcb76Z4V3o1D3QDxEtsxmuHX5D7HEABuYGdV+oeOzDyxMlfeY9GyIM9Nvky4XCiSz2h2XRA==} + engines: {node: '>= 20.0.0'} - '@temporalio/proto@1.14.1': - resolution: {integrity: sha512-mCsUommDPXbXbBu60p1g4jpSqVb+GNR67yR0uKTU8ARb4qVZQo7SQnOUaneoxDERDXuR/yIjVCektMm+7Myb+A==} - engines: {node: '>= 18.0.0'} + '@temporalio/proto@1.15.0': + resolution: {integrity: sha512-Awy4Fjzyba7Pg/CVZjjQ3x2CWkDL1qELyTZWcLlyjXq8bX694JVfBsmiMmF6tHn5/ySOIxDTcc0MSScZ0oKX5A==} + engines: {node: '>= 20.0.0'} - '@temporalio/testing@1.14.1': - resolution: {integrity: sha512-iUyNHdoud5kD62LsXZWIryaPURpyaqCti3jNVaeL8nyQTmYyUXq6Trp5SuEEzbOMO7jLCoYI9cTfwzxAXy2p2g==} - engines: {node: '>= 18.0.0'} + '@temporalio/testing@1.15.0': + resolution: {integrity: sha512-OPbl9Fk969aoGAIamviL+3QT3xWY95MMzcKO+7UUBkmOJTIIbptsWY6ub5d21nmHVS4ewtlj04//dSYBD3EJYQ==} + engines: {node: '>= 20.0.0'} - '@temporalio/worker@1.14.1': - resolution: {integrity: sha512-wFfN5gc03eq1bYAuJNsG9a1iWBG6hL9zAfYbxiJdshPhpHa82BtHGvXD447oT2BX3zqI+Jf2b0m/N0wgkW6wyQ==} - engines: {node: '>= 18.0.0'} + '@temporalio/worker@1.15.0': + resolution: {integrity: sha512-9e0AWP2OxYFgeztMdkoWYbDVqmNubreRkG7/frVKFT3xHdIOrFQ2W6Yomv61q3oKMXTIrpvjClHtiTjAUr70uA==} + engines: {node: '>= 20.0.0'} - '@temporalio/workflow@1.14.1': - resolution: {integrity: sha512-MzshcoRo8zjQYa9WHrv3XC8LVvpRNSVaW3kOSTmHuTYDh/7be48WODOgs5yUpbnkpsw6rjVCDCgtB/K02cQwDg==} - engines: {node: '>= 18.0.0'} + '@temporalio/workflow@1.15.0': + resolution: {integrity: sha512-VaMhVtlA0hLLM/pna26vFSdn5W1Arq2+ccgItdbdRdZUa0X8eW2B7sl/PcUYQOWc4aMUGnvPATKUGUoFKyCxSg==} + engines: {node: '>= 20.0.0'} '@testing-library/dom@10.4.0': resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} @@ -3578,6 +3578,10 @@ packages: resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} engines: {node: '>=10.13.0'} + enhanced-resolve@5.19.0: + resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} + engines: {node: '>=10.13.0'} + enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} @@ -7581,6 +7585,10 @@ packages: resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==} engines: {node: '>=10.13.0'} + watchpack@2.5.1: + resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} + engines: {node: '>=10.13.0'} + web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} @@ -7612,6 +7620,16 @@ packages: webpack-cli: optional: true + webpack@5.105.2: + resolution: {integrity: sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + whatwg-encoding@2.0.0: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'} @@ -9706,56 +9724,56 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@temporalio/activity@1.14.1': + '@temporalio/activity@1.15.0': dependencies: - '@temporalio/client': 1.14.1 - '@temporalio/common': 1.14.1 + '@temporalio/client': 1.15.0 + '@temporalio/common': 1.15.0 abort-controller: 3.0.0 - '@temporalio/client@1.14.1': + '@temporalio/client@1.15.0': dependencies: '@grpc/grpc-js': 1.14.3 - '@temporalio/common': 1.14.1 - '@temporalio/proto': 1.14.1 + '@temporalio/common': 1.15.0 + '@temporalio/proto': 1.15.0 abort-controller: 3.0.0 long: 5.3.2 uuid: 11.1.0 - '@temporalio/common@1.14.1': + '@temporalio/common@1.15.0': dependencies: - '@temporalio/proto': 1.14.1 + '@temporalio/proto': 1.15.0 long: 5.3.2 ms: 3.0.0-canary.1 nexus-rpc: 0.0.1 proto3-json-serializer: 2.0.2 - '@temporalio/core-bridge@1.14.1': + '@temporalio/core-bridge@1.15.0': dependencies: '@grpc/grpc-js': 1.14.3 - '@temporalio/common': 1.14.1 + '@temporalio/common': 1.15.0 - '@temporalio/nexus@1.14.1': + '@temporalio/nexus@1.15.0': dependencies: - '@temporalio/client': 1.14.1 - '@temporalio/common': 1.14.1 - '@temporalio/proto': 1.14.1 + '@temporalio/client': 1.15.0 + '@temporalio/common': 1.15.0 + '@temporalio/proto': 1.15.0 long: 5.3.2 nexus-rpc: 0.0.1 - '@temporalio/proto@1.14.1': + '@temporalio/proto@1.15.0': dependencies: long: 5.3.2 protobufjs: 7.5.4 - '@temporalio/testing@1.14.1(esbuild@0.25.0)': + '@temporalio/testing@1.15.0(esbuild@0.25.0)': dependencies: - '@temporalio/activity': 1.14.1 - '@temporalio/client': 1.14.1 - '@temporalio/common': 1.14.1 - '@temporalio/core-bridge': 1.14.1 - '@temporalio/proto': 1.14.1 - '@temporalio/worker': 1.14.1(esbuild@0.25.0) - '@temporalio/workflow': 1.14.1 + '@temporalio/activity': 1.15.0 + '@temporalio/client': 1.15.0 + '@temporalio/common': 1.15.0 + '@temporalio/core-bridge': 1.15.0 + '@temporalio/proto': 1.15.0 + '@temporalio/worker': 1.15.0(esbuild@0.25.0) + '@temporalio/workflow': 1.15.0 abort-controller: 3.0.0 transitivePeerDependencies: - '@swc/helpers' @@ -9763,17 +9781,17 @@ snapshots: - uglify-js - webpack-cli - '@temporalio/worker@1.14.1(esbuild@0.25.0)': + '@temporalio/worker@1.15.0(esbuild@0.25.0)': dependencies: '@grpc/grpc-js': 1.14.3 '@swc/core': 1.15.5 - '@temporalio/activity': 1.14.1 - '@temporalio/client': 1.14.1 - '@temporalio/common': 1.14.1 - '@temporalio/core-bridge': 1.14.1 - '@temporalio/nexus': 1.14.1 - '@temporalio/proto': 1.14.1 - '@temporalio/workflow': 1.14.1 + '@temporalio/activity': 1.15.0 + '@temporalio/client': 1.15.0 + '@temporalio/common': 1.15.0 + '@temporalio/core-bridge': 1.15.0 + '@temporalio/nexus': 1.15.0 + '@temporalio/proto': 1.15.0 + '@temporalio/workflow': 1.15.0 abort-controller: 3.0.0 heap-js: 2.7.1 memfs: 4.51.1 @@ -9782,21 +9800,21 @@ snapshots: protobufjs: 7.5.4 rxjs: 7.8.2 source-map: 0.7.6 - source-map-loader: 4.0.2(webpack@5.104.0(@swc/core@1.15.5)(esbuild@0.25.0)) + source-map-loader: 4.0.2(webpack@5.105.2(@swc/core@1.15.5)(esbuild@0.25.0)) supports-color: 8.1.1 - swc-loader: 0.2.6(@swc/core@1.15.5)(webpack@5.104.0(@swc/core@1.15.5)(esbuild@0.25.0)) + swc-loader: 0.2.6(@swc/core@1.15.5)(webpack@5.105.2(@swc/core@1.15.5)(esbuild@0.25.0)) unionfs: 4.6.0 - webpack: 5.104.0(@swc/core@1.15.5)(esbuild@0.25.0) + webpack: 5.105.2(@swc/core@1.15.5)(esbuild@0.25.0) transitivePeerDependencies: - '@swc/helpers' - esbuild - uglify-js - webpack-cli - '@temporalio/workflow@1.14.1': + '@temporalio/workflow@1.15.0': dependencies: - '@temporalio/common': 1.14.1 - '@temporalio/proto': 1.14.1 + '@temporalio/common': 1.15.0 + '@temporalio/proto': 1.15.0 nexus-rpc: 0.0.1 '@testing-library/dom@10.4.0': @@ -11503,6 +11521,11 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + enhanced-resolve@5.19.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 @@ -15321,11 +15344,11 @@ snapshots: source-map-js@1.2.1: {} - source-map-loader@4.0.2(webpack@5.104.0(@swc/core@1.15.5)(esbuild@0.25.0)): + source-map-loader@4.0.2(webpack@5.105.2(@swc/core@1.15.5)(esbuild@0.25.0)): dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 - webpack: 5.104.0(@swc/core@1.15.5)(esbuild@0.25.0) + webpack: 5.105.2(@swc/core@1.15.5)(esbuild@0.25.0) source-map-support@0.5.13: dependencies: @@ -15750,11 +15773,11 @@ snapshots: picocolors: 1.1.1 stable: 0.1.8 - swc-loader@0.2.6(@swc/core@1.15.5)(webpack@5.104.0(@swc/core@1.15.5)(esbuild@0.25.0)): + swc-loader@0.2.6(@swc/core@1.15.5)(webpack@5.105.2(@swc/core@1.15.5)(esbuild@0.25.0)): dependencies: '@swc/core': 1.15.5 '@swc/counter': 0.1.3 - webpack: 5.104.0(@swc/core@1.15.5)(esbuild@0.25.0) + webpack: 5.105.2(@swc/core@1.15.5)(esbuild@0.25.0) symbol-tree@3.2.4: {} @@ -15840,6 +15863,18 @@ snapshots: '@swc/core': 1.15.5 esbuild: 0.25.0 + terser-webpack-plugin@5.3.16(@swc/core@1.15.5)(esbuild@0.25.0)(webpack@5.105.2(@swc/core@1.15.5)(esbuild@0.25.0)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + serialize-javascript: 6.0.2 + terser: 5.44.1 + webpack: 5.105.2(@swc/core@1.15.5)(esbuild@0.25.0) + optionalDependencies: + '@swc/core': 1.15.5 + esbuild: 0.25.0 + terser@5.44.1: dependencies: '@jridgewell/source-map': 0.3.11 @@ -16379,6 +16414,11 @@ snapshots: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 + watchpack@2.5.1: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + web-namespaces@2.0.1: {} web-streams-polyfill@3.3.3: {} @@ -16423,6 +16463,38 @@ snapshots: - esbuild - uglify-js + webpack@5.105.2(@swc/core@1.15.5)(esbuild@0.25.0): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.15.0 + acorn-import-phases: 1.0.4(acorn@8.15.0) + browserslist: 4.28.1 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.19.0 + es-module-lexer: 2.0.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.1 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.0 + terser-webpack-plugin: 5.3.16(@swc/core@1.15.5)(esbuild@0.25.0)(webpack@5.105.2(@swc/core@1.15.5)(esbuild@0.25.0)) + watchpack: 2.5.1 + webpack-sources: 3.3.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + whatwg-encoding@2.0.0: dependencies: iconv-lite: 0.6.3 diff --git a/src/lib/components/task-queue/worker-info.svelte b/src/lib/components/task-queue/worker-info.svelte index 8e0a5c77cd..af803ec0f9 100644 --- a/src/lib/components/task-queue/worker-info.svelte +++ b/src/lib/components/task-queue/worker-info.svelte @@ -6,11 +6,11 @@ import Card from '$lib/holocene/card.svelte'; import Icon from '$lib/holocene/icon/icon.svelte'; import Link from '$lib/holocene/link.svelte'; - import { - type WorkerInfo, - type WorkerPollerInfo, - type WorkerSlotsInfo, - } from '$lib/services/worker-service'; + import type { + WorkerInfo, + WorkerPollerInfo, + WorkerSlotsInfo, + } from '$lib/types'; import { formatDurationAbbreviated } from '$lib/utilities/format-time'; import { routeForTaskQueue } from '$lib/utilities/route-for'; import { toWorkerStatusReadable } from '$lib/utilities/screaming-enums'; @@ -129,7 +129,7 @@ {#snippet taskSlotCard( title: string, slots: WorkerSlotsInfo, - poller: WorkerPollerInfo | null, + poller: WorkerPollerInfo, )}
diff --git a/src/lib/components/task-queue/worker-table-cell.svelte b/src/lib/components/task-queue/worker-table-cell.svelte index 53ef0c00c6..357aa05079 100644 --- a/src/lib/components/task-queue/worker-table-cell.svelte +++ b/src/lib/components/task-queue/worker-table-cell.svelte @@ -33,21 +33,14 @@ clearParameters: [attribute], }); } else { - if (query) { - const newQuery = `${query} AND ${attribute}="${value}"`; - updateQueryParameters({ - parameter: 'query', - value: newQuery, - url: page.url, - }); - } else { - const newQuery = `${attribute}="${value}"`; - updateQueryParameters({ - parameter: 'query', - value: newQuery, - url: page.url, - }); - } + const newQuery = query + ? `${query} AND ${attribute}="${value}"` + : `${attribute}="${value}"`; + updateQueryParameters({ + parameter: 'query', + value: newQuery, + url: page.url, + }); } }; diff --git a/src/lib/components/task-queue/worker-table-row.svelte b/src/lib/components/task-queue/worker-table-row.svelte index 05f44cfe9a..686515c64d 100644 --- a/src/lib/components/task-queue/worker-table-row.svelte +++ b/src/lib/components/task-queue/worker-table-row.svelte @@ -1,6 +1,6 @@ @@ -42,11 +42,11 @@ > - {#each columns as { label }} + {#each columns as { label } (label)} {label} {/each} - {#each visibleItems as worker} + {#each visibleItems as worker, i (i)} {/each} diff --git a/src/lib/components/workers/worker-status.svelte b/src/lib/components/workers/worker-status.svelte index fb3e7cf9a1..f5cec496cc 100644 --- a/src/lib/components/workers/worker-status.svelte +++ b/src/lib/components/workers/worker-status.svelte @@ -16,7 +16,6 @@ Unspecified: 'Unspecified', Running: 'Running', 'Shutting Down': 'Shutting Down', - Shutdown: 'Shutdown', }; const workerStatus = cva( @@ -29,7 +28,6 @@ Unspecified: 'bg-slate-100', Running: 'bg-blue-300', 'Shutting Down': 'bg-yellow-200', - Shutdown: 'bg-red-200', }, }, }, diff --git a/src/lib/services/worker-service.ts b/src/lib/services/worker-service.ts index 90cd9d538a..283e097479 100644 --- a/src/lib/services/worker-service.ts +++ b/src/lib/services/worker-service.ts @@ -1,101 +1,14 @@ -import type { Duration, Timestamp } from '@temporalio/common'; - -import type { WorkerStatus } from '$lib/types'; -import type { WorkerDeploymentVersion } from '$lib/types/deployments'; -import type { NamespaceScopedRequest } from '$lib/types/global'; +import type { + DescribeWorkerRequest, + DescribeWorkerResponse, + ListTaskQueuePartitionsRequest, + ListWorkersRequest, + ListWorkersResponse, + WorkerInfo, +} from '$lib/types'; import { requestFromAPI } from '$lib/utilities/request-from-api'; import { routeForApi } from '$lib/utilities/route-for-api'; -export type WorkerQueryParams = { - workerInstanceKey?: string; - workerIdentity?: string; - hostName?: string; - taskQueue?: string; - deploymentName?: string; - buildId?: string; - sdkName?: string; - sdkVersion?: string; - startTime?: Timestamp; - lastHeartbeatTime?: Timestamp; - status?: WorkerStatus; -}; - -export type ListWorkersRequest = NamespaceScopedRequest & { - query?: string; -}; - -export type ListTaskQueueWorkersRequest = NamespaceScopedRequest & { - taskQueue: string; -}; - -export type DescribeWorkerRequest = NamespaceScopedRequest & { - workerInstanceKey: string; -}; - -export type HostInfo = { - hostName: string; - workerGroupingKey: string; - processId: string; - currentHostCpuUsage: number; - currentHostMemUsage: number; -}; - -export type WorkerSlotsInfo = { - currentAvailableSlots?: number; - currentUsedSlots?: number; - slotSupplierKind?: string; - totalProcessedTasks?: number; - totalFailedTasks?: number; - lastIntervalProcessedTasks?: number; - lastIntervalFailedTasks?: number; -}; - -export type WorkerPollerInfo = { - currentPollers: number; - lastSuccessfulPollTime: Timestamp; - isAutoscaling: boolean; -}; - -export type PluginInfo = { - name: string; - version: string; -}; - -export type WorkerHeartbeat = { - workerInstanceKey: string; - workerIdentity: string; - hostInfo: HostInfo; - taskQueue: string; - deploymentVersion: WorkerDeploymentVersion; - sdkName: string; - sdkVersion: string; - status: WorkerStatus; - startTime: Timestamp; - heartbeatTime: Timestamp; - elapsedSinceLastHeartbeat: Duration; - workflowTaskSlotsInfo: WorkerSlotsInfo; - activityTaskSlotsInfo: WorkerSlotsInfo; - nexusTaskSlotsInfo: WorkerSlotsInfo; - localActivitySlotsInfo: WorkerSlotsInfo; - workflowPollerInfo: WorkerPollerInfo; - workflowStickyPollerInfo: WorkerPollerInfo; - activityPollerInfo: WorkerPollerInfo; - nexusPollerInfo: WorkerPollerInfo; - totalStickyCacheHit: number; - totalStickyCacheMiss: number; - currentStickyCacheSize: number; - plugins: PluginInfo[]; -}; - -export type WorkerInfo = { - workerHeartbeat: WorkerHeartbeat; -}; - -export type ListWorkersResponse = { - workersInfo: WorkerInfo[]; - nextPageToken?: string; -}; - type PaginatedWorkerListPromise = ( pageSize: number, token: string, @@ -127,7 +40,7 @@ export const fetchPaginatedWorkers = async ( }; export const fetchPaginatedWorkersForTaskQueue = async ( - parameters: ListTaskQueueWorkersRequest, + parameters: ListTaskQueuePartitionsRequest, request = fetch, ): Promise => { return (pageSize = 100, token = '') => { @@ -151,8 +64,6 @@ export const fetchPaginatedWorkersForTaskQueue = async ( }; }; -export type DescribeWorkerResponse = { workerInfo: WorkerInfo }; - export async function describeWorker( parameters: DescribeWorkerRequest, request = fetch, diff --git a/src/lib/types/api.ts b/src/lib/types/api.ts index 8f4cddd972..619600928a 100644 --- a/src/lib/types/api.ts +++ b/src/lib/types/api.ts @@ -161,11 +161,6 @@ export type TaskQueueRouteParameters = Pick< 'namespace' | 'queue' >; -export type WorkerRouteParameters = Pick< - APIRouteParameters, - 'namespace' | 'queue' | 'workerInstanceKey' ->; - export type ValidWorkflowEndpoints = WorkflowsAPIRoutePath; export type ValidWorkflowParameters = diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index 4ec0698e4c..94855d0a42 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -78,6 +78,16 @@ export type PauseWorkflowRequest = export type UnpauseWorkflowRequest = // temporal.api.workflowservice.v1.IUnpauseWorkflowExecutionRequest; PauseOrUnpauseWorkflowRequest; +export type ListTaskQueuePartitionsRequest = + temporal.api.workflowservice.v1.IListTaskQueuePartitionsRequest; +export type ListWorkersRequest = + temporal.api.workflowservice.v1.IListWorkersRequest; +export type ListWorkersResponse = + temporal.api.workflowservice.v1.IListWorkersResponse; +export type DescribeWorkerRequest = + temporal.api.workflowservice.v1.IDescribeWorkerRequest; +export type DescribeWorkerResponse = + temporal.api.workflowservice.v1.IDescribeWorkerResponse; // api.history @@ -301,6 +311,18 @@ export type EventLink = temporal.api.common.v1.ILink; // api.failure export type Failure = temporal.api.failure.v1.IFailure; +// api.worker +export type WorkerHostInfo = temporal.api.worker.v1.IWorkerHostInfo & { + workerGroupingKey?: string; +}; +export type WorkerHeartbeat = temporal.api.worker.v1.IWorkerHeartbeat & { + hostInfo?: WorkerHostInfo; +}; +export type WorkerPollerInfo = temporal.api.worker.v1.IWorkerPollerInfo; +export type WorkerSlotsInfo = temporal.api.worker.v1.IWorkerSlotsInfo; +export type WorkerInfo = temporal.api.worker.v1.IWorkerInfo; +export type PluginInfo = temporal.api.worker.v1.PluginInfo; + // google export type Timestamp = google.protobuf.ITimestamp; diff --git a/src/lib/utilities/route-for-api.ts b/src/lib/utilities/route-for-api.ts index 0e8b6134e4..b8e724a400 100644 --- a/src/lib/utilities/route-for-api.ts +++ b/src/lib/utilities/route-for-api.ts @@ -3,6 +3,7 @@ import { get } from 'svelte/store'; import { resolve } from '$app/paths'; import { page } from '$app/stores'; +import type { DescribeWorkerRequest } from '$lib/types'; import type { APIRouteParameters, APIRoutePath, @@ -32,7 +33,6 @@ import type { WorkerDeploymentsAPIRoutePath, WorkerDeploymentVersionAPIRoutePath, WorkerDeploymentVersionRouteParameters, - WorkerRouteParameters, WorkflowActivitiesAPIRoutePath, WorkflowActivitiesRouteParameters, WorkflowAPIRoutePath, @@ -215,7 +215,7 @@ export function routeForApi( ): string; export function routeForApi( route: WorkerAPIRoutePath, - parameters: NamespaceRouteParameters | WorkerRouteParameters, + parameters: DescribeWorkerRequest, shouldEncode?: boolean, ): string; export function routeForApi( diff --git a/src/lib/utilities/screaming-enums.ts b/src/lib/utilities/screaming-enums.ts index e3e0b6549c..8de7139654 100644 --- a/src/lib/utilities/screaming-enums.ts +++ b/src/lib/utilities/screaming-enums.ts @@ -100,11 +100,7 @@ export const toCallbackStateReadable = ( return fromScreamingEnum(state, 'CallbackState'); }; -export type ReadableWorkerStatus = - | 'Unspecified' - | 'Running' - | 'Shutting Down' - | 'Shutdown'; +export type ReadableWorkerStatus = 'Unspecified' | 'Running' | 'Shutting Down'; export const toWorkerStatusReadable = ( state?: WorkerStatus, diff --git a/src/lib/utilities/update-query-parameters.ts b/src/lib/utilities/update-query-parameters.ts index f774783435..2728579f98 100644 --- a/src/lib/utilities/update-query-parameters.ts +++ b/src/lib/utilities/update-query-parameters.ts @@ -32,7 +32,6 @@ export const updateQueryParameters = async ({ const params = {}; let replaced = false; - console.log('Updating query parameter:'); url.searchParams.forEach((value, key) => { if (key !== parameter) { params[key] = value; diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 5b5b3ad2f4..347647ab5f 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -247,16 +247,6 @@ } } - // Handle workers page - if (page.url.pathname.endsWith('workers')) { - return routeForWorkers({ namespace }); - } - - // Handle workers page - if (page.url.pathname.includes('workers')) { - return routeForWorkers({ namespace }); - } - return routeForWorkflows({ namespace }); } diff --git a/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte b/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte index 5b1423009d..266a412b77 100644 --- a/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte @@ -16,15 +16,16 @@ const workersHref = $derived(routeForWorkers({ namespace })); const deploymentsHref = $derived(routeForWorkerDeployments({ namespace })); - - const pageTitle = $derived(translate('deployments.worker-deployments')); - +

- {pageTitle} + {translate('deployments.worker-deployments')}

diff --git a/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte index 2f34f7efbc..6d43a1e512 100644 --- a/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte @@ -16,15 +16,13 @@ const workersHref = $derived(routeForWorkers({ namespace })); const deploymentsHref = $derived(routeForWorkerDeployments({ namespace })); - - const pageTitle = $derived(translate('workers.workers')); - +

- {pageTitle} + {translate('workers.workers')}

From 004bf4d5a076e1e87d9360ffd3889b306c87d398 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Wed, 4 Mar 2026 13:35:31 -0700 Subject: [PATCH 10/40] Small fixes and improvements --- .../components/task-queue/worker-info.svelte | 148 ++++++++++-------- .../task-queue/worker-table-cell.svelte | 6 +- .../task-queue/worker-table-row.svelte | 108 +++++-------- .../components/task-queue/worker-table.svelte | 27 ++-- .../task-queue/worker-totals.svelte | 85 ++++------ .../workers/worker-search-table.svelte | 19 +-- src/lib/i18n/locales/en/common.ts | 2 + src/lib/i18n/locales/en/workers.ts | 28 +++- src/lib/services/worker-service.ts | 3 +- src/lib/types/index.ts | 2 - src/lib/utilities/route-for.ts | 7 +- .../+page.svelte | 8 +- 12 files changed, 224 insertions(+), 219 deletions(-) rename src/routes/(app)/namespaces/[namespace]/workers/{[instance] => [workerInstanceKey]}/+page.svelte (84%) diff --git a/src/lib/components/task-queue/worker-info.svelte b/src/lib/components/task-queue/worker-info.svelte index af803ec0f9..2af894d5d6 100644 --- a/src/lib/components/task-queue/worker-info.svelte +++ b/src/lib/components/task-queue/worker-info.svelte @@ -6,6 +6,7 @@ import Card from '$lib/holocene/card.svelte'; import Icon from '$lib/holocene/icon/icon.svelte'; import Link from '$lib/holocene/link.svelte'; + import { translate } from '$lib/i18n/translate'; import type { WorkerInfo, WorkerPollerInfo, @@ -26,7 +27,7 @@ const { namespace } = $derived(page.params); const heartbeat = $derived(worker.workerHeartbeat); - const status = $derived(toWorkerStatusReadable(heartbeat.status)); + const status = $derived(toWorkerStatusReadable(heartbeat?.status)); const totalStickyCacheHit = $derived(heartbeat?.totalStickyCacheHit ?? 0); const totalStickyCacheMiss = $derived(heartbeat?.totalStickyCacheMiss ?? 0); @@ -34,7 +35,7 @@ heartbeat?.currentStickyCacheSize ?? 0, ); - const cacheHitRate = $derived(() => { + const cacheHitRate = $derived.by(() => { const total = totalStickyCacheHit + totalStickyCacheMiss; if (total === 0) return 0; return ((totalStickyCacheHit / total) * 100).toFixed(1); @@ -45,49 +46,51 @@
-

{heartbeat.workerIdentity}

+

{heartbeat?.workerIdentity}

- Task Queue + {translate('common.task-queue')} - {heartbeat.taskQueue} + {heartbeat?.taskQueue}
- Start + {translate('common.start')}
- Last Heartbeat + {translate('workflows.last-heartbeat')}
{formatDurationAbbreviated( - String(heartbeat.elapsedSinceLastHeartbeat), + String(heartbeat?.elapsedSinceLastHeartbeat), )} ago
- SDK + {translate('workers.sdk')}
@@ -96,23 +99,23 @@
{@render taskSlotCard( - 'Workflow', - heartbeat.workflowTaskSlotsInfo, - heartbeat.workflowPollerInfo, + translate('common.workflows-plural', { count: 1 }), + heartbeat?.workflowTaskSlotsInfo, + heartbeat?.workflowPollerInfo, )} {@render taskSlotCard( - 'Activity', - heartbeat.activityTaskSlotsInfo, - heartbeat.activityPollerInfo, + translate('common.activities-plural', { count: 1 }), + heartbeat?.activityTaskSlotsInfo, + heartbeat?.activityPollerInfo, )} {@render taskSlotCard( - 'Nexus Tasks', - heartbeat.nexusTaskSlotsInfo, - heartbeat.nexusPollerInfo, + translate('workers.nexus-tasks'), + heartbeat?.nexusTaskSlotsInfo, + heartbeat?.nexusPollerInfo, )} {@render taskSlotCard( - 'Local Activities', - heartbeat.localActivitySlotsInfo, + translate('workers.local-activities'), + heartbeat?.localActivitySlotsInfo, null, )}
@@ -139,7 +142,7 @@
-
Slots
+
{translate('workers.slots')}

{slots.currentUsedSlots ?? 0} @@ -153,18 +156,23 @@

-

Used

+

{translate('workers.used')}

{#if slots.currentAvailableSlots} - Available out of {slots.currentAvailableSlots} - {:else}None Available + {translate('workers.available-out-of', { + count: slots.currentAvailableSlots, + })} + {:else} + {translate('workers.none-available')} {/if}

-
Tasks Processed
+
+ {translate('workers.tasks-processed')} +
{(slots.totalProcessedTasks ?? 0).toLocaleString()} @@ -183,12 +191,10 @@
{#if poller.lastSuccessfulPollTime} - Last Poll + {translate('workers.last-poll')} + {:else} - No Activity + {translate('workers.no-activity')} {/if}
@@ -199,28 +205,30 @@ {#snippet hostInfo()} -

Host Info

+

{translate('workers.host-info')}

- Host Name + {translate('workers.host-name')} - {heartbeat.hostInfo.hostName} + {heartbeat?.hostInfo?.hostName}
- Process ID - {heartbeat.hostInfo.processId} + {translate('workers.process-id')} + {heartbeat?.hostInfo?.processId}
-
- Instance Key +
+ {translate('workers.instance')} - {heartbeat.workerInstanceKey} + {heartbeat?.workerInstanceKey}
- Worker Grouping Key + {translate('workers.worker-grouping')} - {heartbeat.hostInfo.workerGroupingKey} + {heartbeat?.hostInfo?.workerGroupingKey}
@@ -230,22 +238,22 @@ {#snippet hostUsage()}
-

Host Usage

+

{translate('workers.host-usage')}

- CPU Usage + {translate('workers.cpu-usage')} - {heartbeat.hostInfo.currentHostCpuUsage.toFixed(0)}% + {heartbeat?.hostInfo?.currentHostCpuUsage.toFixed(0)}%
@@ -255,15 +263,15 @@
- Memory Usage + {translate('workers.memory-usage')} - {heartbeat.hostInfo.currentHostMemUsage.toFixed(0)}% + {heartbeat?.hostInfo?.currentHostMemUsage.toFixed(0)}%
@@ -275,43 +283,59 @@ {#snippet workflowCache()} -

Workflow Cache

+

+ {translate('workers.workflow-cache')} +

{currentStickyCacheSize.toLocaleString()} -
Cache size
+
+ {translate('workers.cache-size')} +
- {cacheHitRate()}% + {cacheHitRate}% -
Cache hits
+
+ {translate('workers.cache-hits')} +
{totalStickyCacheHit.toLocaleString()} -
Active thread count
+
+ {translate('workers.active-thread-count')} +
{/snippet} {#snippet diagnostics()} -

Diagnostics

+

+ {translate('workers.diagnostics')} +

- {cacheHitRate()}% + {cacheHitRate}% -
Poll success rate
+
+ {translate('workers.poll-success-rate')} +
- None -
Rate limit
+ {translate('common.none')} +
+ {translate('workers.rate-limit')} +
diff --git a/src/lib/components/task-queue/worker-table-cell.svelte b/src/lib/components/task-queue/worker-table-cell.svelte index 357aa05079..b16614216a 100644 --- a/src/lib/components/task-queue/worker-table-cell.svelte +++ b/src/lib/components/task-queue/worker-table-cell.svelte @@ -8,13 +8,14 @@ import { translate } from '$lib/i18n/translate'; import { updateQueryParameters } from '$lib/utilities/update-query-parameters'; - type Props = { + interface Props { attribute?: string; value: string; filterable?: boolean; href?: string; children?: Snippet; - }; + } + let { attribute, value, @@ -22,6 +23,7 @@ href, children, }: Props = $props(); + const query = $derived(page.url.searchParams.get('query') || ''); const onRowFilterClick = () => { diff --git a/src/lib/components/task-queue/worker-table-row.svelte b/src/lib/components/task-queue/worker-table-row.svelte index 686515c64d..0b4f2b901e 100644 --- a/src/lib/components/task-queue/worker-table-row.svelte +++ b/src/lib/components/task-queue/worker-table-row.svelte @@ -1,5 +1,4 @@ - {#each columns as { label } (label)} - {#if label === translate('workers.identity')} - - {:else if label === translate('workers.task-queue')} - - {:else if label === translate('workers.host-name')} - - {:else if label === translate('workers.instance')} - - {:else if label === translate('workers.status')} - - - - {:else if label === translate('workers.sdk')} - - - - {:else if label === translate('workers.workflow-task-slots')} - {worker.workerHeartbeat?.workflowTaskSlotsInfo?.currentUsedSlots ?? 0} / - {worker.workerHeartbeat?.workflowTaskSlotsInfo?.currentAvailableSlots ?? - 0} - {:else if label === translate('workers.activity-task-slots')} - {worker.workerHeartbeat?.activityTaskSlotsInfo?.currentUsedSlots ?? 0} / - {worker.workerHeartbeat?.activityTaskSlotsInfo?.currentAvailableSlots ?? - 0} - {:else if label === translate('workers.nexus-task-slots')} - {worker.workerHeartbeat?.nexusTaskSlotsInfo?.currentUsedSlots ?? 0} / {worker - .workerHeartbeat?.nexusTaskSlotsInfo?.currentAvailableSlots ?? 0} - {/if} - {/each} + + + + + + + + + + diff --git a/src/lib/components/task-queue/worker-table.svelte b/src/lib/components/task-queue/worker-table.svelte index 0f318703be..da47c52e8a 100644 --- a/src/lib/components/task-queue/worker-table.svelte +++ b/src/lib/components/task-queue/worker-table.svelte @@ -9,19 +9,22 @@ import WorkerTableRow from './worker-table-row.svelte'; - let { namespace, taskQueue } = $props(); + interface Props { + namespace: string; + taskQueue: string; + } + + let { namespace, taskQueue }: Props = $props(); let error: string | null = $state(null); - const columns = $derived([ + const columns = [ { label: translate('workers.status') }, { label: translate('workers.instance') }, + { label: translate('workers.task-queue') }, { label: translate('workers.identity') }, { label: translate('workers.host-name') }, - { label: translate('workers.workflow-task-slots') }, - { label: translate('workers.activity-task-slots') }, - { label: translate('workers.nexus-task-slots') }, { label: translate('workers.sdk') }, - ]); + ]; const onFetch = $derived(() => fetchPaginatedWorkersForTaskQueue({ namespace, taskQueue }), @@ -44,17 +47,17 @@ emptyStateMessage={translate('workers.empty-state-title')} errorMessage={translate('workers.error-message-fetching')} > - {translate('workers.workers')} + + {translate('workers.workers')} + - {#each columns as { label }} + {#each columns as { label } (label)} {label} {/each} - {#each visibleItems as worker} - + {#each visibleItems as worker (worker.workerHeartbeat.workerInstanceKey)} + {/each} diff --git a/src/lib/components/task-queue/worker-totals.svelte b/src/lib/components/task-queue/worker-totals.svelte index 0fa31d7466..d035fed777 100644 --- a/src/lib/components/task-queue/worker-totals.svelte +++ b/src/lib/components/task-queue/worker-totals.svelte @@ -1,84 +1,65 @@
- Active Workers + {translate('workers.active-workers')}
-
{totalActive}
+
{active}
- Running + + {translate('workflows.running')} +
- Inactive Workers + {translate('workers.inactive-workers')}
-
{totalInactive}
+
{inactive}
- Inactive + + {translate('common.inactive')} +
- Total Processed Tasks + {translate('workers.total-processed-tasks')}
-
{totalProcessedTasks}
+
{totalTasks}
- {workflowProcessedTasks} workflow · {activityProcessedTasks} activity + {workflowTasks} + {translate('common.workflows-plural', { count: 1 })} · {activityTasks} + {translate('common.activities-plural', { count: 1 })}
diff --git a/src/lib/components/workers/worker-search-table.svelte b/src/lib/components/workers/worker-search-table.svelte index c0edded95f..5c5db516ed 100644 --- a/src/lib/components/workers/worker-search-table.svelte +++ b/src/lib/components/workers/worker-search-table.svelte @@ -7,7 +7,11 @@ import { translate } from '$lib/i18n/translate'; import { fetchPaginatedWorkers } from '$lib/services/worker-service'; - let { namespace } = $props(); + interface Props { + namespace: string; + } + + let { namespace }: Props = $props(); const query = $derived(page.url.searchParams.get('query') || ''); @@ -17,9 +21,6 @@ { label: translate('workers.task-queue') }, { label: translate('workers.identity') }, { label: translate('workers.host-name') }, - { label: translate('workers.workflow-task-slots') }, - { label: translate('workers.activity-task-slots') }, - { label: translate('workers.nexus-task-slots') }, { label: translate('workers.sdk') }, ]; @@ -37,17 +38,17 @@ emptyStateMessage={translate('workers.empty-state-title')} errorMessage={translate('workers.error-message-fetching')} > - {translate('workers.workers')} + + {translate('workers.workers')} + {#each columns as { label } (label)} {label} {/each} - {#each visibleItems as worker, i (i)} - + {#each visibleItems as worker (worker.workerHeartbeat.workerInstanceKey)} + {/each} diff --git a/src/lib/i18n/locales/en/common.ts b/src/lib/i18n/locales/en/common.ts index a65ec46161..382d12d964 100644 --- a/src/lib/i18n/locales/en/common.ts +++ b/src/lib/i18n/locales/en/common.ts @@ -108,6 +108,8 @@ export const Strings = { workflows: 'Workflows', 'workflows-plural_one': 'Workflow', 'workflows-plural_other': 'Workflows', + 'activities-plural_one': 'Activity', + 'activities-plural_other': 'Activities', schedules: 'Schedules', 'schedules-plural_one': '{{ count }} Schedule', 'schedules-plural_other': '{{ count }} Schedules', diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index 87ef81221b..13d3fc60d2 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -20,9 +20,6 @@ export const Strings = { 'task-queue': 'Task Queue', status: 'Status', sdk: 'SDK', - 'workflow-task-slots': 'Workflow Slots', - 'activity-task-slots': 'Activity Slots', - 'nexus-task-slots': 'Nexus Slots', 'empty-state-title': 'No Workers Found', 'error-message-fetching': 'An error occurred while fetching workers.', 'compatible-build-ids': 'Compatible Build IDs', @@ -41,4 +38,29 @@ export const Strings = { 'Viewing workers for pinned Build ID. Go to the Task Queue page to view all workers.', 'viewing-auto-upgrade-build-ids': 'Viewing workers for current and ramping Build IDs. Go to the Task Queue page to view all workers.', + 'active-workers': 'Active Workers', + 'inactive-workers': 'Inactive Workers', + 'total-processed-tasks': 'Total Processed Tasks', + 'nexus-tasks': 'Nexus Tasks', + 'local-activities': 'Local Activities', + slots: 'Slots', + used: 'Used', + 'available-out-of': 'Available out of {count}', + 'none-available': 'None Available', + 'tasks-processed': 'Tasks Processed', + 'last-poll': 'Last Poll', + 'no-activity': 'No Activity', + 'host-info': 'Host Info', + 'process-id': 'Process ID', + 'worker-grouping': 'Worker Grouping', + 'host-usage': 'Host Usage', + 'cpu-usage': 'CPU Usage', + 'memory-usage': 'Memory Usage', + 'workflow-cache': 'Workflow Cache', + 'cache-size': 'Cache size', + 'cache-hits': 'Cache hits', + 'active-thread-count': 'Active thread count', + diagnostics: 'Diagnostics', + 'poll-success-rate': 'Poll success rate', + 'rate-limit': 'Rate limit', } as const; diff --git a/src/lib/services/worker-service.ts b/src/lib/services/worker-service.ts index 283e097479..8d3c3efbd7 100644 --- a/src/lib/services/worker-service.ts +++ b/src/lib/services/worker-service.ts @@ -1,7 +1,6 @@ import type { DescribeWorkerRequest, DescribeWorkerResponse, - ListTaskQueuePartitionsRequest, ListWorkersRequest, ListWorkersResponse, WorkerInfo, @@ -40,7 +39,7 @@ export const fetchPaginatedWorkers = async ( }; export const fetchPaginatedWorkersForTaskQueue = async ( - parameters: ListTaskQueuePartitionsRequest, + parameters: ListWorkersRequest & { taskQueue: string }, request = fetch, ): Promise => { return (pageSize = 100, token = '') => { diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index dc9b76c2c2..cd35cfe894 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -49,8 +49,6 @@ export type PauseWorkflowRequest = temporal.api.workflowservice.v1.IPauseWorkflowExecutionRequest; export type UnpauseWorkflowRequest = temporal.api.workflowservice.v1.IUnpauseWorkflowExecutionRequest; -export type ListTaskQueuePartitionsRequest = - temporal.api.workflowservice.v1.IListTaskQueuePartitionsRequest; export type ListWorkersRequest = temporal.api.workflowservice.v1.IListWorkersRequest; export type ListWorkersResponse = diff --git a/src/lib/utilities/route-for.ts b/src/lib/utilities/route-for.ts index 7d8d49e94b..40a3969978 100644 --- a/src/lib/utilities/route-for.ts +++ b/src/lib/utilities/route-for.ts @@ -303,16 +303,13 @@ export const routeForTimeline = ({ export const routeForWorkers = ( parameters: NamespaceParameter, ): ResolvedPathname => { - return resolve( - `${routeForNamespace({ namespace: parameters.namespace })}/workers`, - {}, - ); + return `${routeForNamespace({ namespace: parameters.namespace })}/workers`; }; export const routeForWorkflowWorkers = ( parameters: WorkflowParameters, ): ResolvedPathname => { - return resolve(`${routeForWorkflow(parameters)}/workers`, {}); + return `${routeForWorkflow(parameters)}/workers`; }; export const routeForWorkerDeployments = ({ diff --git a/src/routes/(app)/namespaces/[namespace]/workers/[instance]/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workers/[workerInstanceKey]/+page.svelte similarity index 84% rename from src/routes/(app)/namespaces/[namespace]/workers/[instance]/+page.svelte rename to src/routes/(app)/namespaces/[namespace]/workers/[workerInstanceKey]/+page.svelte index d3eaa0ac75..c282389472 100644 --- a/src/routes/(app)/namespaces/[namespace]/workers/[instance]/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workers/[workerInstanceKey]/+page.svelte @@ -3,6 +3,7 @@ import PageTitle from '$lib/components/page-title.svelte'; import WorkerInfo from '$lib/components/task-queue/worker-info.svelte'; + import Error from '$lib/holocene/error.svelte'; import Link from '$lib/holocene/link.svelte'; import { translate } from '$lib/i18n/translate'; import { describeWorker } from '$lib/services/worker-service'; @@ -12,7 +13,10 @@ const workerInstanceKey = $derived(page.params.instance); - +
@@ -30,5 +34,5 @@ {#await describeWorker({ namespace, workerInstanceKey }) then data} {:catch error} -

Error loading worker info: {error.message}

+ {/await} From cddca19ffb95d6f4d96b0438013aa3ae1d5c0d57 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Wed, 4 Mar 2026 14:15:51 -0700 Subject: [PATCH 11/40] Add filtering to workers table --- .../lines-and-dots/workflow-details.svelte | 2 +- .../filter-bar.svelte | 56 ++++++ .../filter-list.svelte} | 84 +++++---- .../filter.svelte | 45 +++-- .../manual-query.svelte | 37 ++-- .../search-attribute-menu.svelte | 48 ++--- .../status-filter-chip.svelte | 164 ++++++++++++++++++ .../shared-search-attribute-filter/types.ts | 1 + .../filter-bar.svelte | 51 ++---- .../components/task-queue/worker-info.svelte | 3 +- .../task-queue/worker-insights.svelte | 4 +- .../components/task-queue/worker-table.svelte | 71 -------- .../workers/worker-search-table.svelte | 58 ------- .../components/workers/worker-status.svelte | 19 +- .../task-queue-workers-table.svelte | 33 ++++ .../workers-table/workers-table-cell.svelte} | 5 +- .../workers-table/workers-table-row.svelte} | 26 ++- .../workers-table-with-search.svelte | 20 +++ .../workers-table/workers-table.svelte | 58 +++++++ src/lib/i18n/locales/en/workers.ts | 2 +- src/lib/i18n/locales/en/workflows.ts | 1 + src/lib/models/worker-status.ts | 13 ++ src/lib/services/worker-service.ts | 33 +--- src/lib/stores/filters.ts | 15 ++ src/lib/stores/search-attributes.ts | 26 ++- src/lib/utilities/get-sdk-version.ts | 17 +- src/lib/utilities/screaming-enums.ts | 3 +- .../[namespace]/workers/+page.svelte | 27 ++- 28 files changed, 604 insertions(+), 318 deletions(-) create mode 100644 src/lib/components/shared-search-attribute-filter/filter-bar.svelte rename src/lib/components/{standalone-activities/activities-summary-filter-bar/dropdown-filter-list.svelte => shared-search-attribute-filter/filter-list.svelte} (50%) rename src/lib/components/{standalone-activities/activities-summary-filter-bar => shared-search-attribute-filter}/filter.svelte (55%) rename src/lib/components/{standalone-activities/activities-summary-filter-bar => shared-search-attribute-filter}/manual-query.svelte (75%) rename src/lib/components/{standalone-activities/activities-summary-filter-bar => shared-search-attribute-filter}/search-attribute-menu.svelte (74%) create mode 100644 src/lib/components/shared-search-attribute-filter/status-filter-chip.svelte create mode 100644 src/lib/components/shared-search-attribute-filter/types.ts delete mode 100644 src/lib/components/task-queue/worker-table.svelte delete mode 100644 src/lib/components/workers/worker-search-table.svelte create mode 100644 src/lib/components/workers/workers-table/task-queue-workers-table.svelte rename src/lib/components/{task-queue/worker-table-cell.svelte => workers/workers-table/workers-table-cell.svelte} (96%) rename src/lib/components/{task-queue/worker-table-row.svelte => workers/workers-table/workers-table-row.svelte} (70%) create mode 100644 src/lib/components/workers/workers-table/workers-table-with-search.svelte create mode 100644 src/lib/components/workers/workers-table/workers-table.svelte create mode 100644 src/lib/models/worker-status.ts diff --git a/src/lib/components/lines-and-dots/workflow-details.svelte b/src/lib/components/lines-and-dots/workflow-details.svelte index c5c7e03698..a07d1fa818 100644 --- a/src/lib/components/lines-and-dots/workflow-details.svelte +++ b/src/lib/components/lines-and-dots/workflow-details.svelte @@ -305,7 +305,7 @@ {/if} {#if sdk && sdkVersion} - SDK + {translate('workflows.sdk')} diff --git a/src/lib/components/shared-search-attribute-filter/filter-bar.svelte b/src/lib/components/shared-search-attribute-filter/filter-bar.svelte new file mode 100644 index 0000000000..f1d5dd0e85 --- /dev/null +++ b/src/lib/components/shared-search-attribute-filter/filter-bar.svelte @@ -0,0 +1,56 @@ + + +
+
+
+ + +
+
+ +
+
+ {#if viewManualQuery} + + {/if} +
diff --git a/src/lib/components/standalone-activities/activities-summary-filter-bar/dropdown-filter-list.svelte b/src/lib/components/shared-search-attribute-filter/filter-list.svelte similarity index 50% rename from src/lib/components/standalone-activities/activities-summary-filter-bar/dropdown-filter-list.svelte rename to src/lib/components/shared-search-attribute-filter/filter-list.svelte index 6db1c9e76c..6a5050decd 100644 --- a/src/lib/components/standalone-activities/activities-summary-filter-bar/dropdown-filter-list.svelte +++ b/src/lib/components/shared-search-attribute-filter/filter-list.svelte @@ -1,47 +1,59 @@
- - + +
diff --git a/src/lib/components/standalone-activities/activities-summary-filter-bar/manual-query.svelte b/src/lib/components/shared-search-attribute-filter/manual-query.svelte similarity index 75% rename from src/lib/components/standalone-activities/activities-summary-filter-bar/manual-query.svelte rename to src/lib/components/shared-search-attribute-filter/manual-query.svelte index 1221aa6f23..701649a394 100644 --- a/src/lib/components/standalone-activities/activities-summary-filter-bar/manual-query.svelte +++ b/src/lib/components/shared-search-attribute-filter/manual-query.svelte @@ -1,4 +1,5 @@
-
-{/snippet} - -
-
-
- - -
- {@render actionToggleButtons()} -
- {#if viewManualQuery} - - {/if} -
+ diff --git a/src/lib/components/task-queue/worker-info.svelte b/src/lib/components/task-queue/worker-info.svelte index 2af894d5d6..7f5bc0fa1c 100644 --- a/src/lib/components/task-queue/worker-info.svelte +++ b/src/lib/components/task-queue/worker-info.svelte @@ -13,6 +13,7 @@ WorkerSlotsInfo, } from '$lib/types'; import { formatDurationAbbreviated } from '$lib/utilities/format-time'; + import { formatSDKName } from '$lib/utilities/get-sdk-version'; import { routeForTaskQueue } from '$lib/utilities/route-for'; import { toWorkerStatusReadable } from '$lib/utilities/screaming-enums'; @@ -89,7 +90,7 @@
{translate('workers.sdk')}
diff --git a/src/lib/components/task-queue/worker-insights.svelte b/src/lib/components/task-queue/worker-insights.svelte index 1f31b2f976..aba0638192 100644 --- a/src/lib/components/task-queue/worker-insights.svelte +++ b/src/lib/components/task-queue/worker-insights.svelte @@ -1,11 +1,11 @@ - + diff --git a/src/lib/components/task-queue/worker-table.svelte b/src/lib/components/task-queue/worker-table.svelte deleted file mode 100644 index da47c52e8a..0000000000 --- a/src/lib/components/task-queue/worker-table.svelte +++ /dev/null @@ -1,71 +0,0 @@ - - -{#if !error} - - - {translate('workers.workers')} - - - - {#each columns as { label } (label)} - {label} - {/each} - - {#each visibleItems as worker (worker.workerHeartbeat.workerInstanceKey)} - - {/each} - - - - - -{:else} - {#await getPollers({ queue: taskQueue, namespace }) then workers} - - {/await} -{/if} diff --git a/src/lib/components/workers/worker-search-table.svelte b/src/lib/components/workers/worker-search-table.svelte deleted file mode 100644 index 5c5db516ed..0000000000 --- a/src/lib/components/workers/worker-search-table.svelte +++ /dev/null @@ -1,58 +0,0 @@ - - -{#key query} - - - {translate('workers.workers')} - - - - {#each columns as { label } (label)} - {label} - {/each} - - {#each visibleItems as worker (worker.workerHeartbeat.workerInstanceKey)} - - {/each} - - - - - -{/key} diff --git a/src/lib/components/workers/worker-status.svelte b/src/lib/components/workers/worker-status.svelte index f5cec496cc..eadc2815a3 100644 --- a/src/lib/components/workers/worker-status.svelte +++ b/src/lib/components/workers/worker-status.svelte @@ -2,20 +2,20 @@ import { cva } from 'class-variance-authority'; import HeartBeat from '$lib/components/heart-beat-indicator.svelte'; - import type { ReadableWorkerStatus } from '$lib/utilities/screaming-enums'; + import { translate } from '$lib/i18n/translate'; + import type { WorkerStatus } from '$lib/models/worker-status'; interface Props { delay?: number; - status?: ReadableWorkerStatus; - 'test-id'?: string; + status?: WorkerStatus; } - let { delay = 0, status = 'Running', 'test-id': testId }: Props = $props(); + let { delay = 0, status = 'Running' }: Props = $props(); - const label: Record = { - Unspecified: 'Unspecified', - Running: 'Running', + const label: Record = { + Running: translate('workflows.running'), 'Shutting Down': 'Shutting Down', + Unspecified: translate('events.event-classification.unspecified'), }; const workerStatus = cva( @@ -36,10 +36,7 @@ const isRunning = $derived(status === 'Running'); -
+
{label[status]} {#if isRunning} diff --git a/src/lib/components/workers/workers-table/task-queue-workers-table.svelte b/src/lib/components/workers/workers-table/task-queue-workers-table.svelte new file mode 100644 index 0000000000..96fe3fc2bd --- /dev/null +++ b/src/lib/components/workers/workers-table/task-queue-workers-table.svelte @@ -0,0 +1,33 @@ + + +{#if useFallback} + {#await getPollers({ queue: taskQueue, namespace }) then workers} + + {/await} +{:else} + +{/if} diff --git a/src/lib/components/task-queue/worker-table-cell.svelte b/src/lib/components/workers/workers-table/workers-table-cell.svelte similarity index 96% rename from src/lib/components/task-queue/worker-table-cell.svelte rename to src/lib/components/workers/workers-table/workers-table-cell.svelte index b16614216a..7ce99217e0 100644 --- a/src/lib/components/task-queue/worker-table-cell.svelte +++ b/src/lib/components/workers/workers-table/workers-table-cell.svelte @@ -10,7 +10,8 @@ interface Props { attribute?: string; - value: string; + copyable?: boolean; + value?: string; filterable?: boolean; href?: string; children?: Snippet; @@ -18,6 +19,7 @@ let { attribute, + copyable = true, value, filterable = false, href, @@ -83,6 +85,7 @@ show={filterOrCopyButtonsVisible} content={value} onFilter={onRowFilterClick} + {copyable} {filterable} filtered={query.includes(`${attribute}=`)} /> diff --git a/src/lib/components/task-queue/worker-table-row.svelte b/src/lib/components/workers/workers-table/workers-table-row.svelte similarity index 70% rename from src/lib/components/task-queue/worker-table-row.svelte rename to src/lib/components/workers/workers-table/workers-table-row.svelte index 0b4f2b901e..2c73ec3326 100644 --- a/src/lib/components/task-queue/worker-table-row.svelte +++ b/src/lib/components/workers/workers-table/workers-table-row.svelte @@ -1,12 +1,12 @@ + +{#key query} + +{/key} diff --git a/src/lib/components/workers/workers-table/workers-table.svelte b/src/lib/components/workers/workers-table/workers-table.svelte new file mode 100644 index 0000000000..64298ddac0 --- /dev/null +++ b/src/lib/components/workers/workers-table/workers-table.svelte @@ -0,0 +1,58 @@ + + + + + {translate('workers.workers')} + + + + {#each columns as { label } (label)} + {label} + {/each} + + {#each visibleItems as worker (worker.workerHeartbeat.workerInstanceKey)} + + {/each} + + + + + diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index 13d3fc60d2..4234322f3a 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -19,7 +19,7 @@ export const Strings = { instance: 'Instance', 'task-queue': 'Task Queue', status: 'Status', - sdk: 'SDK', + sdk: 'Worker SDK', 'empty-state-title': 'No Workers Found', 'error-message-fetching': 'An error occurred while fetching workers.', 'compatible-build-ids': 'Compatible Build IDs', diff --git a/src/lib/i18n/locales/en/workflows.ts b/src/lib/i18n/locales/en/workflows.ts index d06ea88181..3b920e0708 100644 --- a/src/lib/i18n/locales/en/workflows.ts +++ b/src/lib/i18n/locales/en/workflows.ts @@ -348,4 +348,5 @@ export const Strings = { 'timeline-minimized': 'Timeline and Event History are collapsed to minimized height', 'timeline-expanded': 'Timeline and Event History are expanded to full height', + sdk: 'Workflow SDK', } as const; diff --git a/src/lib/models/worker-status.ts b/src/lib/models/worker-status.ts new file mode 100644 index 0000000000..28892d4024 --- /dev/null +++ b/src/lib/models/worker-status.ts @@ -0,0 +1,13 @@ +export type WorkerStatus = 'Unspecified' | 'Running' | 'Shutting Down' | null; + +export type WorkerFilters = readonly (WorkerStatus | 'All')[]; + +export const workerStatuses: readonly WorkerStatus[] = [ + 'Running', + 'Shutting Down', +] as const; + +export const workerStatusFilters: WorkerFilters = [ + 'All', + ...workerStatuses, +] as const; diff --git a/src/lib/services/worker-service.ts b/src/lib/services/worker-service.ts index 8d3c3efbd7..db70f87f42 100644 --- a/src/lib/services/worker-service.ts +++ b/src/lib/services/worker-service.ts @@ -27,36 +27,11 @@ export const fetchPaginatedWorkers = async ( query: parameters.query, }, }).then(({ workersInfo, nextPageToken }) => { - if (!workersInfo) { - throw new Error('No workers info in response'); - } + // if (!workersInfo) { + // throw new Error('No workers info in response'); + // } return { - items: workersInfo, - nextPageToken: nextPageToken ? String(nextPageToken) : '', - }; - }); - }; -}; - -export const fetchPaginatedWorkersForTaskQueue = async ( - parameters: ListWorkersRequest & { taskQueue: string }, - request = fetch, -): Promise => { - return (pageSize = 100, token = '') => { - const route = routeForApi('workers', parameters); - return requestFromAPI(route, { - request, - params: { - query: `TaskQueue="${parameters.taskQueue}"`, - maximumPageSize: String(pageSize), - nextPageToken: token, - }, - }).then(({ workersInfo, nextPageToken }) => { - if (!workersInfo) { - throw new Error('No workers info in response'); - } - return { - items: workersInfo, + items: workersInfo ?? [], nextPageToken: nextPageToken ? String(nextPageToken) : '', }; }); diff --git a/src/lib/stores/filters.ts b/src/lib/stores/filters.ts index 7eaccadd83..46c125b439 100644 --- a/src/lib/stores/filters.ts +++ b/src/lib/stores/filters.ts @@ -84,6 +84,21 @@ export const activityFilters = writable( updateActivityFilters, ); +const updateWorkerFilters: StartStopNotifier = ( + set, +) => { + return parameters.subscribe(({ query }) => { + if (!query && get(workerFilters).length) { + set([]); + } + }); +}; + +export const workerFilters = writable( + [], + updateWorkerFilters, +); + const updateEventCategoryFilter: StartStopNotifier< EventTypeCategory[] | null > = (set) => { diff --git a/src/lib/stores/search-attributes.ts b/src/lib/stores/search-attributes.ts index a7939e1f01..df21b05633 100644 --- a/src/lib/stores/search-attributes.ts +++ b/src/lib/stores/search-attributes.ts @@ -1,4 +1,4 @@ -import { derived, get, type Readable, writable } from 'svelte/store'; +import { derived, get, readable, type Readable, writable } from 'svelte/store'; import { z } from 'zod/v3'; @@ -191,6 +191,30 @@ export const sortedSearchAttributeOptions: Readable = }); }); +export const workerSearchAttributes: Readable = readable({ + WorkerStatus: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + WorkerInstanceKey: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + WorkerIdentity: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + HostName: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + TaskQueue: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + StartTime: SEARCH_ATTRIBUTE_TYPE.DATETIME, + LastHeartbeatTime: SEARCH_ATTRIBUTE_TYPE.DATETIME, + DeploymentName: SEARCH_ATTRIBUTE_TYPE.DATETIME, + BuildId: SEARCH_ATTRIBUTE_TYPE.DATETIME, + SdkName: SEARCH_ATTRIBUTE_TYPE.DATETIME, + SdkVersion: SEARCH_ATTRIBUTE_TYPE.DATETIME, +}); + +export const workerSearchAttributeOptions: Readable = + derived(workerSearchAttributes, ($workerSearchAttributes) => { + return Object.entries($workerSearchAttributes).map(([key, value]) => { + return { + label: key, + value: key, + type: value, + }; + }); + }); export const activitySearchAttributeOptions: Readable = derived(customSearchAttributeOptions, ($customSearchAttributeOptions) => { return [ diff --git a/src/lib/utilities/get-sdk-version.ts b/src/lib/utilities/get-sdk-version.ts index 182ca4f948..a5f99dda4d 100644 --- a/src/lib/utilities/get-sdk-version.ts +++ b/src/lib/utilities/get-sdk-version.ts @@ -2,6 +2,18 @@ import type { WorkflowTaskCompletedEvent } from '$lib/types/events'; import { capitalize } from './format-camel-case'; +export const formatSDKName = (sdkName: string | undefined): string => { + let sdk = ''; + if (!sdkName) return sdk; + + sdk = capitalize(sdkName.split('-')[1]); + if (sdk === 'Dotnet') { + sdk = '.NET'; + } + + return sdk; +}; + export const getSDKandVersion = ( tasks: WorkflowTaskCompletedEvent[], ): { sdk: string; version: string } => { @@ -16,10 +28,7 @@ export const getSDKandVersion = ( const sdkName = sdkMetadata?.sdkName; const sdkVersion = sdkMetadata?.sdkVersion; if (sdkName) { - sdk = capitalize(sdkName.split('-')[1]); - if (sdk === 'Dotnet') { - sdk = '.NET'; - } + sdk = formatSDKName(sdkName); } if (sdkVersion) { version = sdkVersion; diff --git a/src/lib/utilities/screaming-enums.ts b/src/lib/utilities/screaming-enums.ts index 8de7139654..e11ef7ff63 100644 --- a/src/lib/utilities/screaming-enums.ts +++ b/src/lib/utilities/screaming-enums.ts @@ -1,3 +1,4 @@ +import type { WorkerStatus as ReadableWorkerStatus } from '$lib/models/worker-status'; import type { ArchivalState, CallbackState, @@ -100,8 +101,6 @@ export const toCallbackStateReadable = ( return fromScreamingEnum(state, 'CallbackState'); }; -export type ReadableWorkerStatus = 'Unspecified' | 'Running' | 'Shutting Down'; - export const toWorkerStatusReadable = ( state?: WorkerStatus, ): ReadableWorkerStatus => { diff --git a/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte index 6d43a1e512..ab82f1c928 100644 --- a/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte @@ -1,12 +1,21 @@ @@ -43,4 +59,11 @@
- + + + From cb9bda3602290902847ad14c604f602d8fc0a99d Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Thu, 5 Mar 2026 10:57:13 -0700 Subject: [PATCH 12/40] Update no workers polling alert --- src/lib/components/worker-table.svelte | 2 +- .../workers/no-workers-polling-alert.svelte | 34 +++++++++++++++++++ .../components/workers/worker-status.svelte | 4 +-- .../workflow/workflow-call-stack-error.svelte | 27 --------------- src/lib/i18n/locales/en/workers.ts | 1 + src/lib/i18n/locales/en/workflows.ts | 7 ++-- src/lib/layouts/workflow-header.svelte | 2 -- src/lib/models/worker-status.ts | 4 +-- src/lib/pages/workflow-workers.svelte | 5 --- .../[workflow]/[run]/workers/+page.svelte | 6 ++-- 10 files changed, 48 insertions(+), 44 deletions(-) create mode 100644 src/lib/components/workers/no-workers-polling-alert.svelte delete mode 100644 src/lib/components/workflow/workflow-call-stack-error.svelte delete mode 100644 src/lib/pages/workflow-workers.svelte diff --git a/src/lib/components/worker-table.svelte b/src/lib/components/worker-table.svelte index 52737720ca..2af0455b96 100644 --- a/src/lib/components/worker-table.svelte +++ b/src/lib/components/worker-table.svelte @@ -178,7 +178,7 @@ {:else} - + {/each} diff --git a/src/lib/components/workers/no-workers-polling-alert.svelte b/src/lib/components/workers/no-workers-polling-alert.svelte new file mode 100644 index 0000000000..ce450c7c87 --- /dev/null +++ b/src/lib/components/workers/no-workers-polling-alert.svelte @@ -0,0 +1,34 @@ + + + diff --git a/src/lib/components/workers/worker-status.svelte b/src/lib/components/workers/worker-status.svelte index eadc2815a3..dab8c099f1 100644 --- a/src/lib/components/workers/worker-status.svelte +++ b/src/lib/components/workers/worker-status.svelte @@ -14,7 +14,7 @@ const label: Record = { Running: translate('workflows.running'), - 'Shutting Down': 'Shutting Down', + ShuttingDown: 'Shutting Down', Unspecified: translate('events.event-classification.unspecified'), }; @@ -27,7 +27,7 @@ status: { Unspecified: 'bg-slate-100', Running: 'bg-blue-300', - 'Shutting Down': 'bg-yellow-200', + ShuttingDown: 'bg-yellow-200', }, }, }, diff --git a/src/lib/components/workflow/workflow-call-stack-error.svelte b/src/lib/components/workflow/workflow-call-stack-error.svelte deleted file mode 100644 index 9cb6992968..0000000000 --- a/src/lib/components/workflow/workflow-call-stack-error.svelte +++ /dev/null @@ -1,27 +0,0 @@ - - -{#if runningWithNoWorkers} -
- - {translate('workflows.workflow-error-no-workers-description', { - taskQueue: workflow?.taskQueue ?? '', - })} - -
-{/if} diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index 4234322f3a..5ec0fe909c 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -63,4 +63,5 @@ export const Strings = { diagnostics: 'Diagnostics', 'poll-success-rate': 'Poll success rate', 'rate-limit': 'Rate limit', + 'troubleshooting-workers-link': 'Troubleshooting workers', } as const; diff --git a/src/lib/i18n/locales/en/workflows.ts b/src/lib/i18n/locales/en/workflows.ts index 3b920e0708..27494b8c70 100644 --- a/src/lib/i18n/locales/en/workflows.ts +++ b/src/lib/i18n/locales/en/workflows.ts @@ -155,9 +155,11 @@ export const Strings = { 'workflow-404-title': 'This is not the Workflow you are looking for', 'workflow-error-title': 'We are having technical difficulties retrieving this Workflow', - 'workflow-error-no-workers-title': 'No Workers Running', + 'workflow-error-no-workers-title': 'No workers polling', 'workflow-error-no-workers-description': - 'There are no Workers polling the {{taskQueue}} Task Queue.', + 'There are no workers polling the {{taskQueue}} task queue.', + 'workers-alert-description': + 'Check your deployment or orchestration system (Kubernetes, ECS, etc.). Review worker logs for crash information. Restart or redeploy workers.', 'workflow-error-no-compatible-workers-title': 'No Compatible Workers Running', 'workflow-error-no-compatible-workers-description': 'There are no compatible Workers polling the {{taskQueue}} Task Queue.', @@ -203,7 +205,6 @@ export const Strings = { 'workflow-task-handler': 'Workflow Task Handler', 'activity-handler': 'Activity Handler', 'nexus-handler': 'Nexus Handler', - 'workers-empty-state': 'No Workers Found', 'call-stack-empty-state': 'No Call Stacks Found', 'no-workers-failure-message': 'This will fail if you have no workers running.', diff --git a/src/lib/layouts/workflow-header.svelte b/src/lib/layouts/workflow-header.svelte index 410c7104e4..ea9ab871cd 100644 --- a/src/lib/layouts/workflow-header.svelte +++ b/src/lib/layouts/workflow-header.svelte @@ -6,7 +6,6 @@ import CodecServerErrorBanner from '$lib/components/codec-server-error-banner.svelte'; import WorkflowDetails from '$lib/components/lines-and-dots/workflow-details.svelte'; import { timestamp } from '$lib/components/timestamp.svelte'; - import WorkflowCallStackError from '$lib/components/workflow/workflow-call-stack-error.svelte'; import WorkflowActions from '$lib/components/workflow-actions.svelte'; import WorkflowStatus from '$lib/components/workflow-status.svelte'; import Alert from '$lib/holocene/alert.svelte'; @@ -175,7 +174,6 @@
- {#if cancelInProgress}
- import TaskQueueWorkerInsights from '$lib/components/task-queue/worker-insights.svelte'; - - - diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte index 2464a7bb63..a48be447a8 100644 --- a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte @@ -2,8 +2,9 @@ import { page } from '$app/state'; import PageTitle from '$lib/components/page-title.svelte'; + import TaskQueueWorkerInsights from '$lib/components/task-queue/worker-insights.svelte'; + import NoWorkersPollingAlert from '$lib/components/workers/no-workers-polling-alert.svelte'; import { translate } from '$lib/i18n/translate'; - import WorkflowWorkers from '$lib/pages/workflow-workers.svelte'; const workflowId = $derived(page.params.workflow); @@ -12,4 +13,5 @@ title={`${translate('workflows.workers-tab')} | ${workflowId}`} url={page.url.href} /> - + + From abd41a2f8ff2411fb6ddec29167aac3d98d4c073 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Thu, 5 Mar 2026 17:13:09 -0700 Subject: [PATCH 13/40] Worker details page updates --- .../components/task-queue/worker-info.svelte | 248 +++++++++--------- src/lib/holocene/icon/paths.ts | 2 + src/lib/holocene/icon/svg/microchip.svelte | 11 + src/lib/i18n/locales/en/workers.ts | 4 +- src/lib/layouts/workflow-run-layout.svelte | 1 - .../workers/[workerInstanceKey]/+page.svelte | 2 +- 6 files changed, 136 insertions(+), 132 deletions(-) create mode 100644 src/lib/holocene/icon/svg/microchip.svelte diff --git a/src/lib/components/task-queue/worker-info.svelte b/src/lib/components/task-queue/worker-info.svelte index 7f5bc0fa1c..b7fe8514a0 100644 --- a/src/lib/components/task-queue/worker-info.svelte +++ b/src/lib/components/task-queue/worker-info.svelte @@ -1,25 +1,30 @@
-
-
- -

{heartbeat?.workerIdentity}

-
+
+ +

+ +

+
-
-
- {translate('common.task-queue')} - - {heartbeat?.taskQueue} - -
-
- {translate('common.start')} - -
-
- {translate('workflows.last-heartbeat')} -
- - - {formatDurationAbbreviated( - String(heartbeat?.elapsedSinceLastHeartbeat), - )} ago - -
-
-
- {translate('workers.sdk')} + + + {translate('workflows.last-heartbeat')} + + + + {translate('common.start')} + + + + + {translate('common.task-queue')} + + + + {translate('workers.sdk')} + -
-
-
+ + +
@@ -123,7 +138,6 @@
{@render hostInfo()} - {@render hostUsage()} {@render workflowCache()} {@render diagnostics()}
@@ -143,12 +157,14 @@
-
{translate('workers.slots')}
-
-

+

+ {translate('workers.slots')} +
+
+

{slots.currentUsedSlots ?? 0}

-

+

{#if slots.currentAvailableSlots} {slots.currentAvailableSlots - slots.currentUsedSlots || 0} {:else} @@ -156,7 +172,7 @@ {/if}

-
+

{translate('workers.used')}

{#if slots.currentAvailableSlots} @@ -171,23 +187,23 @@

-
+
{translate('workers.tasks-processed')}
- + {(slots.totalProcessedTasks ?? 0).toLocaleString()}
{#if poller}
-
- Poller +
+ {translate('workers.poller')} {poller.isAutoscaling ? 'Autoscaling' : 'Manual'}
- + {poller.currentPollers ?? 0}
@@ -205,86 +221,68 @@ {/snippet} {#snippet hostInfo()} - -

{translate('workers.host-info')}

-
-
- {translate('workers.host-name')} - - {heartbeat?.hostInfo?.hostName} - -
-
- {translate('workers.process-id')} - {heartbeat?.hostInfo?.processId} -
-
- {translate('workers.instance')} - - {heartbeat?.workerInstanceKey} - -
-
- {translate('workers.worker-grouping')} - - {heartbeat?.hostInfo?.workerGroupingKey} - -
-
-
-{/snippet} +
+ +

+ {translate('workers.host-info')} +

+
+
{translate('workers.host-name')}
+
{heartbeat?.hostInfo?.hostName}
-{#snippet hostUsage()} - -
-

{translate('workers.host-usage')}

-
-
+
{translate('workers.process-id')}
+
{heartbeat?.hostInfo?.processId}
+ +
+ {translate('workers.worker-grouping')} +
+
{heartbeat?.hostInfo?.workerGroupingKey}
+
+
+
- - + + {translate('workers.cpu-usage')} {heartbeat?.hostInfo?.currentHostCpuUsage.toFixed(0)}%
-
+
- + {translate('workers.memory-usage')} {heartbeat?.hostInfo?.currentHostMemUsage.toFixed(0)}%
-
+
-
- + +
{/snippet} {#snippet workflowCache()} -

+

{translate('workers.workflow-cache')}

@@ -292,7 +290,7 @@ {currentStickyCacheSize.toLocaleString()} -
+
{translate('workers.cache-size')}
@@ -300,16 +298,14 @@ {cacheHitRate}% -
+
{translate('workers.cache-hits')}
- - {totalStickyCacheHit.toLocaleString()} - -
+ - +
{translate('workers.active-thread-count')}
@@ -318,23 +314,19 @@ {#snippet diagnostics()} -

+

{translate('workers.diagnostics')}

- - {cacheHitRate}% - -
+ - % +
{translate('workers.poll-success-rate')}
- {translate('common.none')} -
+ {translate('common.none')} +
{translate('workers.rate-limit')}
diff --git a/src/lib/holocene/icon/paths.ts b/src/lib/holocene/icon/paths.ts index 9a4b81c346..56b32118cf 100644 --- a/src/lib/holocene/icon/paths.ts +++ b/src/lib/holocene/icon/paths.ts @@ -83,6 +83,7 @@ import lock from './svg/lock.svelte'; import logout from './svg/logout.svelte'; import marker from './svg/marker.svelte'; import merge from './svg/merge.svelte'; +import microchip from './svg/microchip.svelte'; import microsoft from './svg/microsoft.svelte'; import minimize from './svg/minimize.svelte'; import moon from './svg/moon.svelte'; @@ -227,6 +228,7 @@ export const icons = { logout, marker, merge, + microchip, microsoft, minimize, moon, diff --git a/src/lib/holocene/icon/svg/microchip.svelte b/src/lib/holocene/icon/svg/microchip.svelte new file mode 100644 index 0000000000..adb80b665a --- /dev/null +++ b/src/lib/holocene/icon/svg/microchip.svelte @@ -0,0 +1,11 @@ + + + + + diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index 5ec0fe909c..876e1eb352 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -45,7 +45,7 @@ export const Strings = { 'local-activities': 'Local Activities', slots: 'Slots', used: 'Used', - 'available-out-of': 'Available out of {count}', + 'available-out-of': 'Available out of {{count}}', 'none-available': 'None Available', 'tasks-processed': 'Tasks Processed', 'last-poll': 'Last Poll', @@ -53,7 +53,6 @@ export const Strings = { 'host-info': 'Host Info', 'process-id': 'Process ID', 'worker-grouping': 'Worker Grouping', - 'host-usage': 'Host Usage', 'cpu-usage': 'CPU Usage', 'memory-usage': 'Memory Usage', 'workflow-cache': 'Workflow Cache', @@ -64,4 +63,5 @@ export const Strings = { 'poll-success-rate': 'Poll success rate', 'rate-limit': 'Rate limit', 'troubleshooting-workers-link': 'Troubleshooting workers', + poller: 'Poller', } as const; diff --git a/src/lib/layouts/workflow-run-layout.svelte b/src/lib/layouts/workflow-run-layout.svelte index 452745e34b..152c7591e0 100644 --- a/src/lib/layouts/workflow-run-layout.svelte +++ b/src/lib/layouts/workflow-run-layout.svelte @@ -8,7 +8,6 @@ import SkeletonWorkflow from '$lib/holocene/skeleton/workflow.svelte'; import { translate } from '$lib/i18n/translate'; import WorkflowHeader from '$lib/layouts/workflow-header.svelte'; - import { Action } from '$lib/models/workflow-actions'; import { fetchAllEvents, throttleRefresh, diff --git a/src/routes/(app)/namespaces/[namespace]/workers/[workerInstanceKey]/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workers/[workerInstanceKey]/+page.svelte index c282389472..c9a9327069 100644 --- a/src/routes/(app)/namespaces/[namespace]/workers/[workerInstanceKey]/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workers/[workerInstanceKey]/+page.svelte @@ -10,7 +10,7 @@ import { routeForWorkers } from '$lib/utilities/route-for'; const namespace = $derived(page.params.namespace); - const workerInstanceKey = $derived(page.params.instance); + const workerInstanceKey = $derived(page.params.workerInstanceKey); Date: Thu, 5 Mar 2026 17:38:30 -0700 Subject: [PATCH 14/40] Add Go dependency warning for usage stats --- .../components/task-queue/worker-info.svelte | 106 +++++++++++------- src/lib/i18n/locales/en/workers.ts | 4 + 2 files changed, 72 insertions(+), 38 deletions(-) diff --git a/src/lib/components/task-queue/worker-info.svelte b/src/lib/components/task-queue/worker-info.svelte index b7fe8514a0..df851b4516 100644 --- a/src/lib/components/task-queue/worker-info.svelte +++ b/src/lib/components/task-queue/worker-info.svelte @@ -11,10 +11,12 @@ import Timestamp from '$lib/components/timestamp.svelte'; import { timestamp } from '$lib/components/timestamp.svelte'; import WorkerStatus from '$lib/components/workers/worker-status.svelte'; + import Alert from '$lib/holocene/alert.svelte'; import Badge from '$lib/holocene/badge.svelte'; import Card from '$lib/holocene/card.svelte'; import Copyable from '$lib/holocene/copyable/index.svelte'; import Icon from '$lib/holocene/icon/icon.svelte'; + import Link from '$lib/holocene/link.svelte'; import { translate } from '$lib/i18n/translate'; import type { WorkerInfo, @@ -40,6 +42,13 @@ const currentStickyCacheSize = $derived( heartbeat?.currentStickyCacheSize ?? 0, ); + const isGoSdk = $derived(heartbeat?.sdkName?.startsWith('temporal-go')); + const goDependencyPotentiallyMissing = $derived( + isGoSdk + ? !heartbeat?.hostInfo?.currentHostCpuUsage && + !heartbeat?.hostInfo?.currentHostMemUsage + : false, + ); const cacheHitRate = $derived.by(() => { const total = totalStickyCacheHit + totalStickyCacheMiss; @@ -220,6 +229,64 @@ {/snippet} +{#snippet goDependencyWarning()} + +

{translate('workers.go-dependency-warning-description')}

+ + +
+{/snippet} + +{#snippet hostUsage()} + +
+
+ + + {translate('workers.cpu-usage')} + + {heartbeat?.hostInfo?.currentHostCpuUsage.toFixed(0)}% +
+
+
+
+
+
+
+ + + {translate('workers.memory-usage')} + + {heartbeat?.hostInfo?.currentHostMemUsage.toFixed(0)}% +
+
+
+
+
+ {#if goDependencyPotentiallyMissing} + {@render goDependencyWarning()} + {/if} +
+{/snippet} + {#snippet hostInfo()}
@@ -239,44 +306,7 @@
{heartbeat?.hostInfo?.workerGroupingKey}
- -
-
- - - {translate('workers.cpu-usage')} - - {heartbeat?.hostInfo?.currentHostCpuUsage.toFixed(0)}% -
-
-
-
-
-
-
- - - {translate('workers.memory-usage')} - - {heartbeat?.hostInfo?.currentHostMemUsage.toFixed(0)}% -
-
-
-
-
-
+ {@render hostUsage()}
{/snippet} diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index 876e1eb352..e4135e7491 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -64,4 +64,8 @@ export const Strings = { 'rate-limit': 'Rate limit', 'troubleshooting-workers-link': 'Troubleshooting workers', poller: 'Poller', + 'go-dependency-warning': 'Potential missing dependency', + 'go-dependency-warning-description': + 'This worker is not showing any usage. You may need to enable a dependency for the Go SDK in order to see this data.', + 'go-dependency-warning-link': 'Learn more.', } as const; From feab31c420fe5e9a003ce270ce1665e5e964a077 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Thu, 12 Mar 2026 18:02:04 -0600 Subject: [PATCH 15/40] Add poll success rate --- .../components/task-queue/worker-info.svelte | 18 +++++++++++------- src/lib/i18n/locales/en/workers.ts | 1 - 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/lib/components/task-queue/worker-info.svelte b/src/lib/components/task-queue/worker-info.svelte index df851b4516..72f1268f0e 100644 --- a/src/lib/components/task-queue/worker-info.svelte +++ b/src/lib/components/task-queue/worker-info.svelte @@ -55,6 +55,16 @@ if (total === 0) return 0; return ((totalStickyCacheHit / total) * 100).toFixed(1); }); + const pollSuccessRate = $derived.by(() => { + if (!heartbeat?.workflowTaskSlotsInfo?.totalProcessedTasks) return 0; + const successCount = + heartbeat.workflowTaskSlotsInfo.totalProcessedTasks - + (heartbeat.workflowTaskSlotsInfo.totalFailedTasks ?? 0); + return ( + (successCount / heartbeat.workflowTaskSlotsInfo.totalProcessedTasks) * + 100 + ).toFixed(1); + });
@@ -333,12 +343,6 @@
-
- - -
- {translate('workers.active-thread-count')} -
-
{/snippet} @@ -349,7 +353,7 @@
- - % + {pollSuccessRate}%
{translate('workers.poll-success-rate')}
diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index e4135e7491..c14b4d6b72 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -58,7 +58,6 @@ export const Strings = { 'workflow-cache': 'Workflow Cache', 'cache-size': 'Cache size', 'cache-hits': 'Cache hits', - 'active-thread-count': 'Active thread count', diagnostics: 'Diagnostics', 'poll-success-rate': 'Poll success rate', 'rate-limit': 'Rate limit', From 081e4e43556bdce6fc1d60b14ffa672ad72e00e9 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Thu, 12 Mar 2026 18:42:28 -0600 Subject: [PATCH 16/40] Update nav to workers --- .../workers-table-with-search.svelte | 2 +- src/lib/holocene/icon/paths.ts | 2 ++ src/lib/holocene/icon/svg/workers.svelte | 19 +++++++++++++++++++ src/routes/(app)/+layout.svelte | 6 +++--- 4 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 src/lib/holocene/icon/svg/workers.svelte diff --git a/src/lib/components/workers/workers-table/workers-table-with-search.svelte b/src/lib/components/workers/workers-table/workers-table-with-search.svelte index 1094ec88f8..37aadcfc91 100644 --- a/src/lib/components/workers/workers-table/workers-table-with-search.svelte +++ b/src/lib/components/workers/workers-table/workers-table-with-search.svelte @@ -15,6 +15,6 @@ const onFetch = $derived(() => fetchPaginatedWorkers({ namespace, query })); -{#key query} +{#key [namespace, query]} {/key} diff --git a/src/lib/holocene/icon/paths.ts b/src/lib/holocene/icon/paths.ts index 56b32118cf..fddfed1042 100644 --- a/src/lib/holocene/icon/paths.ts +++ b/src/lib/holocene/icon/paths.ts @@ -138,6 +138,7 @@ import usage from './svg/usage.svelte'; import userGroup from './svg/user-group.svelte'; import verticalEllipsis from './svg/vertical-ellipsis.svelte'; import warning from './svg/warning.svelte'; +import workers from './svg/workers.svelte'; import workflow from './svg/workflow.svelte'; import xmarkFilled from './svg/xmark-filled.svelte'; import xmarkSquare from './svg/xmark-square.svelte'; @@ -284,6 +285,7 @@ export const icons = { 'vertical-ellipsis': verticalEllipsis, warning, 'system-window': systemWindow, + workers, workflow, 'xmark-filled': xmarkFilled, 'xmark-square': xmarkSquare, diff --git a/src/lib/holocene/icon/svg/workers.svelte b/src/lib/holocene/icon/svg/workers.svelte new file mode 100644 index 0000000000..3d6bf907f3 --- /dev/null +++ b/src/lib/holocene/icon/svg/workers.svelte @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 347647ab5f..f0cc9ae222 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -158,9 +158,9 @@ }, { href: workersRoute, - icon: 'merge', - label: translate('deployments.deployments'), - tooltip: translate('deployments.worker-deployments'), + icon: 'workers', + label: translate('workers.workers'), + tooltip: translate('workers.workers'), isActive: (path) => path.includes(workersRoute) || path.includes(workerDeploymentsRoute), }, From b3d2ce182c085ebbfd794286ac18304878c69d5d Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Thu, 12 Mar 2026 18:48:28 -0600 Subject: [PATCH 17/40] Fix type and test --- .../status-filter-chip.svelte | 12 +++++------- src/lib/utilities/route-for-base-path.test.ts | 13 ++++++++++++- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/lib/components/shared-search-attribute-filter/status-filter-chip.svelte b/src/lib/components/shared-search-attribute-filter/status-filter-chip.svelte index c4c6c93fa6..1b359310b4 100644 --- a/src/lib/components/shared-search-attribute-filter/status-filter-chip.svelte +++ b/src/lib/components/shared-search-attribute-filter/status-filter-chip.svelte @@ -18,6 +18,7 @@ workflowStatusFilters, } from '$lib/models/workflow-status'; import type { WorkflowStatus as WorkflowStatusType } from '$lib/types/workflows'; + import { createFilter } from '$lib/utilities/query/to-list-workflow-filters.js'; import type { StatusAttribute } from './types.ts'; @@ -73,26 +74,23 @@ if (localFilters.length === 1 && localFilters[0].value === '') { localFilters = [ - { + createFilter({ attribute, - operator: '', - parenthesis: '', type: 'Keyword', value: status, conditional: '=', - }, + }), ]; } else { localFilters = [ ...localFilters, - { + createFilter({ attribute, - operator: '', parenthesis: localFilters.length ? ')' : '', type: 'Keyword', value: status, conditional: '=', - }, + }), ]; } diff --git a/src/lib/utilities/route-for-base-path.test.ts b/src/lib/utilities/route-for-base-path.test.ts index eefa10464a..da0e52737e 100644 --- a/src/lib/utilities/route-for-base-path.test.ts +++ b/src/lib/utilities/route-for-base-path.test.ts @@ -54,6 +54,7 @@ import { routeForWorkerDeployment, routeForWorkerDeployments, routeForWorkerDeploymentVersion, + routeForWorkerInstance, routeForWorkers, routeForWorkflow, routeForWorkflowMemo, @@ -63,6 +64,7 @@ import { routeForWorkflowStart, routeForWorkflowsWithQuery, routeForWorkflowUpdate, + routeForWorkflowWorkers, } from './route-for'; describe('routeFor functions should resolve the base path exactly once', () => { @@ -108,7 +110,7 @@ describe('routeFor functions should resolve the base path exactly once', () => { ], ['routeForEventHistory', () => routeForEventHistory(workflowParams)], ['routeForTimeline', () => routeForTimeline(workflowParams)], - ['routeForWorkers', () => routeForWorkers(workflowParams)], + ['routeForWorkers', () => routeForWorkers(namespaceParams)], [ 'routeForWorkerDeployments', () => routeForWorkerDeployments(namespaceParams), @@ -201,6 +203,15 @@ describe('routeFor functions should resolve the base path exactly once', () => { }), ], ['routeForLoginPage', () => routeForLoginPage('', false)], + ['routeForWorkflowWorkers', () => routeForWorkflowWorkers(workflowParams)], + [ + 'routeForWorkerInstance', + () => + routeForWorkerInstance({ + namespace: 'default', + workerInstanceKey: 'worker-1', + }), + ], ]; it.each(cases)('%s should resolve the base path', (_name, fn) => { From 657db82424cfed45a1450cfa555521c685108eeb Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Fri, 13 Mar 2026 18:05:32 -0600 Subject: [PATCH 18/40] Routing, page, and component updates --- .../task-queue/worker-insights.svelte | 11 ---- .../workers-table/workers-table-cell.svelte | 36 +++++----- .../workers-table-with-search.svelte | 20 ------ .../layouts/workers-layout.svelte} | 32 +++++---- src/lib/pages/deployment.svelte | 5 +- src/lib/pages/task-queue.svelte | 9 +-- src/lib/pages/worker-details.svelte | 32 +++++++++ src/lib/pages/workers.svelte | 57 ++++++++++++++++ src/lib/services/worker-service.ts | 11 +++- src/lib/utilities/route-for.ts | 4 +- .../task-queues/[queue]/+page.svelte | 10 ++- .../[namespace]/worker-deployments/+page.ts | 13 ++++ .../[deployment]/+page.svelte | 11 ---- .../worker-deployments/[deployment]/+page.ts | 14 ++++ .../[namespace]/workers/+page.svelte | 66 ++----------------- .../workers/[workerInstanceKey]/+page.svelte | 27 +------- .../workers/deployments/+page.svelte | 18 +++++ .../deployments/[deployment]/+page.svelte | 15 +++++ .../[workflow]/[run]/workers/+page.svelte | 8 ++- 19 files changed, 229 insertions(+), 170 deletions(-) delete mode 100644 src/lib/components/task-queue/worker-insights.svelte delete mode 100644 src/lib/components/workers/workers-table/workers-table-with-search.svelte rename src/{routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte => lib/layouts/workers-layout.svelte} (65%) create mode 100644 src/lib/pages/worker-details.svelte create mode 100644 src/lib/pages/workers.svelte create mode 100644 src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.ts delete mode 100644 src/routes/(app)/namespaces/[namespace]/worker-deployments/[deployment]/+page.svelte create mode 100644 src/routes/(app)/namespaces/[namespace]/worker-deployments/[deployment]/+page.ts create mode 100644 src/routes/(app)/namespaces/[namespace]/workers/deployments/+page.svelte create mode 100644 src/routes/(app)/namespaces/[namespace]/workers/deployments/[deployment]/+page.svelte diff --git a/src/lib/components/task-queue/worker-insights.svelte b/src/lib/components/task-queue/worker-insights.svelte deleted file mode 100644 index aba0638192..0000000000 --- a/src/lib/components/task-queue/worker-insights.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/src/lib/components/workers/workers-table/workers-table-cell.svelte b/src/lib/components/workers/workers-table/workers-table-cell.svelte index 7ce99217e0..107e1c7357 100644 --- a/src/lib/components/workers/workers-table/workers-table-cell.svelte +++ b/src/lib/components/workers/workers-table/workers-table-cell.svelte @@ -6,7 +6,12 @@ import FilterOrCopyButtons from '$lib/holocene/filter-or-copy-buttons.svelte'; import Link from '$lib/holocene/link.svelte'; import { translate } from '$lib/i18n/translate'; - import { updateQueryParameters } from '$lib/utilities/update-query-parameters'; + import type { SearchAttributeFilter } from '$lib/models/search-attribute-filters'; + import { workerFilters } from '$lib/stores/filters'; + import { + createFilter, + updateQueryParamsFromFilter, + } from '$lib/utilities/query/to-list-workflow-filters'; interface Props { attribute?: string; @@ -28,24 +33,23 @@ const query = $derived(page.url.searchParams.get('query') || ''); - const onRowFilterClick = () => { - if (query.includes(`${attribute}="`)) { - updateQueryParameters({ - parameter: 'query', - value: '', - url: page.url, - clearParameters: [attribute], + const onRowFilterClick = async () => { + const filter = $workerFilters.find((f) => f.attribute === attribute); + const getOtherFilters = () => + $workerFilters.filter((f) => f.attribute !== attribute); + + if (!filter || filter.value !== value) { + const newFilter: SearchAttributeFilter = createFilter({ + attribute, + value, + conditional: '=', }); + $workerFilters = [...getOtherFilters(), newFilter]; } else { - const newQuery = query - ? `${query} AND ${attribute}="${value}"` - : `${attribute}="${value}"`; - updateQueryParameters({ - parameter: 'query', - value: newQuery, - url: page.url, - }); + $workerFilters = [...getOtherFilters()]; } + + updateQueryParamsFromFilter(page.url, $workerFilters); }; let filterOrCopyButtonsVisible = $state(false); diff --git a/src/lib/components/workers/workers-table/workers-table-with-search.svelte b/src/lib/components/workers/workers-table/workers-table-with-search.svelte deleted file mode 100644 index 37aadcfc91..0000000000 --- a/src/lib/components/workers/workers-table/workers-table-with-search.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - -{#key [namespace, query]} - -{/key} diff --git a/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte b/src/lib/layouts/workers-layout.svelte similarity index 65% rename from src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte rename to src/lib/layouts/workers-layout.svelte index 266a412b77..f60c7a714b 100644 --- a/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte +++ b/src/lib/layouts/workers-layout.svelte @@ -1,31 +1,33 @@ -

- {translate('deployments.worker-deployments')} + {translate('workers.workers')}

@@ -34,16 +36,24 @@ label={translate('workers.workers')} id="workers-tab" href={workersHref} - active={false} + active={pathMatches( + page.url.pathname, + routeForWorkers({ namespace }), + true, + )} />
- +{@render children()} diff --git a/src/lib/pages/deployment.svelte b/src/lib/pages/deployment.svelte index 6d77a8407f..9a72afb4e0 100644 --- a/src/lib/pages/deployment.svelte +++ b/src/lib/pages/deployment.svelte @@ -3,6 +3,7 @@ import VersionTableRow from '$lib/components/deployments/version-table-row.svelte'; import Button from '$lib/holocene/button.svelte'; + import Error from '$lib/holocene/error.svelte'; import Icon from '$lib/holocene/icon/icon.svelte'; import Link from '$lib/holocene/link.svelte'; import SkeletonTable from '$lib/holocene/skeleton/table.svelte'; @@ -72,7 +73,7 @@ {translate('deployments.deployments')} - {#each columns as { label }} + {#each columns as { label } (label)} {label} {/each} @@ -84,5 +85,7 @@ /> {/each} + {:catch error} + {/await}
diff --git a/src/lib/pages/task-queue.svelte b/src/lib/pages/task-queue.svelte index b9297f4008..c7ae3fbc43 100644 --- a/src/lib/pages/task-queue.svelte +++ b/src/lib/pages/task-queue.svelte @@ -1,15 +1,16 @@
-

Task Queue

+

{translate('workers.task-queue')}

{taskQueue}

- +
diff --git a/src/lib/pages/worker-details.svelte b/src/lib/pages/worker-details.svelte new file mode 100644 index 0000000000..bed56dd66b --- /dev/null +++ b/src/lib/pages/worker-details.svelte @@ -0,0 +1,32 @@ + + +
+
+
+ + {translate('workers.view-all-workers')} + +
+
+
+{#await describeWorker({ namespace, workerInstanceKey }) then data} + +{:catch error} + +{/await} diff --git a/src/lib/pages/workers.svelte b/src/lib/pages/workers.svelte new file mode 100644 index 0000000000..8b84cc2e8b --- /dev/null +++ b/src/lib/pages/workers.svelte @@ -0,0 +1,57 @@ + + +{#if showAlert} + + + +{:else} + + + {#key [namespace, query]} + + {/key} +{/if} diff --git a/src/lib/services/worker-service.ts b/src/lib/services/worker-service.ts index db70f87f42..8a13b06b8a 100644 --- a/src/lib/services/worker-service.ts +++ b/src/lib/services/worker-service.ts @@ -27,9 +27,14 @@ export const fetchPaginatedWorkers = async ( query: parameters.query, }, }).then(({ workersInfo, nextPageToken }) => { - // if (!workersInfo) { - // throw new Error('No workers info in response'); - // } + if (!workersInfo) { + // TODO: Verify if we want this behavior + throw { + statusCode: 501, + statusText: 'Not Implemented', + message: 'No workers info in response', + }; + } return { items: workersInfo ?? [], nextPageToken: nextPageToken ? String(nextPageToken) : '', diff --git a/src/lib/utilities/route-for.ts b/src/lib/utilities/route-for.ts index 9d457c5667..9b49a37ca2 100644 --- a/src/lib/utilities/route-for.ts +++ b/src/lib/utilities/route-for.ts @@ -306,7 +306,7 @@ export const routeForWorkerDeployments = ({ }: { namespace: string; }): ResolvedPathname => { - return resolve('/namespaces/[namespace]/worker-deployments', { + return resolve('/namespaces/[namespace]/workers/deployments', { namespace, }); }; @@ -333,7 +333,7 @@ export const routeForWorkerDeployment = ({ deployment: string; }): ResolvedPathname => { const deploymentName = encodeURIForSvelte(deployment); - return resolve('/namespaces/[namespace]/worker-deployments/[deployment]', { + return resolve('/namespaces/[namespace]/workers/deployments/[deployment]', { namespace, deployment: deploymentName, }); diff --git a/src/routes/(app)/namespaces/[namespace]/task-queues/[queue]/+page.svelte b/src/routes/(app)/namespaces/[namespace]/task-queues/[queue]/+page.svelte index fb2e8c670e..58707e8ab1 100644 --- a/src/routes/(app)/namespaces/[namespace]/task-queues/[queue]/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/task-queues/[queue]/+page.svelte @@ -1,11 +1,15 @@ - + diff --git a/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.ts b/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.ts new file mode 100644 index 0000000000..0f968b2fbb --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.ts @@ -0,0 +1,13 @@ +import { redirect } from '@sveltejs/kit'; + +import type { PageLoad } from './$types'; + +import { routeForWorkerDeployments } from '$lib/utilities/route-for'; + +export const load: PageLoad = async function ({ params }) { + const { namespace } = params; + const route = routeForWorkerDeployments({ + namespace, + }); + redirect(301, route); +}; diff --git a/src/routes/(app)/namespaces/[namespace]/worker-deployments/[deployment]/+page.svelte b/src/routes/(app)/namespaces/[namespace]/worker-deployments/[deployment]/+page.svelte deleted file mode 100644 index afc428d7e4..0000000000 --- a/src/routes/(app)/namespaces/[namespace]/worker-deployments/[deployment]/+page.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/src/routes/(app)/namespaces/[namespace]/worker-deployments/[deployment]/+page.ts b/src/routes/(app)/namespaces/[namespace]/worker-deployments/[deployment]/+page.ts new file mode 100644 index 0000000000..60bb507ac2 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/worker-deployments/[deployment]/+page.ts @@ -0,0 +1,14 @@ +import { redirect } from '@sveltejs/kit'; + +import type { PageLoad } from './$types'; + +import { routeForWorkerDeployment } from '$lib/utilities/route-for'; + +export const load: PageLoad = async function ({ params }) { + const { namespace, deployment } = params; + const route = routeForWorkerDeployment({ + namespace, + deployment, + }); + redirect(301, route); +}; diff --git a/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte index ab82f1c928..6ce36a4496 100644 --- a/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte @@ -1,69 +1,15 @@ -
-
-

- {translate('workers.workers')} -

-
- - - - - - -
- - - - + + + diff --git a/src/routes/(app)/namespaces/[namespace]/workers/[workerInstanceKey]/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workers/[workerInstanceKey]/+page.svelte index c9a9327069..2ecec6b660 100644 --- a/src/routes/(app)/namespaces/[namespace]/workers/[workerInstanceKey]/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workers/[workerInstanceKey]/+page.svelte @@ -2,14 +2,9 @@ import { page } from '$app/state'; import PageTitle from '$lib/components/page-title.svelte'; - import WorkerInfo from '$lib/components/task-queue/worker-info.svelte'; - import Error from '$lib/holocene/error.svelte'; - import Link from '$lib/holocene/link.svelte'; import { translate } from '$lib/i18n/translate'; - import { describeWorker } from '$lib/services/worker-service'; - import { routeForWorkers } from '$lib/utilities/route-for'; + import WorkerDetails from '$lib/pages/worker-details.svelte'; - const namespace = $derived(page.params.namespace); const workerInstanceKey = $derived(page.params.workerInstanceKey); @@ -17,22 +12,4 @@ title="{translate('workers.workers')} | {workerInstanceKey}" url={page.url.href} /> - -
-
-
- - {translate('workers.view-all-workers')} - -
-
-
-{#await describeWorker({ namespace, workerInstanceKey }) then data} - -{:catch error} - -{/await} + diff --git a/src/routes/(app)/namespaces/[namespace]/workers/deployments/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workers/deployments/+page.svelte new file mode 100644 index 0000000000..7c383d347a --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workers/deployments/+page.svelte @@ -0,0 +1,18 @@ + + + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/workers/deployments/[deployment]/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workers/deployments/[deployment]/+page.svelte new file mode 100644 index 0000000000..83c51e6e15 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workers/deployments/[deployment]/+page.svelte @@ -0,0 +1,15 @@ + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte index a48be447a8..7297384e9c 100644 --- a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte @@ -2,11 +2,13 @@ import { page } from '$app/state'; import PageTitle from '$lib/components/page-title.svelte'; - import TaskQueueWorkerInsights from '$lib/components/task-queue/worker-insights.svelte'; import NoWorkersPollingAlert from '$lib/components/workers/no-workers-polling-alert.svelte'; + import WorkersTable from '$lib/components/workers/workers-table/task-queue-workers-table.svelte'; import { translate } from '$lib/i18n/translate'; + import { workflowRun } from '$lib/stores/workflow-run'; - const workflowId = $derived(page.params.workflow); + const { namespace, workflow: workflowId } = $derived(page.params); + const taskQueue = $derived($workflowRun?.workflow?.taskQueue); - + From 42a6a4114ce29ffe55cba2d91dc1061394a2e8a9 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Mon, 16 Mar 2026 16:34:32 -0600 Subject: [PATCH 19/40] UI and accesibility updates --- .../lines-and-dots/workflow-details.svelte | 12 +- .../standalone-activities-disabled.svelte | 1 + .../task-queue/worker-totals.svelte | 65 ----- .../worker-info.svelte | 239 ++++++++++-------- .../components/workers/worker-status.svelte | 2 +- .../task-queue-workers-table.svelte | 26 +- .../worker-heartbeats-sdk-alert.svelte | 64 +++++ .../workers-table/workers-table-cell.svelte | 43 +++- .../workers-table/workers-table-row.svelte | 17 +- .../workers-table/workers-table.svelte | 18 +- src/lib/holocene/icon/svg/workers.svelte | 12 +- src/lib/i18n/locales/en/workers.ts | 11 +- src/lib/layouts/workers-layout.svelte | 2 +- src/lib/layouts/workflow-header.svelte | 10 +- .../pages/standalone-activity-workers.svelte | 15 +- src/lib/pages/worker-details.svelte | 24 +- src/lib/pages/workers.svelte | 38 +-- src/lib/services/worker-service.ts | 8 - src/lib/stores/events.ts | 7 + src/lib/stores/search-attributes.ts | 8 +- src/lib/utilities/route-for-base-path.test.ts | 5 + src/lib/utilities/route-for.ts | 19 +- .../[workflow]/[run]/workers/+page.svelte | 2 - 23 files changed, 353 insertions(+), 295 deletions(-) delete mode 100644 src/lib/components/task-queue/worker-totals.svelte rename src/lib/components/{task-queue => workers}/worker-info.svelte (65%) create mode 100644 src/lib/components/workers/workers-table/worker-heartbeats-sdk-alert.svelte diff --git a/src/lib/components/lines-and-dots/workflow-details.svelte b/src/lib/components/lines-and-dots/workflow-details.svelte index a07d1fa818..8c58992c09 100644 --- a/src/lib/components/lines-and-dots/workflow-details.svelte +++ b/src/lib/components/lines-and-dots/workflow-details.svelte @@ -6,7 +6,7 @@ import { translate } from '$lib/i18n/translate'; import { fetchWorkflow } from '$lib/services/workflow-service'; import { isCloud } from '$lib/stores/advanced-visibility'; - import { fullEventHistory } from '$lib/stores/events'; + import { fullEventHistory, sdkInfo } from '$lib/stores/events'; import { relativeTime, timeFormat, @@ -19,8 +19,6 @@ formatDuration, } from '$lib/utilities/format-time'; import { getBuildIdFromVersion } from '$lib/utilities/get-deployment-build-id'; - import { getSDKandVersion } from '$lib/utilities/get-sdk-version'; - import { isWorkflowTaskCompletedEvent } from '$lib/utilities/is-event-type'; import { routeForSchedule, routeForTaskQueue, @@ -84,13 +82,7 @@ $fullEventHistory.reduce((acc, e) => e.billableActions + acc, 0).toString(), ); - const workflowCompletedTasks = $derived( - $fullEventHistory.filter(isWorkflowTaskCompletedEvent), - ); - - const { sdk, version: sdkVersion } = $derived( - getSDKandVersion(workflowCompletedTasks), - ); + const { sdk, version: sdkVersion } = $derived($sdkInfo); const fetchLatestRun = async () => { const result = await fetchWorkflow({ diff --git a/src/lib/components/standalone-activities/standalone-activities-disabled.svelte b/src/lib/components/standalone-activities/standalone-activities-disabled.svelte index 838cf7b673..71d3a71648 100644 --- a/src/lib/components/standalone-activities/standalone-activities-disabled.svelte +++ b/src/lib/components/standalone-activities/standalone-activities-disabled.svelte @@ -36,6 +36,7 @@ activity.enableStandalone:

{translate('standalone-activities.standalone-activities-enablement')}

diff --git a/src/lib/components/task-queue/worker-totals.svelte b/src/lib/components/task-queue/worker-totals.svelte deleted file mode 100644 index d035fed777..0000000000 --- a/src/lib/components/task-queue/worker-totals.svelte +++ /dev/null @@ -1,65 +0,0 @@ - - -
- -
- {translate('workers.active-workers')} -
-
{active}
-
- - - {translate('workflows.running')} - -
-
- -
- {translate('workers.inactive-workers')} -
-
{inactive}
-
- - {translate('common.inactive')} - -
-
- - -
- {translate('workers.total-processed-tasks')} -
-
{totalTasks}
-
- {workflowTasks} - {translate('common.workflows-plural', { count: 1 })} · {activityTasks} - {translate('common.activities-plural', { count: 1 })} -
-
-
diff --git a/src/lib/components/task-queue/worker-info.svelte b/src/lib/components/workers/worker-info.svelte similarity index 65% rename from src/lib/components/task-queue/worker-info.svelte rename to src/lib/components/workers/worker-info.svelte index 72f1268f0e..fdcc3abf39 100644 --- a/src/lib/components/task-queue/worker-info.svelte +++ b/src/lib/components/workers/worker-info.svelte @@ -16,7 +16,6 @@ import Card from '$lib/holocene/card.svelte'; import Copyable from '$lib/holocene/copyable/index.svelte'; import Icon from '$lib/holocene/icon/icon.svelte'; - import Link from '$lib/holocene/link.svelte'; import { translate } from '$lib/i18n/translate'; import type { WorkerInfo, @@ -24,7 +23,7 @@ WorkerSlotsInfo, } from '$lib/types'; import { formatSDKName } from '$lib/utilities/get-sdk-version'; - import { routeForTaskQueue } from '$lib/utilities/route-for'; + import { routeForWorkersWithQuery } from '$lib/utilities/route-for'; import { toWorkerStatusReadable } from '$lib/utilities/screaming-enums'; type Props = { @@ -67,8 +66,11 @@ }); -
-
+
@@ -85,9 +87,9 @@ class="overflow-hidden text-ellipsis text-left" /> -
+
- + {translate('workflows.last-heartbeat')} - - {translate('common.start')} {translate('common.task-queue')} - - {translate('workers.sdk')} + + + {translate('deployments.build-id')} + + {translate('deployments.deployment')} + +
-
+
{@render taskSlotCard( translate('common.workflows-plural', { count: 1 }), heartbeat?.workflowTaskSlotsInfo, @@ -153,15 +168,15 @@ heartbeat?.localActivitySlotsInfo, null, )} -
+ -
+
+
-
+ {#snippet taskSlotCard( title: string, @@ -174,71 +189,101 @@ {slots.slotSupplierKind}
-
+
-
+
{translate('workers.slots')} -
-
-

- {slots.currentUsedSlots ?? 0} -

-

- {#if slots.currentAvailableSlots} - {slots.currentAvailableSlots - slots.currentUsedSlots || 0} - {:else} - - - {/if} -

-
-
-

{translate('workers.used')}

-

- {#if slots.currentAvailableSlots} - {translate('workers.available-out-of', { - count: slots.currentAvailableSlots, - })} - {:else} - {translate('workers.none-available')} - {/if} -

-
+ +
+
+

+ {slots.currentUsedSlots ?? 0} +

+

+ {#if slots.currentAvailableSlots} + {slots.currentAvailableSlots - slots.currentUsedSlots || 0} + {:else} + - + {/if} +

+
+
+

{translate('workers.used')}

+

+ {#if slots.currentAvailableSlots} + {translate('workers.available-out-of', { + count: slots.currentAvailableSlots, + })} + {:else} + {translate('workers.none-available')} + {/if} +

+
+
+ {@render meterBar( + 'slots-label', + slots.currentUsedSlots ?? 0, + slots.currentAvailableSlots + ? slots.currentAvailableSlots + : (slots.currentUsedSlots ?? 0), + )}
-
+
{translate('workers.tasks-processed')} -
- + +
{(slots.totalProcessedTasks ?? 0).toLocaleString()} - +
{#if poller}
-
+
{translate('workers.poller')} {poller.isAutoscaling ? 'Autoscaling' : 'Manual'} -
- - {poller.currentPollers ?? 0} - -
- {#if poller.lastSuccessfulPollTime} - {translate('workers.last-poll')} - - {:else} - {translate('workers.no-activity')} - {/if} -
+ +
+

+ {poller.currentPollers ?? 0} +

+

+ {#if poller.lastSuccessfulPollTime} + {translate('workers.last-poll')} + + {:else} + {translate('workers.no-activity')} + {/if} +

+
{/if} -
+
{/snippet} +{#snippet meterBar(labelledby: string, value: number, maxValue: number = 100)} +
+
+
+{/snippet} + {#snippet goDependencyWarning()}
- + {translate('workers.cpu-usage')} {heartbeat?.hostInfo?.currentHostCpuUsage.toFixed(0)}%
-
-
-
+ {@render meterBar( + 'cpu-label', + heartbeat?.hostInfo?.currentHostCpuUsage ?? 0, + )}
- + {translate('workers.memory-usage')} {heartbeat?.hostInfo?.currentHostMemUsage.toFixed(0)}%
-
-
-
+ {@render meterBar( + 'memory-label', + heartbeat?.hostInfo?.currentHostMemUsage ?? 0, + )}
{#if goDependencyPotentiallyMissing} {@render goDependencyWarning()} @@ -325,24 +360,24 @@

{translate('workers.workflow-cache')}

-
+
- +
{currentStickyCacheSize.toLocaleString()} - -
+
+
{translate('workers.cache-size')} -
+
- +
{cacheHitRate}% - -
+
+
{translate('workers.cache-hits')} -
+
-
+ {/snippet} @@ -351,19 +386,11 @@

{translate('workers.diagnostics')}

-
-
- {pollSuccessRate}% -
- {translate('workers.poll-success-rate')} -
-
-
- {translate('common.none')} -
- {translate('workers.rate-limit')} -
-
-
+
+
{pollSuccessRate}%
+
+ {translate('workers.poll-success-rate')} +
+
{/snippet} diff --git a/src/lib/components/workers/worker-status.svelte b/src/lib/components/workers/worker-status.svelte index dab8c099f1..d35e9d1490 100644 --- a/src/lib/components/workers/worker-status.svelte +++ b/src/lib/components/workers/worker-status.svelte @@ -14,7 +14,7 @@ const label: Record = { Running: translate('workflows.running'), - ShuttingDown: 'Shutting Down', + ShuttingDown: translate('workers.shutting-down'), Unspecified: translate('events.event-classification.unspecified'), }; diff --git a/src/lib/components/workers/workers-table/task-queue-workers-table.svelte b/src/lib/components/workers/workers-table/task-queue-workers-table.svelte index 96fe3fc2bd..37c905cead 100644 --- a/src/lib/components/workers/workers-table/task-queue-workers-table.svelte +++ b/src/lib/components/workers/workers-table/task-queue-workers-table.svelte @@ -2,32 +2,36 @@ import FallbackWorkersTable from '$lib/components/worker-table.svelte'; import { getPollers } from '$lib/services/pollers-service'; import { fetchPaginatedWorkers } from '$lib/services/worker-service'; - import { isNotFound, isNotImplemented } from '$lib/utilities/handle-error'; - import type { APIErrorResponse } from '$lib/utilities/request-from-api'; import WorkersTable from './workers-table.svelte'; interface Props { namespace: string; + useFallback?: boolean; + searchAttributes?: Record; taskQueue: string; } - let { namespace, taskQueue }: Props = $props(); - let useFallback: boolean = $state(false); + let { + namespace, + useFallback = false, + searchAttributes, + taskQueue, + }: Props = $props(); const onFetch = $derived(() => fetchPaginatedWorkers({ namespace, query: `TaskQueue="${taskQueue}"` }), ); - - const onError = (error: APIErrorResponse) => { - if (isNotFound(error) || isNotImplemented(error)) useFallback = true; - }; -{#if useFallback} +{#snippet fallback()} {#await getPollers({ queue: taskQueue, namespace }) then workers} - + {/await} +{/snippet} + +{#if useFallback} + {@render fallback()} {:else} - + {/if} diff --git a/src/lib/components/workers/workers-table/worker-heartbeats-sdk-alert.svelte b/src/lib/components/workers/workers-table/worker-heartbeats-sdk-alert.svelte new file mode 100644 index 0000000000..40305289ce --- /dev/null +++ b/src/lib/components/workers/workers-table/worker-heartbeats-sdk-alert.svelte @@ -0,0 +1,64 @@ + + + +
+ {#if currentSdk} + {@const { href, sdk, version } = currentSdk} +

+ {translate('workers.worker-heartbeats-sdk-link-preface')} + + + + {translate('workers.worker-heartbeats-sdk-link-postface')} +

+ {:else} +

{translate('workers.worker-heartbeats-sdk-list-preface')}

+ {#each supportedVersions as { href, sdk, version } (`${sdk}:${version}`)} + + + + {/each} + {/if} +
+
diff --git a/src/lib/components/workers/workers-table/workers-table-cell.svelte b/src/lib/components/workers/workers-table/workers-table-cell.svelte index 107e1c7357..b14b67c448 100644 --- a/src/lib/components/workers/workers-table/workers-table-cell.svelte +++ b/src/lib/components/workers/workers-table/workers-table-cell.svelte @@ -5,6 +5,7 @@ import FilterOrCopyButtons from '$lib/holocene/filter-or-copy-buttons.svelte'; import Link from '$lib/holocene/link.svelte'; + import Tooltip from '$lib/holocene/tooltip.svelte'; import { translate } from '$lib/i18n/translate'; import type { SearchAttributeFilter } from '$lib/models/search-attribute-filters'; import { workerFilters } from '$lib/stores/filters'; @@ -64,6 +65,12 @@ hideFilterOrCopy(); } }; + const truncate = (value: string | undefined): string => { + if (value?.length > 13) { + return `${value.slice(0, 6)}...${value.slice(-6)}`; + } + return value ?? ''; + }; - {#if href} + {#if attribute === 'BuildId' || attribute === 'WorkerInstanceKey'} + {#if href} + + {truncate(value)} + + {:else} + + {truncate(value)} + + {/if} + {:else if href} {value} {:else if children} {@render children?.()} {:else} {value} {/if} - + {#if value} + + {/if} diff --git a/src/lib/components/workers/workers-table/workers-table-row.svelte b/src/lib/components/workers/workers-table/workers-table-row.svelte index 2c73ec3326..783a52af76 100644 --- a/src/lib/components/workers/workers-table/workers-table-row.svelte +++ b/src/lib/components/workers/workers-table/workers-table-row.svelte @@ -1,5 +1,6 @@ - + {#if hasQuery} + + {:else} +
+ +
+ {/if}
diff --git a/src/lib/holocene/icon/svg/workers.svelte b/src/lib/holocene/icon/svg/workers.svelte index 3d6bf907f3..82441dbb1a 100644 --- a/src/lib/holocene/icon/svg/workers.svelte +++ b/src/lib/holocene/icon/svg/workers.svelte @@ -5,15 +5,7 @@ - - - diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index c14b4d6b72..aeba9a3955 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -3,6 +3,7 @@ export const Namespace = 'workers' as const; export const Strings = { worker: 'Worker', workers: 'Workers', + 'worker-details': 'Worker Details', 'worker-views': 'Worker Views', 'view-all-workers': 'View All Workers', 'back-to-workers': 'Back to Workers', @@ -16,9 +17,11 @@ export const Strings = { overall: 'Overall', 'host-name': 'Host Name', identity: 'Identity', - instance: 'Instance', + instance_one: 'Instance', + instance_other: 'Instances', 'task-queue': 'Task Queue', status: 'Status', + 'shutting-down': 'Shutting Down', sdk: 'Worker SDK', 'empty-state-title': 'No Workers Found', 'error-message-fetching': 'An error occurred while fetching workers.', @@ -60,11 +63,15 @@ export const Strings = { 'cache-hits': 'Cache hits', diagnostics: 'Diagnostics', 'poll-success-rate': 'Poll success rate', - 'rate-limit': 'Rate limit', 'troubleshooting-workers-link': 'Troubleshooting workers', poller: 'Poller', 'go-dependency-warning': 'Potential missing dependency', 'go-dependency-warning-description': 'This worker is not showing any usage. You may need to enable a dependency for the Go SDK in order to see this data.', 'go-dependency-warning-link': 'Learn more.', + 'no-worker-heartbeats': 'No workers are sending heartbeats.', + 'worker-heartbeats-sdk-link-preface': 'Upgrade SDK to', + 'worker-heartbeats-sdk-link-postface': 'to enable.', + 'worker-heartbeats-sdk-list-preface': + 'Upgrade SDK to one of the following versions to enable:', } as const; diff --git a/src/lib/layouts/workers-layout.svelte b/src/lib/layouts/workers-layout.svelte index f60c7a714b..80e79e7972 100644 --- a/src/lib/layouts/workers-layout.svelte +++ b/src/lib/layouts/workers-layout.svelte @@ -33,7 +33,7 @@
{/if} + - - {getWorkflowPollersWithVersions( - workflow.searchAttributes.indexedFields, - workers, - )?.pollers?.length || 0} - + - import WorkerTable from '$lib/components/worker-table.svelte'; - import { getPollers } from '$lib/services/pollers-service'; + import WorkersTable from '$lib/components/workers/workers-table/task-queue-workers-table.svelte'; import { decodePayload } from '$lib/utilities/decode-payload'; import { isEmptyObject } from '$lib/utilities/is'; import { activityExecution } from '$lib/utilities/standalone-activity-poller.svelte'; @@ -12,9 +11,7 @@ let { namespace }: Props = $props(); const searchAttributes = $derived($activityExecution?.info?.searchAttributes); - const queue = $derived($activityExecution?.info?.taskQueue); - - const getPollersRequest = getPollers({ queue, namespace }); + const taskQueue = $derived($activityExecution?.info?.taskQueue); const decodedSearchAttributes = $derived.by(() => { if (isEmptyObject(searchAttributes)) return {}; @@ -32,6 +29,8 @@ }); -{#await getPollersRequest then workers} - -{/await} + diff --git a/src/lib/pages/worker-details.svelte b/src/lib/pages/worker-details.svelte index bed56dd66b..c398d77f17 100644 --- a/src/lib/pages/worker-details.svelte +++ b/src/lib/pages/worker-details.svelte @@ -1,7 +1,7 @@ -
-
-
- - {translate('workers.view-all-workers')} - -
-
-
+
+ + {translate('workers.view-all-workers')} + +
{#await describeWorker({ namespace, workerInstanceKey }) then data} {:catch error} diff --git a/src/lib/pages/workers.svelte b/src/lib/pages/workers.svelte index 8b84cc2e8b..0fde062c74 100644 --- a/src/lib/pages/workers.svelte +++ b/src/lib/pages/workers.svelte @@ -5,53 +5,33 @@ import FilterBar from '$lib/components/shared-search-attribute-filter/filter-bar.svelte'; import WorkersTable from '$lib/components/workers/workers-table/workers-table.svelte'; - import Alert from '$lib/holocene/alert.svelte'; import { fetchPaginatedWorkers } from '$lib/services/worker-service'; import { workerFilters } from '$lib/stores/filters'; import { workerSearchAttributeOptions, workerSearchAttributes, } from '$lib/stores/search-attributes'; - import { isNotImplemented } from '$lib/utilities/handle-error'; import { toListWorkflowFilters } from '$lib/utilities/query/to-list-workflow-filters'; - import type { APIErrorResponse } from '$lib/utilities/request-from-api'; const { namespace } = $derived(page.params); const query = $derived(page.url.searchParams.get('query') || ''); const onFetch = $derived(() => fetchPaginatedWorkers({ namespace, query })); - let showAlert = $state(false); - onMount(() => { const query = page.url.searchParams.get('query') ?? ''; if (query) { $workerFilters = toListWorkflowFilters(query, $workerSearchAttributes); } }); - - $effect(() => { - void namespace; - showAlert = false; - }); - - const onError = (error: APIErrorResponse) => { - if (isNotImplemented(error)) showAlert = true; - }; -{#if showAlert} - - - -{:else} - + - {#key [namespace, query]} - - {/key} -{/if} +{#key [namespace, query]} + +{/key} diff --git a/src/lib/services/worker-service.ts b/src/lib/services/worker-service.ts index 8a13b06b8a..e916003588 100644 --- a/src/lib/services/worker-service.ts +++ b/src/lib/services/worker-service.ts @@ -27,14 +27,6 @@ export const fetchPaginatedWorkers = async ( query: parameters.query, }, }).then(({ workersInfo, nextPageToken }) => { - if (!workersInfo) { - // TODO: Verify if we want this behavior - throw { - statusCode: 501, - statusText: 'Not Implemented', - message: 'No workers info in response', - }; - } return { items: workersInfo ?? [], nextPageToken: nextPageToken ? String(nextPageToken) : '', diff --git a/src/lib/stores/events.ts b/src/lib/stores/events.ts index 842602dc15..67ae74e7bb 100644 --- a/src/lib/stores/events.ts +++ b/src/lib/stores/events.ts @@ -5,9 +5,11 @@ import { page } from '$app/stores'; import type { FetchEventsParameters } from '$lib/services/events-service'; import type { WorkflowEvents } from '$lib/types/events'; import { decodeURIForSvelte } from '$lib/utilities/encode-uri'; +import { getSDKandVersion } from '$lib/utilities/get-sdk-version'; import { isLocalActivityMarkerEvent, isResetEvent, + isWorkflowTaskCompletedEvent, } from '$lib/utilities/is-event-type'; import { eventFilterSort } from './event-view'; @@ -69,6 +71,11 @@ export const resetEvents = derived(fullEventHistory, (events) => events.filter(isResetEvent), ); +export const sdkInfo = derived(fullEventHistory, ($history) => { + const workflowCompletedTasks = $history.filter(isWorkflowTaskCompletedEvent); + return getSDKandVersion(workflowCompletedTasks); +}); + export const decodeEventHistory = persistStore( 'decodeEventHistory', true, diff --git a/src/lib/stores/search-attributes.ts b/src/lib/stores/search-attributes.ts index df21b05633..78551e2baf 100644 --- a/src/lib/stores/search-attributes.ts +++ b/src/lib/stores/search-attributes.ts @@ -199,10 +199,10 @@ export const workerSearchAttributes: Readable = readable({ TaskQueue: SEARCH_ATTRIBUTE_TYPE.KEYWORD, StartTime: SEARCH_ATTRIBUTE_TYPE.DATETIME, LastHeartbeatTime: SEARCH_ATTRIBUTE_TYPE.DATETIME, - DeploymentName: SEARCH_ATTRIBUTE_TYPE.DATETIME, - BuildId: SEARCH_ATTRIBUTE_TYPE.DATETIME, - SdkName: SEARCH_ATTRIBUTE_TYPE.DATETIME, - SdkVersion: SEARCH_ATTRIBUTE_TYPE.DATETIME, + DeploymentName: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + BuildId: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + SdkName: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + SdkVersion: SEARCH_ATTRIBUTE_TYPE.KEYWORD, }); export const workerSearchAttributeOptions: Readable = diff --git a/src/lib/utilities/route-for-base-path.test.ts b/src/lib/utilities/route-for-base-path.test.ts index da0e52737e..b575b03e6e 100644 --- a/src/lib/utilities/route-for-base-path.test.ts +++ b/src/lib/utilities/route-for-base-path.test.ts @@ -56,6 +56,7 @@ import { routeForWorkerDeploymentVersion, routeForWorkerInstance, routeForWorkers, + routeForWorkersWithQuery, routeForWorkflow, routeForWorkflowMemo, routeForWorkflowQuery, @@ -212,6 +213,10 @@ describe('routeFor functions should resolve the base path exactly once', () => { workerInstanceKey: 'worker-1', }), ], + [ + 'routeForWorkersWithQuery', + () => routeForWorkersWithQuery({ namespace: 'default', query: 'test' }), + ], ]; it.each(cases)('%s should resolve the base path', (_name, fn) => { diff --git a/src/lib/utilities/route-for.ts b/src/lib/utilities/route-for.ts index 9b49a37ca2..6bd5b7e24e 100644 --- a/src/lib/utilities/route-for.ts +++ b/src/lib/utilities/route-for.ts @@ -27,7 +27,7 @@ type RouteParameters = { }; export type NamespaceParameter = Pick; -export type WorkflowsParameter = Pick< +export type QueryParameters = Pick< RouteParameters, 'namespace' | 'query' | 'page' >; @@ -192,7 +192,7 @@ export const routeForWorkflowsWithQuery = ({ namespace, query, page, -}: WorkflowsParameter): ResolvedPathname | undefined => { +}: QueryParameters): ResolvedPathname | undefined => { if (!BROWSER) { return undefined; } @@ -295,6 +295,21 @@ export const routeForWorkers = ( return `${routeForNamespace({ namespace: parameters.namespace })}/workers`; }; +export const routeForWorkersWithQuery = ({ + namespace, + query, + page, +}: QueryParameters): ResolvedPathname | undefined => { + if (!BROWSER) { + return undefined; + } + + return toURL(routeForWorkers({ namespace }), { + query: query ?? '', + ...(page && { page }), + }); +}; + export const routeForWorkflowWorkers = ( parameters: WorkflowParameters, ): ResolvedPathname => { diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte index 7297384e9c..7533ee823d 100644 --- a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte @@ -2,7 +2,6 @@ import { page } from '$app/state'; import PageTitle from '$lib/components/page-title.svelte'; - import NoWorkersPollingAlert from '$lib/components/workers/no-workers-polling-alert.svelte'; import WorkersTable from '$lib/components/workers/workers-table/task-queue-workers-table.svelte'; import { translate } from '$lib/i18n/translate'; import { workflowRun } from '$lib/stores/workflow-run'; @@ -15,5 +14,4 @@ title={`${translate('workflows.workers-tab')} | ${workflowId}`} url={page.url.href} /> - From 5d18478df71da04bc696c5f6249a71ed8e6fd9a3 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Tue, 17 Mar 2026 17:10:20 -0600 Subject: [PATCH 20/40] Hide/show alerts --- .../workers/no-workers-polling-alert.svelte | 8 +++--- .../workers/worker-heartbeats-disabled.svelte | 18 +++++++++++++ .../workers-table/workers-table.svelte | 5 +++- src/lib/i18n/locales/en/workers.ts | 4 +++ .../pages/standalone-activity-workers.svelte | 6 +++++ src/lib/pages/task-queue.svelte | 9 ++++++- src/lib/pages/workers.svelte | 27 ++++++++++++------- .../utilities/is-running-with-no-workers.ts | 9 +++++++ .../[workflow]/[run]/workers/+page.svelte | 5 +++- 9 files changed, 74 insertions(+), 17 deletions(-) create mode 100644 src/lib/components/workers/worker-heartbeats-disabled.svelte create mode 100644 src/lib/utilities/is-running-with-no-workers.ts diff --git a/src/lib/components/workers/no-workers-polling-alert.svelte b/src/lib/components/workers/no-workers-polling-alert.svelte index ce450c7c87..691d68ce66 100644 --- a/src/lib/components/workers/no-workers-polling-alert.svelte +++ b/src/lib/components/workers/no-workers-polling-alert.svelte @@ -4,12 +4,10 @@ import Link from '$lib/holocene/link.svelte'; import { translate } from '$lib/i18n/translate'; import { workflowRun } from '$lib/stores/workflow-run'; + import { isRunningWithNoWorkers } from '$lib/utilities/is-running-with-no-workers'; - const { workflow, workers, workersLoaded } = $derived($workflowRun); - const isPending = $derived(workflow?.isRunning || workflow?.isPaused); - const runningWithNoWorkers = $derived( - workersLoaded && isPending && !workers?.pollers?.length, - ); + const { workflow } = $derived($workflowRun); + const runningWithNoWorkers = $derived(isRunningWithNoWorkers($workflowRun)); + import Alert from '$lib/holocene/alert.svelte'; + import CodeBlock from '$lib/holocene/code-block.svelte'; + import { translate } from '$lib/i18n/translate'; + + const content = `frontend.workerHeartbeatsEnabled: + - value: true`; + + + +
+

{translate('workers.worker-heartbeats-enablement')}

+ +
diff --git a/src/lib/components/workers/workers-table/workers-table.svelte b/src/lib/components/workers/workers-table/workers-table.svelte index 0e02f1277a..ddfc909f2b 100644 --- a/src/lib/components/workers/workers-table/workers-table.svelte +++ b/src/lib/components/workers/workers-table/workers-table.svelte @@ -6,7 +6,9 @@ type PaginatedRequest, } from '$lib/holocene/table/paginated-table/api-paginated.svelte'; import { translate } from '$lib/i18n/translate'; + import { workflowRun } from '$lib/stores/workflow-run'; import type { WorkerInfo } from '$lib/types'; + import { isRunningWithNoWorkers } from '$lib/utilities/is-running-with-no-workers'; import type { APIErrorResponse } from '$lib/utilities/request-from-api'; import WorkerHeartbeatsSDKAlert from './worker-heartbeats-sdk-alert.svelte'; @@ -34,6 +36,7 @@ ]; const hasQuery = $derived(page.url.searchParams.get('query') !== null); + const runningWithNoWorkers = $derived(isRunningWithNoWorkers($workflowRun)); - {#if hasQuery} + {#if hasQuery || runningWithNoWorkers} {:else}
diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index aeba9a3955..1a75e68159 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -74,4 +74,8 @@ export const Strings = { 'worker-heartbeats-sdk-link-postface': 'to enable.', 'worker-heartbeats-sdk-list-preface': 'Upgrade SDK to one of the following versions to enable:', + 'worker-heartbeats-disabled': + 'Worker heartbeats are disabled for this Namespace.', + 'worker-heartbeats-enablement': + 'If you want to enable worker heartbeats for this namespace, please ensure the following value is set in Dynamic Config.', } as const; diff --git a/src/lib/pages/standalone-activity-workers.svelte b/src/lib/pages/standalone-activity-workers.svelte index 4e46806649..c3fda6883e 100644 --- a/src/lib/pages/standalone-activity-workers.svelte +++ b/src/lib/pages/standalone-activity-workers.svelte @@ -1,4 +1,6 @@
@@ -12,5 +15,9 @@

{taskQueue}

- +
diff --git a/src/lib/pages/workers.svelte b/src/lib/pages/workers.svelte index 0fde062c74..6f158abf6b 100644 --- a/src/lib/pages/workers.svelte +++ b/src/lib/pages/workers.svelte @@ -4,6 +4,7 @@ import { page } from '$app/state'; import FilterBar from '$lib/components/shared-search-attribute-filter/filter-bar.svelte'; + import WorkerHeartbeatsDisabled from '$lib/components/workers/worker-heartbeats-disabled.svelte'; import WorkersTable from '$lib/components/workers/workers-table/workers-table.svelte'; import { fetchPaginatedWorkers } from '$lib/services/worker-service'; import { workerFilters } from '$lib/stores/filters'; @@ -23,15 +24,23 @@ $workerFilters = toListWorkflowFilters(query, $workerSearchAttributes); } }); + + const workerHeartbeatsEnabled = $derived( + !!page.data.namespace.namespaceInfo?.capabilities?.workerHeartbeats, + ); - +{#if workerHeartbeatsEnabled} + -{#key [namespace, query]} - -{/key} + {#key [namespace, query]} + + {/key} +{:else} + +{/if} diff --git a/src/lib/utilities/is-running-with-no-workers.ts b/src/lib/utilities/is-running-with-no-workers.ts new file mode 100644 index 0000000000..a6e10f3db5 --- /dev/null +++ b/src/lib/utilities/is-running-with-no-workers.ts @@ -0,0 +1,9 @@ +import type { WorkflowRunWithWorkers } from '$lib/stores/workflow-run'; + +export function isRunningWithNoWorkers( + workflowRun: WorkflowRunWithWorkers, +): boolean { + const { workflow, workers, workersLoaded } = workflowRun; + const isPending = workflow?.isRunning || workflow?.isPaused; + return Boolean(workersLoaded && isPending && !workers?.pollers?.length); +} diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte index 7533ee823d..96d0742195 100644 --- a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte @@ -8,10 +8,13 @@ const { namespace, workflow: workflowId } = $derived(page.params); const taskQueue = $derived($workflowRun?.workflow?.taskQueue); + const workerHeartbeatsEnabled = $derived( + !!page.data.namespace.namespaceInfo?.capabilities?.workerHeartbeats, + ); - + From 3588500f6acd92106149af340de53b9ad08daef3 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Wed, 18 Mar 2026 11:00:01 -0600 Subject: [PATCH 21/40] Fix strict errors --- .../lines-and-dots/workflow-details.svelte | 40 ++++++----- .../filter-list.svelte | 2 +- .../search-attribute-menu.svelte | 2 +- .../status-filter-chip.svelte | 4 +- src/lib/components/workers/worker-info.svelte | 68 +++++++++++-------- .../workers-table/workers-table-cell.svelte | 10 +-- .../workers-table/workers-table-row.svelte | 18 ++--- .../workers-table/workers-table.svelte | 5 +- .../filter-bar/dropdown-filter-chip.svelte | 6 +- .../workflow/workflow-summary.svelte | 30 ++++---- src/lib/layouts/workflow-header.svelte | 4 +- src/lib/models/worker-status.ts | 2 +- src/lib/pages/deployments.svelte | 17 +++-- .../pages/standalone-activity-workers.svelte | 19 +++--- src/lib/services/worker-service.ts | 2 +- src/lib/utilities/get-sdk-version.ts | 2 +- src/lib/utilities/screaming-enums.ts | 4 +- .../[workflow]/[run]/workers/+page.svelte | 2 +- 18 files changed, 128 insertions(+), 109 deletions(-) diff --git a/src/lib/components/lines-and-dots/workflow-details.svelte b/src/lib/components/lines-and-dots/workflow-details.svelte index 8c58992c09..9845877538 100644 --- a/src/lib/components/lines-and-dots/workflow-details.svelte +++ b/src/lib/components/lines-and-dots/workflow-details.svelte @@ -79,7 +79,9 @@ ], ); let totalActions = $derived( - $fullEventHistory.reduce((acc, e) => e.billableActions + acc, 0).toString(), + $fullEventHistory + .reduce((acc, e) => (e?.billableActions ?? 0) + acc, 0) + .toString(), ); const { sdk, version: sdkVersion } = $derived($sdkInfo); @@ -158,18 +160,20 @@ href={routeForWorkflowsWithQuery({ namespace, query: `WorkflowType="${workflow?.name}"`, - })} + }) ?? ''} iconName="filter" /> - {translate('common.task-queue')} - + {#if workflow?.taskQueue} + {translate('common.task-queue')} + + {/if} {#if workflow?.priority} {@const { priorityKey, fairnessKey } = workflow.priority} @@ -206,11 +210,11 @@ copyableText={versioningBuildId} text={versioningBuildId} href={deploymentVersion - ? routeForWorkflowsWithQuery({ + ? (routeForWorkflowsWithQuery({ namespace, query: `TemporalWorkerDeploymentVersion="${deploymentVersion}"`, - }) - : undefined} + }) ?? '') + : ''} iconName={deploymentVersion ? 'filter' : undefined} /> {/if} @@ -226,7 +230,7 @@ href={routeForWorkflowsWithQuery({ namespace, query: `TemporalWorkflowVersioningBehavior="${versioningBehavior}"`, - })} + }) ?? ''} iconName="filter" /> {/if} @@ -244,15 +248,15 @@ })} /> {/if} - {#if parent} + {#if parent?.workflowId && parent?.runId} {translate('workflows.parent-workflow')} {/if} diff --git a/src/lib/components/shared-search-attribute-filter/filter-list.svelte b/src/lib/components/shared-search-attribute-filter/filter-list.svelte index 3335570ceb..e36bbb5a70 100644 --- a/src/lib/components/shared-search-attribute-filter/filter-list.svelte +++ b/src/lib/components/shared-search-attribute-filter/filter-list.svelte @@ -87,7 +87,7 @@ if (index === $activeQueryIndex) { $activeQueryIndex = null; $filter = createFilter(); - } else if (index < $activeQueryIndex) { + } else if ($activeQueryIndex !== null && index < $activeQueryIndex) { $activeQueryIndex -= 1; } } diff --git a/src/lib/components/shared-search-attribute-filter/search-attribute-menu.svelte b/src/lib/components/shared-search-attribute-filter/search-attribute-menu.svelte index 2f9991c6b7..a2e1bc69f3 100644 --- a/src/lib/components/shared-search-attribute-filter/search-attribute-menu.svelte +++ b/src/lib/components/shared-search-attribute-filter/search-attribute-menu.svelte @@ -139,7 +139,7 @@ onclick={() => { handleNewQuery(value, type); }} - disabled={statusAttribute && + disabled={Boolean(statusAttribute) && value === statusAttribute && !!$filters.find((f) => f.attribute === statusAttribute)} > diff --git a/src/lib/components/shared-search-attribute-filter/status-filter-chip.svelte b/src/lib/components/shared-search-attribute-filter/status-filter-chip.svelte index 1b359310b4..90aed4bbd7 100644 --- a/src/lib/components/shared-search-attribute-filter/status-filter-chip.svelte +++ b/src/lib/components/shared-search-attribute-filter/status-filter-chip.svelte @@ -59,7 +59,7 @@ `dropdown-filter-chip-${attribute.toLowerCase()}`, ); - function handleStatusSelect(status: string) { + function handleStatusSelect(status: string | null) { if (localFilters.find((f) => f.value === status)) { localFilters = localFilters.filter((f) => f.value !== status); onUpdate(localFilters); @@ -144,7 +144,7 @@ handleStatusSelect(status)} {checked} - label={status} + label={status ?? undefined} labelHidden /> {/snippet} diff --git a/src/lib/components/workers/worker-info.svelte b/src/lib/components/workers/worker-info.svelte index fdcc3abf39..4038712500 100644 --- a/src/lib/components/workers/worker-info.svelte +++ b/src/lib/components/workers/worker-info.svelte @@ -27,13 +27,13 @@ import { toWorkerStatusReadable } from '$lib/utilities/screaming-enums'; type Props = { - worker: WorkerInfo; + worker: WorkerInfo | null | undefined; }; let { worker }: Props = $props(); const { namespace } = $derived(page.params); - const heartbeat = $derived(worker.workerHeartbeat); + const heartbeat = $derived(worker?.workerHeartbeat); const status = $derived(toWorkerStatusReadable(heartbeat?.status)); const totalStickyCacheHit = $derived(heartbeat?.totalStickyCacheHit ?? 0); @@ -81,7 +81,7 @@ - {translate('common.task-queue')} - + {#if heartbeat?.taskQueue} + {translate('common.task-queue')} + + {/if} {translate('workers.sdk')} @@ -134,13 +136,13 @@ {translate('deployments.build-id')} {translate('deployments.deployment')} @@ -180,13 +182,15 @@ {#snippet taskSlotCard( title: string, - slots: WorkerSlotsInfo, - poller: WorkerPollerInfo, + slots: WorkerSlotsInfo | null | undefined, + poller: WorkerPollerInfo | null | undefined, )}

{title}

- {slots.slotSupplierKind} + {#if slots?.slotSupplierKind} + {slots.slotSupplierKind} + {/if}
@@ -200,11 +204,11 @@

- {slots.currentUsedSlots ?? 0} + {slots?.currentUsedSlots ?? 0}

- {#if slots.currentAvailableSlots} - {slots.currentAvailableSlots - slots.currentUsedSlots || 0} + {#if slots?.currentAvailableSlots} + {slots.currentAvailableSlots - (slots?.currentUsedSlots ?? 0)} {:else} - {/if} @@ -213,7 +217,7 @@

{translate('workers.used')}

- {#if slots.currentAvailableSlots} + {#if slots?.currentAvailableSlots} {translate('workers.available-out-of', { count: slots.currentAvailableSlots, })} @@ -225,10 +229,10 @@

{@render meterBar( 'slots-label', - slots.currentUsedSlots ?? 0, - slots.currentAvailableSlots + slots?.currentUsedSlots ?? 0, + slots?.currentAvailableSlots ? slots.currentAvailableSlots - : (slots.currentUsedSlots ?? 0), + : (slots?.currentUsedSlots ?? 0), )}
@@ -237,7 +241,7 @@ {translate('workers.tasks-processed')}
- {(slots.totalProcessedTasks ?? 0).toLocaleString()} + {(slots?.totalProcessedTasks ?? 0).toLocaleString()}
@@ -306,7 +310,9 @@ {translate('workers.cpu-usage')} - {heartbeat?.hostInfo?.currentHostCpuUsage.toFixed(0)}% + {heartbeat?.hostInfo?.currentHostCpuUsage?.toFixed(0) ?? '-'}%
{@render meterBar( 'cpu-label', @@ -319,7 +325,9 @@ {translate('workers.memory-usage')} - {heartbeat?.hostInfo?.currentHostMemUsage.toFixed(0)}% + {heartbeat?.hostInfo?.currentHostMemUsage?.toFixed(0) ?? '-'}%
{@render meterBar( 'memory-label', diff --git a/src/lib/components/workers/workers-table/workers-table-cell.svelte b/src/lib/components/workers/workers-table/workers-table-cell.svelte index b14b67c448..956da88701 100644 --- a/src/lib/components/workers/workers-table/workers-table-cell.svelte +++ b/src/lib/components/workers/workers-table/workers-table-cell.svelte @@ -17,7 +17,7 @@ interface Props { attribute?: string; copyable?: boolean; - value?: string; + value?: string | null; filterable?: boolean; href?: string; children?: Snippet; @@ -65,8 +65,8 @@ hideFilterOrCopy(); } }; - const truncate = (value: string | undefined): string => { - if (value?.length > 13) { + const truncate = (value: string | undefined | null): string => { + if (value?.length && value.length > 13) { return `${value.slice(0, 6)}...${value.slice(-6)}`; } return value ?? ''; @@ -84,11 +84,11 @@ > {#if attribute === 'BuildId' || attribute === 'WorkerInstanceKey'} {#if href} - + {truncate(value)} {:else} - + {truncate(value)} {/if} diff --git a/src/lib/components/workers/workers-table/workers-table-row.svelte b/src/lib/components/workers/workers-table/workers-table-row.svelte index 783a52af76..b6e565ac93 100644 --- a/src/lib/components/workers/workers-table/workers-table-row.svelte +++ b/src/lib/components/workers/workers-table/workers-table-row.svelte @@ -17,7 +17,7 @@ let { worker, namespace, filterable = false }: Props = $props(); const status = $derived( - toWorkerStatusReadable(worker.workerHeartbeat.status), + toWorkerStatusReadable(worker.workerHeartbeat?.status), ); @@ -27,10 +27,10 @@ @@ -46,27 +46,27 @@ /> diff --git a/src/lib/components/workers/workers-table/workers-table.svelte b/src/lib/components/workers/workers-table/workers-table.svelte index ddfc909f2b..875be2e1bd 100644 --- a/src/lib/components/workers/workers-table/workers-table.svelte +++ b/src/lib/components/workers/workers-table/workers-table.svelte @@ -9,7 +9,6 @@ import { workflowRun } from '$lib/stores/workflow-run'; import type { WorkerInfo } from '$lib/types'; import { isRunningWithNoWorkers } from '$lib/utilities/is-running-with-no-workers'; - import type { APIErrorResponse } from '$lib/utilities/request-from-api'; import WorkerHeartbeatsSDKAlert from './worker-heartbeats-sdk-alert.svelte'; import WorkersTableRow from './workers-table-row.svelte'; @@ -18,7 +17,7 @@ filterable?: boolean; namespace: string; onFetch: () => Promise>; - onError?: (err: APIErrorResponse) => void; + onError?: (err: unknown) => void; } let { filterable = false, namespace, onFetch, onError }: Props = $props(); @@ -59,7 +58,7 @@ {label} {/each} - {#each visibleItems as worker (worker.workerHeartbeat.workerInstanceKey)} + {#each visibleItems as worker (worker.workerHeartbeat?.workerInstanceKey)} {/each} diff --git a/src/lib/components/workflow/filter-bar/dropdown-filter-chip.svelte b/src/lib/components/workflow/filter-bar/dropdown-filter-chip.svelte index 1f1a7b2e24..8ef5b5ab4f 100644 --- a/src/lib/components/workflow/filter-bar/dropdown-filter-chip.svelte +++ b/src/lib/components/workflow/filter-bar/dropdown-filter-chip.svelte @@ -55,7 +55,7 @@ onUpdate: (updatedFilter: SearchAttributeFilter) => void; onRemove: () => void; index?: number; - openIndex?: number; + openIndex?: number | null; }; let { @@ -179,7 +179,7 @@ } if (isDateTimeFilter(filter)) { - if (filter.customDate) return value.split('BETWEEN')[1]; + if (filter.customDate) return value?.split('BETWEEN')[1]; return $timestamp(value, { format: 'short' }); } @@ -190,7 +190,7 @@ return value; } - function applyChanges(e) { + function applyChanges(e: SubmitEvent) { e.preventDefault(); if (isInConditional(localFilter.conditional)) { diff --git a/src/lib/components/workflow/workflow-summary.svelte b/src/lib/components/workflow/workflow-summary.svelte index 0d4939ec35..5f04a912d7 100644 --- a/src/lib/components/workflow/workflow-summary.svelte +++ b/src/lib/components/workflow/workflow-summary.svelte @@ -40,19 +40,19 @@
@@ -60,19 +60,21 @@

{translate('common.task-queue')}

- + {#if workflow?.taskQueue} + + {/if}
diff --git a/src/lib/layouts/workflow-header.svelte b/src/lib/layouts/workflow-header.svelte index 626feed50b..c6c5a16d1d 100644 --- a/src/lib/layouts/workflow-header.svelte +++ b/src/lib/layouts/workflow-header.svelte @@ -53,7 +53,7 @@ run: runId, id: eventId, } = $derived(page.params); - const { workflow, workers } = $derived($workflowRun); + const { workflow } = $derived($workflowRun); const routeParameters = $derived({ namespace, workflow: workflowId, @@ -265,7 +265,7 @@ )} > - {workflow.historyEvents} + {workflow?.historyEvents} { - error = - err?.body?.message || translate('deployments.error-message-fetching'); + const onError = (err: unknown) => { + if ( + has(err, 'body') && + has(err.body, 'message') && + typeof err.body.message === 'string' + ) { + error = err.body.message; + } else { + error = translate('deployments.error-message-fetching'); + } }; const columns = [ @@ -54,7 +61,7 @@ >{translate('deployments.deployments')} - {#each columns as { label }} + {#each columns as { label } (label)} {label} {/each} diff --git a/src/lib/pages/standalone-activity-workers.svelte b/src/lib/pages/standalone-activity-workers.svelte index c3fda6883e..e4babcc354 100644 --- a/src/lib/pages/standalone-activity-workers.svelte +++ b/src/lib/pages/standalone-activity-workers.svelte @@ -13,7 +13,7 @@ let { namespace }: Props = $props(); const searchAttributes = $derived($activityExecution?.info?.searchAttributes); - const taskQueue = $derived($activityExecution?.info?.taskQueue); + const taskQueue = $derived($activityExecution?.info?.taskQueue ?? ''); const workerHeartbeatsEnabled = $derived( !!page.data.namespace.namespaceInfo?.capabilities?.workerHeartbeats, ); @@ -21,15 +21,14 @@ const decodedSearchAttributes = $derived.by(() => { if (isEmptyObject(searchAttributes)) return {}; - const decoded = Object.entries(searchAttributes.indexedFields).reduce( - (searchAttributes, [searchAttributeName, payload]) => { - return { - ...searchAttributes, - [searchAttributeName]: decodePayload(payload), - }; - }, - {}, - ); + const decoded = Object.entries( + searchAttributes?.indexedFields ?? {}, + ).reduce((searchAttributes, [searchAttributeName, payload]) => { + return { + ...searchAttributes, + [searchAttributeName]: decodePayload(payload), + }; + }, {}); return decoded; }); diff --git a/src/lib/services/worker-service.ts b/src/lib/services/worker-service.ts index e916003588..3e5e4af68d 100644 --- a/src/lib/services/worker-service.ts +++ b/src/lib/services/worker-service.ts @@ -24,7 +24,7 @@ export const fetchPaginatedWorkers = async ( params: { maximumPageSize: String(pageSize), nextPageToken: token, - query: parameters.query, + ...(parameters.query && { query: parameters.query }), }, }).then(({ workersInfo, nextPageToken }) => { return { diff --git a/src/lib/utilities/get-sdk-version.ts b/src/lib/utilities/get-sdk-version.ts index a5f99dda4d..c788a7c5ac 100644 --- a/src/lib/utilities/get-sdk-version.ts +++ b/src/lib/utilities/get-sdk-version.ts @@ -2,7 +2,7 @@ import type { WorkflowTaskCompletedEvent } from '$lib/types/events'; import { capitalize } from './format-camel-case'; -export const formatSDKName = (sdkName: string | undefined): string => { +export const formatSDKName = (sdkName: string | undefined | null): string => { let sdk = ''; if (!sdkName) return sdk; diff --git a/src/lib/utilities/screaming-enums.ts b/src/lib/utilities/screaming-enums.ts index e11ef7ff63..3c03bbeafb 100644 --- a/src/lib/utilities/screaming-enums.ts +++ b/src/lib/utilities/screaming-enums.ts @@ -83,7 +83,7 @@ export const toWorkflowTaskFailureReadable = ( export const toPendingActivityStateReadable = ( state?: PendingActivityState, ): PendingActivityState => { - if (!state) return state; + if (!state) return 'Unspecified'; return fromScreamingEnum(state, 'PendingActivityState'); }; @@ -102,7 +102,7 @@ export const toCallbackStateReadable = ( }; export const toWorkerStatusReadable = ( - state?: WorkerStatus, + state: WorkerStatus | undefined | null, ): ReadableWorkerStatus => { if (!state) return 'Unspecified'; return fromScreamingEnum( diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte index 96d0742195..69f9dca6b1 100644 --- a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte @@ -7,7 +7,7 @@ import { workflowRun } from '$lib/stores/workflow-run'; const { namespace, workflow: workflowId } = $derived(page.params); - const taskQueue = $derived($workflowRun?.workflow?.taskQueue); + const taskQueue = $derived($workflowRun?.workflow?.taskQueue ?? ''); const workerHeartbeatsEnabled = $derived( !!page.data.namespace.namespaceInfo?.capabilities?.workerHeartbeats, ); From 871a4d929c382b4d1e4477101caad6ac4c338d42 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Wed, 18 Mar 2026 16:01:00 -0600 Subject: [PATCH 22/40] Fix routes when switching Namespaces --- src/routes/(app)/+layout.svelte | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index f0cc9ae222..abdd1f0ecf 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -56,7 +56,7 @@ isCloud ? [page.params.namespace] : $namespaces.map( - (namespace: Namespace) => namespace?.namespaceInfo?.name, + (namespace: Namespace) => namespace?.namespaceInfo?.name as string, ), ); let namespaceList: NamespaceListItem[] = $derived( @@ -235,14 +235,22 @@ subPath: 'activities', fullRoute: routeForStandaloneActivities({ namespace }), }, + { + subPath: 'workers/deployments', + fullRoute: routeForWorkerDeployments({ namespace }), + }, { subPath: 'workers', fullRoute: routeForWorkers({ namespace }), }, ]; + const segments = page.url.pathname.split('/').filter(Boolean); + const namespaceIndex = segments.indexOf(page.params.namespace); + const sectionSegments = segments.slice(namespaceIndex + 1); + for (const { subPath, fullRoute } of namespacePages) { - if (page.url.pathname.endsWith(subPath)) { + if (sectionSegments.join('/').startsWith(subPath)) { return fullRoute; } } From 9ef56c75d956574192b6b5ba603b30c20776ed9e Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Wed, 18 Mar 2026 21:17:09 -0600 Subject: [PATCH 23/40] Small fixes --- .../workers/worker-details/skeleton.svelte | 122 ++++++++++++++++++ .../worker-details.svelte} | 37 ++++-- .../task-queue-workers-table.svelte | 15 ++- .../workers-table/workers-table-row.svelte | 10 +- .../workers-table/workers-table.svelte | 2 +- src/lib/i18n/locales/en/workers.ts | 1 - src/lib/pages/deployment.svelte | 14 +- src/lib/pages/worker-details.svelte | 25 ++-- src/lib/pages/workers.svelte | 7 +- src/lib/utilities/get-sdk-version.ts | 3 +- 10 files changed, 198 insertions(+), 38 deletions(-) create mode 100644 src/lib/components/workers/worker-details/skeleton.svelte rename src/lib/components/workers/{worker-info.svelte => worker-details/worker-details.svelte} (94%) diff --git a/src/lib/components/workers/worker-details/skeleton.svelte b/src/lib/components/workers/worker-details/skeleton.svelte new file mode 100644 index 0000000000..20ec1912c0 --- /dev/null +++ b/src/lib/components/workers/worker-details/skeleton.svelte @@ -0,0 +1,122 @@ + + +
+ {@render breadcrumb()} + + +
+ +
+
+ + +
+ +
+ {#each Array(3) as _, i (i)} +
+ + + + +
+ {/each} +
+ +
+
+ {#each Array(4) as _, i (i)} + + +
+
+ + + + +
+
+ + +
+
+ + + +
+
+
+ {/each} +
+ + +
+
diff --git a/src/lib/components/workers/worker-info.svelte b/src/lib/components/workers/worker-details/worker-details.svelte similarity index 94% rename from src/lib/components/workers/worker-info.svelte rename to src/lib/components/workers/worker-details/worker-details.svelte index 4038712500..a0b542f6a8 100644 --- a/src/lib/components/workers/worker-info.svelte +++ b/src/lib/components/workers/worker-details/worker-details.svelte @@ -1,4 +1,6 @@ +
+ {@render breadcrumb()} + {#if heartbeat?.workerInstanceKey} + + {heartbeat.workerInstanceKey} + {/if} +
+
{translate('workers.slots')} @@ -228,7 +239,7 @@
{@render meterBar( - 'slots-label', + `slots-${title}`, slots?.currentUsedSlots ?? 0, slots?.currentAvailableSlots ? slots.currentAvailableSlots @@ -283,7 +294,9 @@ >
{/snippet} @@ -311,7 +324,7 @@ {translate('workers.cpu-usage')} {heartbeat?.hostInfo?.currentHostCpuUsage?.toFixed(0) ?? '-'}%{heartbeat?.hostInfo?.currentHostCpuUsage?.toFixed(0) ?? '0'}%
{@render meterBar( @@ -326,7 +339,7 @@ {translate('workers.memory-usage')} {heartbeat?.hostInfo?.currentHostMemUsage?.toFixed(0) ?? '-'}%{heartbeat?.hostInfo?.currentHostMemUsage?.toFixed(0) ?? '0'}%
{@render meterBar( @@ -348,15 +361,17 @@
{translate('workers.host-name')}
-
{heartbeat?.hostInfo?.hostName}
+
{heartbeat?.hostInfo?.hostName ?? '-'}
{translate('workers.process-id')}
-
{heartbeat?.hostInfo?.processId}
+
{heartbeat?.hostInfo?.processId ?? '-'}
{translate('workers.worker-grouping')}
-
{heartbeat?.hostInfo?.workerGroupingKey}
+
+ {heartbeat?.hostInfo?.workerGroupingKey ?? '-'} +
{@render hostUsage()} diff --git a/src/lib/components/workers/workers-table/task-queue-workers-table.svelte b/src/lib/components/workers/workers-table/task-queue-workers-table.svelte index 37c905cead..94c8fcb234 100644 --- a/src/lib/components/workers/workers-table/task-queue-workers-table.svelte +++ b/src/lib/components/workers/workers-table/task-queue-workers-table.svelte @@ -1,5 +1,9 @@ {#snippet fallback()} - {#await getPollers({ queue: taskQueue, namespace }) then workers} + {#await getPollers({ queue: taskQueue, namespace })} + + + {:then workers} + {:catch error} + {/await} {/snippet} diff --git a/src/lib/components/workers/workers-table/workers-table-row.svelte b/src/lib/components/workers/workers-table/workers-table-row.svelte index b6e565ac93..81156eedfb 100644 --- a/src/lib/components/workers/workers-table/workers-table-row.svelte +++ b/src/lib/components/workers/workers-table/workers-table-row.svelte @@ -28,10 +28,12 @@ {label} {/each} - {#each visibleItems as worker (worker.workerHeartbeat?.workerInstanceKey)} + {#each visibleItems as worker, i (worker.workerHeartbeat?.workerInstanceKey ?? i)} {/each} diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index 1a75e68159..fb407f88fc 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -5,7 +5,6 @@ export const Strings = { workers: 'Workers', 'worker-details': 'Worker Details', 'worker-views': 'Worker Views', - 'view-all-workers': 'View All Workers', 'back-to-workers': 'Back to Workers', version: 'Version', versioning: 'Versioning', diff --git a/src/lib/pages/deployment.svelte b/src/lib/pages/deployment.svelte index 9a72afb4e0..1b69d9596a 100644 --- a/src/lib/pages/deployment.svelte +++ b/src/lib/pages/deployment.svelte @@ -16,20 +16,18 @@ routeForWorkflowsWithQuery, } from '$lib/utilities/route-for'; - const namespace = $derived(page.params.namespace); - const deploymentName = $derived(page.params.deployment); - const parameters = $derived({ - namespace, - deploymentName: decodeURIForSvelte(deploymentName), - }); + const { namespace } = $derived(page.params); + const deploymentName = $derived(decodeURIForSvelte(page.params.deployment)); const workflowHref = $derived( routeForWorkflowsWithQuery({ - namespace: page.params.namespace, + namespace, query: `TemporalWorkerDeployment="${deploymentName}"`, }), ); - const deploymentFetch = $derived(fetchDeployment(parameters)); + const deploymentFetch = $derived( + fetchDeployment({ namespace, deploymentName }), + ); const columns = [ { label: translate('deployments.build-id') }, diff --git a/src/lib/pages/worker-details.svelte b/src/lib/pages/worker-details.svelte index c398d77f17..1c5d94bd2b 100644 --- a/src/lib/pages/worker-details.svelte +++ b/src/lib/pages/worker-details.svelte @@ -1,28 +1,35 @@ -
+{#snippet breadcrumb()} - {translate('workers.view-all-workers')} + {translate('workers.back-to-workers')} -
-{#await describeWorker({ namespace, workerInstanceKey }) then data} - +{/snippet} + +{#await workerDetailsPromise} + +{:then data} + {:catch error} + {@render breadcrumb()} {/await} diff --git a/src/lib/pages/workers.svelte b/src/lib/pages/workers.svelte index 6f158abf6b..7348486128 100644 --- a/src/lib/pages/workers.svelte +++ b/src/lib/pages/workers.svelte @@ -16,7 +16,6 @@ const { namespace } = $derived(page.params); const query = $derived(page.url.searchParams.get('query') || ''); - const onFetch = $derived(() => fetchPaginatedWorkers({ namespace, query })); onMount(() => { const query = page.url.searchParams.get('query') ?? ''; @@ -39,7 +38,11 @@ /> {#key [namespace, query]} - + fetchPaginatedWorkers({ namespace, query })} + filterable + /> {/key} {:else} diff --git a/src/lib/utilities/get-sdk-version.ts b/src/lib/utilities/get-sdk-version.ts index c788a7c5ac..78f815616e 100644 --- a/src/lib/utilities/get-sdk-version.ts +++ b/src/lib/utilities/get-sdk-version.ts @@ -6,7 +6,8 @@ export const formatSDKName = (sdkName: string | undefined | null): string => { let sdk = ''; if (!sdkName) return sdk; - sdk = capitalize(sdkName.split('-')[1]); + const parts = sdkName.split('-'); + sdk = capitalize(parts[1] ?? parts[0]); if (sdk === 'Dotnet') { sdk = '.NET'; } From 38e0f61a44128f2861e8760e13a3b0f3560a19cd Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Wed, 18 Mar 2026 22:35:01 -0600 Subject: [PATCH 24/40] Add useFallback prop for workers tables --- src/lib/pages/standalone-activity-workers.svelte | 5 +++-- src/lib/pages/task-queue.svelte | 10 ++++++++-- src/lib/pages/worker-details.svelte | 8 ++++---- .../[namespace]/task-queues/[queue]/+page.svelte | 4 ++-- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/lib/pages/standalone-activity-workers.svelte b/src/lib/pages/standalone-activity-workers.svelte index e4babcc354..ba2fec5262 100644 --- a/src/lib/pages/standalone-activity-workers.svelte +++ b/src/lib/pages/standalone-activity-workers.svelte @@ -8,9 +8,10 @@ interface Props { namespace: string; + useFallback?: boolean; } - let { namespace }: Props = $props(); + let { namespace, useFallback = false }: Props = $props(); const searchAttributes = $derived($activityExecution?.info?.searchAttributes); const taskQueue = $derived($activityExecution?.info?.taskQueue ?? ''); @@ -37,5 +38,5 @@ {namespace} {taskQueue} searchAttributes={decodedSearchAttributes} - useFallback={!workerHeartbeatsEnabled} + useFallback={!workerHeartbeatsEnabled || useFallback} /> diff --git a/src/lib/pages/task-queue.svelte b/src/lib/pages/task-queue.svelte index 7dbcb75253..70c3d2f286 100644 --- a/src/lib/pages/task-queue.svelte +++ b/src/lib/pages/task-queue.svelte @@ -4,7 +4,13 @@ import WorkersTable from '$lib/components/workers/workers-table/task-queue-workers-table.svelte'; import { translate } from '$lib/i18n/translate'; - const { namespace, queue: taskQueue } = $derived(page.params); + interface Props { + namespace: string; + queue: string; + useFallback?: boolean; + } + + let { namespace, queue: taskQueue, useFallback = false }: Props = $props(); const workerHeartbeatsEnabled = $derived( !!page.data.namespace.namespaceInfo?.capabilities?.workerHeartbeats, ); @@ -18,6 +24,6 @@ diff --git a/src/lib/pages/worker-details.svelte b/src/lib/pages/worker-details.svelte index 1c5d94bd2b..6d3c1c9b9d 100644 --- a/src/lib/pages/worker-details.svelte +++ b/src/lib/pages/worker-details.svelte @@ -1,8 +1,8 @@ - + From 52f3f594c8612cb87fc590b21dc2459ead8349ba Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Wed, 25 Mar 2026 17:44:45 -0600 Subject: [PATCH 25/40] Filter updates --- .../filter-bar.svelte | 14 ++- .../manual-query.svelte | 13 ++- .../filter-bar.svelte | 3 +- .../worker-details/worker-details.svelte | 36 +++++--- .../workers-table/workers-table-cell.svelte | 46 ++++++---- .../workers-table/workers-table-row.svelte | 20 ++++- src/lib/layouts/workflow-header.svelte | 2 +- src/lib/pages/workers.svelte | 1 + src/lib/stores/search-attributes.ts | 87 +++++++------------ 9 files changed, 129 insertions(+), 93 deletions(-) diff --git a/src/lib/components/shared-search-attribute-filter/filter-bar.svelte b/src/lib/components/shared-search-attribute-filter/filter-bar.svelte index f1d5dd0e85..aa8b8d2c82 100644 --- a/src/lib/components/shared-search-attribute-filter/filter-bar.svelte +++ b/src/lib/components/shared-search-attribute-filter/filter-bar.svelte @@ -6,6 +6,7 @@ import Tooltip from '$lib/holocene/tooltip.svelte'; import type { SearchAttributeFilter } from '$lib/models/search-attribute-filters'; import type { SearchAttributeOption } from '$lib/stores/search-attributes'; + import type { SearchAttributes } from '$lib/types/workflows'; import type { StatusAttribute } from './types.ts'; @@ -15,13 +16,20 @@ interface Props { filters: Writable; options: SearchAttributeOption[]; + searchAttributes?: SearchAttributes; id: string; statusAttribute?: StatusAttribute; onManualSearch?: (query: string) => void; } - let { filters, options, id, statusAttribute, onManualSearch }: Props = - $props(); + let { + filters, + options, + searchAttributes, + id, + statusAttribute, + onManualSearch, + }: Props = $props(); let viewManualQuery = $state(false); @@ -51,6 +59,6 @@
{#if viewManualQuery} - + {/if}
diff --git a/src/lib/components/shared-search-attribute-filter/manual-query.svelte b/src/lib/components/shared-search-attribute-filter/manual-query.svelte index 701649a394..fb52eae196 100644 --- a/src/lib/components/shared-search-attribute-filter/manual-query.svelte +++ b/src/lib/components/shared-search-attribute-filter/manual-query.svelte @@ -9,18 +9,25 @@ import { translate } from '$lib/i18n/translate'; import type { SearchAttributeFilter } from '$lib/models/search-attribute-filters'; import { currentPageKey } from '$lib/stores/pagination'; - import { searchAttributes } from '$lib/stores/search-attributes'; + import { searchAttributes as defaultSearchAttributes } from '$lib/stores/search-attributes'; + import type { SearchAttributes } from '$lib/types/workflows'; import { toListWorkflowFilters } from '$lib/utilities/query/to-list-workflow-filters'; import { MAX_QUERY_LENGTH } from '$lib/utilities/request-from-api'; import { updateQueryParameters } from '$lib/utilities/update-query-parameters'; interface Props { filters: Writable; + searchAttributes?: SearchAttributes; id: string; onSearch?: (query: string) => void; } - let { filters, id, onSearch }: Props = $props(); + let { + filters, + searchAttributes = $defaultSearchAttributes, + id, + onSearch, + }: Props = $props(); let manualSearchString = $state(''); @@ -39,7 +46,7 @@ $filters = []; } else { try { - $filters = toListWorkflowFilters(manualSearchString, $searchAttributes); + $filters = toListWorkflowFilters(manualSearchString, searchAttributes); } catch (e) { console.error(e); } diff --git a/src/lib/components/standalone-activities/activities-summary-filter-bar/filter-bar.svelte b/src/lib/components/standalone-activities/activities-summary-filter-bar/filter-bar.svelte index c0677a53de..bc964c9fc1 100644 --- a/src/lib/components/standalone-activities/activities-summary-filter-bar/filter-bar.svelte +++ b/src/lib/components/standalone-activities/activities-summary-filter-bar/filter-bar.svelte @@ -3,7 +3,7 @@ import { activitiesQuery, activityRefresh } from '$lib/stores/activities'; import { activityFilters } from '$lib/stores/filters'; import { activitySearchAttributeOptions } from '$lib/stores/search-attributes'; - + import { activitySearchAttributes } from '$lib/stores/search-attributes'; function onManualSearch(query: string) { $activitiesQuery = query; $activityRefresh = Date.now(); @@ -13,6 +13,7 @@ + {translate('deployments.build-id')} - {translate('deployments.deployment')} - + {#if heartbeat?.deploymentVersion?.deploymentName} + {translate('deployments.deployment')} + + {/if} @@ -308,10 +317,13 @@ title={translate('workers.go-dependency-warning')} >

{translate('workers.go-dependency-warning-description')}

- - + + {translate('workers.go-dependency-warning-link')} + {/snippet} @@ -324,7 +336,7 @@ {translate('workers.cpu-usage')} {heartbeat?.hostInfo?.currentHostCpuUsage?.toFixed(0) ?? '0'}%{heartbeat?.hostInfo?.currentHostCpuUsage?.toFixed(1) ?? '0'}%
{@render meterBar( @@ -339,7 +351,7 @@ {translate('workers.memory-usage')} {heartbeat?.hostInfo?.currentHostMemUsage?.toFixed(0) ?? '0'}%{heartbeat?.hostInfo?.currentHostMemUsage?.toFixed(1) ?? '0'}%
{@render meterBar( diff --git a/src/lib/components/workers/workers-table/workers-table-cell.svelte b/src/lib/components/workers/workers-table/workers-table-cell.svelte index 956da88701..5e27bb6050 100644 --- a/src/lib/components/workers/workers-table/workers-table-cell.svelte +++ b/src/lib/components/workers/workers-table/workers-table-cell.svelte @@ -17,6 +17,7 @@ interface Props { attribute?: string; copyable?: boolean; + filters?: SearchAttributeFilter[]; value?: string | null; filterable?: boolean; href?: string; @@ -26,29 +27,42 @@ let { attribute, copyable = true, + filters, value, filterable = false, href, children, }: Props = $props(); - const query = $derived(page.url.searchParams.get('query') || ''); + const matchesFilter = (a: SearchAttributeFilter, b: SearchAttributeFilter) => + a.attribute === b.attribute && a.value === b.value; + + const toggleFilters = $derived( + filters && filters.length > 0 + ? filters + : [createFilter({ attribute, value, conditional: '=' })], + ); + + const isFiltered = $derived( + toggleFilters.some((f) => + $workerFilters.some((wf) => matchesFilter(wf, f)), + ), + ); const onRowFilterClick = async () => { - const filter = $workerFilters.find((f) => f.attribute === attribute); - const getOtherFilters = () => - $workerFilters.filter((f) => f.attribute !== attribute); + const toRemove = toggleFilters.filter((f) => + $workerFilters.some((wf) => matchesFilter(wf, f)), + ); + const toAdd = toggleFilters.filter( + (f) => !$workerFilters.some((wf) => matchesFilter(wf, f)), + ); - if (!filter || filter.value !== value) { - const newFilter: SearchAttributeFilter = createFilter({ - attribute, - value, - conditional: '=', - }); - $workerFilters = [...getOtherFilters(), newFilter]; - } else { - $workerFilters = [...getOtherFilters()]; - } + $workerFilters = [ + ...$workerFilters.filter( + (wf) => !toRemove.some((f) => matchesFilter(wf, f)), + ), + ...toAdd, + ]; updateQueryParamsFromFilter(page.url, $workerFilters); }; @@ -99,7 +113,7 @@ {:else} {value} {/if} - {#if value} + {#if value || (filters && filters.length > 0)} {/if} diff --git a/src/lib/components/workers/workers-table/workers-table-row.svelte b/src/lib/components/workers/workers-table/workers-table-row.svelte index 81156eedfb..ec4462c064 100644 --- a/src/lib/components/workers/workers-table/workers-table-row.svelte +++ b/src/lib/components/workers/workers-table/workers-table-row.svelte @@ -4,6 +4,7 @@ import WorkerStatus from '$lib/components/workers/worker-status.svelte'; import type { WorkerInfo } from '$lib/types'; import { formatSDKName } from '$lib/utilities/get-sdk-version'; + import { createFilter } from '$lib/utilities/query/to-list-workflow-filters'; import { routeForWorkerInstance } from '$lib/utilities/route-for'; import { toWorkerStatusReadable } from '$lib/utilities/screaming-enums'; @@ -41,10 +42,10 @@ value={worker.workerHeartbeat?.deploymentVersion?.deploymentName} {filterable} /> + - + - + diff --git a/src/lib/stores/search-attributes.ts b/src/lib/stores/search-attributes.ts index 78551e2baf..231f363295 100644 --- a/src/lib/stores/search-attributes.ts +++ b/src/lib/stores/search-attributes.ts @@ -200,7 +200,7 @@ export const workerSearchAttributes: Readable = readable({ StartTime: SEARCH_ATTRIBUTE_TYPE.DATETIME, LastHeartbeatTime: SEARCH_ATTRIBUTE_TYPE.DATETIME, DeploymentName: SEARCH_ATTRIBUTE_TYPE.KEYWORD, - BuildId: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + // BuildId: SEARCH_ATTRIBUTE_TYPE.KEYWORD, // TODO: Add back when API supports it SdkName: SEARCH_ATTRIBUTE_TYPE.KEYWORD, SdkVersion: SEARCH_ATTRIBUTE_TYPE.KEYWORD, }); @@ -215,59 +215,36 @@ export const workerSearchAttributeOptions: Readable = }; }); }); + +export const activitySearchAttributes: Readable = derived( + customSearchAttributes, + ($customSearchAttributes) => ({ + ExecutionStatus: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + ActivityId: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + ActivityType: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + RunId: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + TaskQueue: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + StartTime: SEARCH_ATTRIBUTE_TYPE.DATETIME, + ExecutionTime: SEARCH_ATTRIBUTE_TYPE.DATETIME, + CloseTime: SEARCH_ATTRIBUTE_TYPE.DATETIME, + ExecutionDuration: SEARCH_ATTRIBUTE_TYPE.INT, + StateTransitionCount: SEARCH_ATTRIBUTE_TYPE.INT, + ...$customSearchAttributes, + }), +); export const activitySearchAttributeOptions: Readable = - derived(customSearchAttributeOptions, ($customSearchAttributeOptions) => { - return [ - { - label: 'ExecutionStatus', - value: 'ExecutionStatus', - type: SEARCH_ATTRIBUTE_TYPE.KEYWORD, - }, - { - label: 'ActivityId', - value: 'ActivityId', - type: SEARCH_ATTRIBUTE_TYPE.KEYWORD, - }, - { - label: 'ActivityType', - value: 'ActivityType', - type: SEARCH_ATTRIBUTE_TYPE.KEYWORD, - }, - { label: 'RunId', value: 'RunId', type: SEARCH_ATTRIBUTE_TYPE.KEYWORD }, - { - label: 'TaskQueue', - value: 'TaskQueue', - type: SEARCH_ATTRIBUTE_TYPE.KEYWORD, - }, - { - label: 'StartTime', - value: 'StartTime', - type: SEARCH_ATTRIBUTE_TYPE.DATETIME, - }, - { - label: 'ExecutionTime', - value: 'ExecutionTime', - type: SEARCH_ATTRIBUTE_TYPE.DATETIME, - }, - { - label: 'CloseTime', - value: 'CloseTime', - type: SEARCH_ATTRIBUTE_TYPE.DATETIME, - }, - { - label: 'ExecutionDuration', - value: 'ExecutionDuration', - type: SEARCH_ATTRIBUTE_TYPE.INT, - }, - { - label: 'StateTransitionCount', - value: 'StateTransitionCount', - type: SEARCH_ATTRIBUTE_TYPE.INT, - }, - ...$customSearchAttributeOptions, - ].sort((a, b) => { - if (a.label < b.label) return -1; - if (a.label > b.label) return 1; - return 0; - }); + derived(activitySearchAttributes, ($activitySearchAttributes) => { + return Object.entries($activitySearchAttributes) + .map(([key, value]) => { + return { + label: key, + value: key, + type: value, + }; + }) + .sort((a, b) => { + if (a.label < b.label) return -1; + if (a.label > b.label) return 1; + return 0; + }); }); From aa6de43cda66a170fb6b348016addded924e6b35 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Thu, 26 Mar 2026 12:42:24 -0600 Subject: [PATCH 26/40] Don't include null conditions in workers filters --- .../filter-bar.svelte | 10 ++++- .../filter.svelte | 11 +++++- .../filter-bar/dropdown-filter-chip.svelte | 38 ++++++++++++------- src/lib/pages/workers.svelte | 1 + 4 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/lib/components/shared-search-attribute-filter/filter-bar.svelte b/src/lib/components/shared-search-attribute-filter/filter-bar.svelte index aa8b8d2c82..58a219142b 100644 --- a/src/lib/components/shared-search-attribute-filter/filter-bar.svelte +++ b/src/lib/components/shared-search-attribute-filter/filter-bar.svelte @@ -20,6 +20,7 @@ id: string; statusAttribute?: StatusAttribute; onManualSearch?: (query: string) => void; + includeNullConditions?: boolean; } let { @@ -29,6 +30,7 @@ id, statusAttribute, onManualSearch, + includeNullConditions, }: Props = $props(); let viewManualQuery = $state(false); @@ -40,7 +42,13 @@ >
- +
void; chipOpenIndex: Writable; id: string; + includeNullConditions: boolean; } @@ -37,9 +38,16 @@ options: SearchAttributeOption[]; id: string; statusAttribute?: StatusAttribute; + includeNullConditions?: boolean; } - let { filters, options, id, statusAttribute }: Props = $props(); + let { + filters, + options, + id, + statusAttribute, + includeNullConditions = true, + }: Props = $props(); const filter = writable(createFilter()); const activeQueryIndex = writable(null); @@ -58,6 +66,7 @@ resetFilter, chipOpenIndex, id, + includeNullConditions, }); function handleSubmit() { diff --git a/src/lib/components/workflow/filter-bar/dropdown-filter-chip.svelte b/src/lib/components/workflow/filter-bar/dropdown-filter-chip.svelte index 8ef5b5ab4f..1bd804b11b 100644 --- a/src/lib/components/workflow/filter-bar/dropdown-filter-chip.svelte +++ b/src/lib/components/workflow/filter-bar/dropdown-filter-chip.svelte @@ -3,8 +3,12 @@ import { addHours, addMinutes, addSeconds, startOfDay } from 'date-fns'; import { zonedTimeToUtc } from 'date-fns-tz'; - import { untrack } from 'svelte'; + import { getContext, untrack } from 'svelte'; + import { + SEARCH_ATTRIBUTE_FILTER_CONTEXT, + type SearchAttributeFilterContext, + } from '$lib/components/shared-search-attribute-filter/filter.svelte'; import { timestamp } from '$lib/components/timestamp.svelte'; import Button from '$lib/holocene/button.svelte'; import DatePicker from '$lib/holocene/date-picker.svelte'; @@ -66,6 +70,10 @@ openIndex = null, }: Props = $props(); + const { includeNullConditions = true } = + getContext(SEARCH_ATTRIBUTE_FILTER_CONTEXT) ?? + {}; + const open = writable(false); let localFilter = $state({ ...filter }); @@ -81,12 +89,16 @@ const isTimeRange = $derived(localFilter.conditional === 'BETWEEN'); const selectedTime = $derived(getSelectedTimezone($timeFormat)); - const defaultConditionOptions = [ - { value: 'is', label: translate('common.is-null') }, - { value: 'is not', label: translate('common.is-not-null') }, - ]; + const defaultConditionOptions = $derived( + includeNullConditions + ? [ + { value: 'is', label: translate('common.is-null') }, + { value: 'is not', label: translate('common.is-not-null') }, + ] + : [], + ); - const conditionalOptions = [ + const conditionalOptions = $derived([ { value: '=', label: translate('common.equal-to'), id: 'equal-to' }, { value: '!=', @@ -99,16 +111,16 @@ id: 'starts-with', }, ...defaultConditionOptions, - ]; + ]); - const dateConditionalOptions = [ + const dateConditionalOptions = $derived([ { value: '<=', label: translate('common.before') }, { value: 'BETWEEN', label: translate('common.between') }, { value: '>=', label: translate('common.after') }, ...defaultConditionOptions, - ]; + ]); - const numberConditionalOptions = [ + const numberConditionalOptions = $derived([ { value: '>', label: '>', id: 'greater-than' }, { value: '>=', @@ -132,14 +144,14 @@ }, { value: '<', label: '<', id: 'less-than' }, ...defaultConditionOptions, - ]; + ]); - const listConditionalOptions = [ + const listConditionalOptions = $derived([ { value: 'in', label: 'In' }, { value: '=', label: translate('common.equal-to') }, { value: '!=', label: translate('common.not-equal-to') }, ...defaultConditionOptions, - ]; + ]); function getDisplayKeyWithConditional(filter: SearchAttributeFilter): string { const { attribute, conditional } = filter; diff --git a/src/lib/pages/workers.svelte b/src/lib/pages/workers.svelte index 7fad8237b3..d533fa9d7d 100644 --- a/src/lib/pages/workers.svelte +++ b/src/lib/pages/workers.svelte @@ -36,6 +36,7 @@ searchAttributes={$workerSearchAttributes} id="worker" statusAttribute="WorkerStatus" + includeNullConditions={false} /> {#key [namespace, query]} From 1bec9b3552e989ceba1f47d1fcd59ccd59261d56 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Thu, 26 Mar 2026 16:07:48 -0600 Subject: [PATCH 27/40] Update empty states and other fixes --- .../deployments/deployment-table-row.svelte | 2 +- .../no-query-results.svelte} | 0 .../no-workflow-task-failures.svelte | 0 .../table-empty-state.svelte | 4 +- .../worker-details/worker-details.svelte | 20 +++------ .../workers/worker-heartbeats-disabled.svelte | 32 +++++++++----- ...e => worker-heartbeats-sdk-warning.svelte} | 42 ++++++++++++------- .../workers-query-empty-state.svelte | 19 +++++++++ .../workers-table/workers-table-row.svelte | 11 ++++- .../workers-table/workers-table.svelte | 11 ++--- .../table-empty-state.svelte | 7 ++-- src/lib/i18n/locales/en/deployments.ts | 1 - src/lib/i18n/locales/en/workers.ts | 15 +++---- src/lib/layouts/workers-layout.svelte | 13 +++--- src/lib/pages/deployments.svelte | 2 +- 15 files changed, 112 insertions(+), 67 deletions(-) rename src/lib/components/{workflow/workflows-summary-configurable-table/empty-states/no-workflows.svelte => empty-states/no-query-results.svelte} (100%) rename src/lib/components/{workflow/workflows-summary-configurable-table => }/empty-states/no-workflow-task-failures.svelte (100%) rename src/lib/components/workers/workers-table/{worker-heartbeats-sdk-alert.svelte => worker-heartbeats-sdk-warning.svelte} (56%) create mode 100644 src/lib/components/workers/workers-table/workers-query-empty-state.svelte diff --git a/src/lib/components/deployments/deployment-table-row.svelte b/src/lib/components/deployments/deployment-table-row.svelte index d090057bb8..fed7d2c5a7 100644 --- a/src/lib/components/deployments/deployment-table-row.svelte +++ b/src/lib/components/deployments/deployment-table-row.svelte @@ -47,7 +47,7 @@ {#each columns as { label } (label)} - {#if label === translate('deployments.name')} + {#if label === translate('deployments.deployment')} import { page } from '$app/state'; - import NoWorkflows from '$lib/components/workflow/workflows-summary-configurable-table/empty-states/no-workflows.svelte'; + import NoQueryResults from '$lib/components/empty-states/no-query-results.svelte'; import Alert from '$lib/holocene/alert.svelte'; import { translate } from '$lib/i18n/translate'; import { activityError } from '$lib/stores/activities'; @@ -30,7 +30,7 @@ {translate('standalone-activities.empty-state-description')} {/if}

- +
{:else} diff --git a/src/lib/components/workers/worker-details/worker-details.svelte b/src/lib/components/workers/worker-details/worker-details.svelte index 6deb26074e..18ae8c0778 100644 --- a/src/lib/components/workers/worker-details/worker-details.svelte +++ b/src/lib/components/workers/worker-details/worker-details.svelte @@ -328,6 +328,8 @@ {/snippet} {#snippet hostUsage()} + {@const cpuUsage = (heartbeat?.hostInfo?.currentHostCpuUsage ?? 0) * 100} + {@const memUsage = (heartbeat?.hostInfo?.currentHostMemUsage ?? 0) * 100}
@@ -335,14 +337,9 @@ {translate('workers.cpu-usage')} - {heartbeat?.hostInfo?.currentHostCpuUsage?.toFixed(1) ?? '0'}% + {cpuUsage.toFixed(0)}%
- {@render meterBar( - 'cpu-label', - heartbeat?.hostInfo?.currentHostCpuUsage ?? 0, - )} + {@render meterBar('cpu-label', cpuUsage)}
@@ -350,14 +347,9 @@ {translate('workers.memory-usage')} - {heartbeat?.hostInfo?.currentHostMemUsage?.toFixed(1) ?? '0'}% + {memUsage.toFixed(0)}%
- {@render meterBar( - 'memory-label', - heartbeat?.hostInfo?.currentHostMemUsage ?? 0, - )} + {@render meterBar('memory-label', memUsage)}
{#if goDependencyPotentiallyMissing} {@render goDependencyWarning()} diff --git a/src/lib/components/workers/worker-heartbeats-disabled.svelte b/src/lib/components/workers/worker-heartbeats-disabled.svelte index 61dce95d99..aa3bddea83 100644 --- a/src/lib/components/workers/worker-heartbeats-disabled.svelte +++ b/src/lib/components/workers/worker-heartbeats-disabled.svelte @@ -1,18 +1,30 @@ - -
-

{translate('workers.worker-heartbeats-enablement')}

- -
+ +
+ + + +
+
+ {translate('workers.worker-heartbeats-disabled')} +
+

+ {translate('workers.worker-heartbeats-enablement')} +

+
+ +
+
diff --git a/src/lib/components/workers/workers-table/worker-heartbeats-sdk-alert.svelte b/src/lib/components/workers/workers-table/worker-heartbeats-sdk-warning.svelte similarity index 56% rename from src/lib/components/workers/workers-table/worker-heartbeats-sdk-alert.svelte rename to src/lib/components/workers/workers-table/worker-heartbeats-sdk-warning.svelte index 40305289ce..38bd6d519c 100644 --- a/src/lib/components/workers/workers-table/worker-heartbeats-sdk-alert.svelte +++ b/src/lib/components/workers/workers-table/worker-heartbeats-sdk-warning.svelte @@ -1,6 +1,6 @@ - -
+ + + +
+
{translate('workers.no-worker-heartbeats')}
{#if currentSdk} {@const { href, sdk, version } = currentSdk} -

+

{translate('workers.worker-heartbeats-sdk-link-preface')} - {translate('workers.worker-heartbeats-sdk-link-postface')} + {translate('workers.worker-heartbeats-sdk-link-postface')}

{:else} -

{translate('workers.worker-heartbeats-sdk-list-preface')}

- {#each supportedVersions as { href, sdk, version } (`${sdk}:${version}`)} - - - - {/each} +

+ {translate('workers.worker-heartbeats-sdk-list-preface')} +

+
+ {#each supportedVersions as { href, sdk, version } (`${sdk}:${version}`)} + + + + {/each} +
{/if}
- +
diff --git a/src/lib/components/workers/workers-table/workers-query-empty-state.svelte b/src/lib/components/workers/workers-table/workers-query-empty-state.svelte new file mode 100644 index 0000000000..a0834f7e87 --- /dev/null +++ b/src/lib/components/workers/workers-table/workers-query-empty-state.svelte @@ -0,0 +1,19 @@ + + +
+
+

+ {translate('workers.empty-state-title')} +

+

+ {translate('workers.worker-query-empty-state-description')} +

+
+ +
diff --git a/src/lib/components/workers/workers-table/workers-table-row.svelte b/src/lib/components/workers/workers-table/workers-table-row.svelte index ec4462c064..954914a46d 100644 --- a/src/lib/components/workers/workers-table/workers-table-row.svelte +++ b/src/lib/components/workers/workers-table/workers-table-row.svelte @@ -5,7 +5,10 @@ import type { WorkerInfo } from '$lib/types'; import { formatSDKName } from '$lib/utilities/get-sdk-version'; import { createFilter } from '$lib/utilities/query/to-list-workflow-filters'; - import { routeForWorkerInstance } from '$lib/utilities/route-for'; + import { + routeForWorkerDeployment, + routeForWorkerInstance, + } from '$lib/utilities/route-for'; import { toWorkerStatusReadable } from '$lib/utilities/screaming-enums'; import WorkersTableCell from './workers-table-cell.svelte'; @@ -40,6 +43,12 @@ diff --git a/src/lib/components/workers/workers-table/workers-table.svelte b/src/lib/components/workers/workers-table/workers-table.svelte index 2cb318a1c5..643bf4f809 100644 --- a/src/lib/components/workers/workers-table/workers-table.svelte +++ b/src/lib/components/workers/workers-table/workers-table.svelte @@ -10,7 +10,8 @@ import type { WorkerInfo } from '$lib/types'; import { isRunningWithNoWorkers } from '$lib/utilities/is-running-with-no-workers'; - import WorkerHeartbeatsSDKAlert from './worker-heartbeats-sdk-alert.svelte'; + import WorkerHeartbeatsSDKAlert from './worker-heartbeats-sdk-warning.svelte'; + import WorkersQueryEmptyState from './workers-query-empty-state.svelte'; import WorkersTableRow from './workers-table-row.svelte'; interface Props { @@ -63,12 +64,12 @@ {/each} - {#if hasQuery || runningWithNoWorkers} + {#if hasQuery} + + {:else if runningWithNoWorkers} {:else} -
- -
+ {/if}
diff --git a/src/lib/components/workflow/workflows-summary-configurable-table/table-empty-state.svelte b/src/lib/components/workflow/workflows-summary-configurable-table/table-empty-state.svelte index a68c09d610..b4a6dff154 100644 --- a/src/lib/components/workflow/workflows-summary-configurable-table/table-empty-state.svelte +++ b/src/lib/components/workflow/workflows-summary-configurable-table/table-empty-state.svelte @@ -3,6 +3,8 @@ import { page } from '$app/state'; + import NoQueryResults from '$lib/components/empty-states/no-query-results.svelte'; + import NoWorkflowTaskFailures from '$lib/components/empty-states/no-workflow-task-failures.svelte'; import Alert from '$lib/holocene/alert.svelte'; import Link from '$lib/holocene/link.svelte'; import { translate } from '$lib/i18n/translate'; @@ -10,9 +12,6 @@ import { TASK_FAILURES_QUERY } from '$lib/utilities/workflow-task-failures'; import noResultsImages from '$lib/vendor/empty-state.svg'; - import NoWorkflowTaskFailures from './empty-states/no-workflow-task-failures.svelte'; - import NoWorkflows from './empty-states/no-workflows.svelte'; - interface Props { cloud?: Snippet; } @@ -64,7 +63,7 @@ {#if hasTaskFailuresQuery} {:else} - + {/if}
diff --git a/src/lib/i18n/locales/en/deployments.ts b/src/lib/i18n/locales/en/deployments.ts index 3a11758bd1..fabaeb3b2f 100644 --- a/src/lib/i18n/locales/en/deployments.ts +++ b/src/lib/i18n/locales/en/deployments.ts @@ -12,7 +12,6 @@ export const Strings = { 'error-message-fetching': 'Error fetching deployments', 'empty-state-description': 'Want to learn about versioning? . Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque vitae lacus eu mauris facilisis sodales.', - name: 'Deployment Name', 'current-version': 'Current Version', 'deployment-version': 'Deployment Version', 'build-id': 'Build ID', diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index fb407f88fc..6b51ca6dab 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -23,6 +23,8 @@ export const Strings = { 'shutting-down': 'Shutting Down', sdk: 'Worker SDK', 'empty-state-title': 'No Workers Found', + 'worker-query-empty-state-description': + 'Try adjusting or clearing filters to see more results', 'error-message-fetching': 'An error occurred while fetching workers.', 'compatible-build-ids': 'Compatible Build IDs', 'version-sets': 'Version Sets', @@ -64,17 +66,16 @@ export const Strings = { 'poll-success-rate': 'Poll success rate', 'troubleshooting-workers-link': 'Troubleshooting workers', poller: 'Poller', - 'go-dependency-warning': 'Potential missing dependency', + 'go-dependency-warning': 'Missing Dependency', 'go-dependency-warning-description': - 'This worker is not showing any usage. You may need to enable a dependency for the Go SDK in order to see this data.', + 'No usage data is available for this Worker. A Go SDK dependency may need to be enabled to surface this data.', 'go-dependency-warning-link': 'Learn more.', - 'no-worker-heartbeats': 'No workers are sending heartbeats.', + 'no-worker-heartbeats': 'No Workers Sending Heartbeats', 'worker-heartbeats-sdk-link-preface': 'Upgrade SDK to', 'worker-heartbeats-sdk-link-postface': 'to enable.', 'worker-heartbeats-sdk-list-preface': - 'Upgrade SDK to one of the following versions to enable:', - 'worker-heartbeats-disabled': - 'Worker heartbeats are disabled for this Namespace.', + 'Upgrade SDK to one of the following versions', + 'worker-heartbeats-disabled': 'Worker Heartbeats Disabled', 'worker-heartbeats-enablement': - 'If you want to enable worker heartbeats for this namespace, please ensure the following value is set in Dynamic Config.', + 'To enable Heartbeats for this Namespace, set the following value in Dynamic Config', } as const; diff --git a/src/lib/layouts/workers-layout.svelte b/src/lib/layouts/workers-layout.svelte index 80e79e7972..f99b936856 100644 --- a/src/lib/layouts/workers-layout.svelte +++ b/src/lib/layouts/workers-layout.svelte @@ -3,6 +3,7 @@ import { page } from '$app/state'; + import Badge from '$lib/holocene/badge.svelte'; import TabList from '$lib/holocene/tab/tab-list.svelte'; import Tab from '$lib/holocene/tab/tab.svelte'; import Tabs from '$lib/holocene/tab/tabs.svelte'; @@ -25,11 +26,9 @@
-
-

- {translate('workers.workers')} -

-
+

+ {translate('workers.workers')} +

+ > + Pre-Release + Date: Thu, 26 Mar 2026 16:54:52 -0600 Subject: [PATCH 28/40] Make task queue a filter in standalone activity details --- .../activity-header.svelte | 58 ++++++++++++------- .../worker-details/worker-details.svelte | 2 - .../workers-table/workers-table-cell.svelte | 4 +- .../workers-table/workers-table-row.svelte | 28 +++++---- .../i18n/locales/en/standalone-activities.ts | 7 +-- 5 files changed, 61 insertions(+), 38 deletions(-) diff --git a/src/lib/components/standalone-activities/activity-header.svelte b/src/lib/components/standalone-activities/activity-header.svelte index 7bfdb40bb1..9667fbdcef 100644 --- a/src/lib/components/standalone-activities/activity-header.svelte +++ b/src/lib/components/standalone-activities/activity-header.svelte @@ -2,10 +2,7 @@ import Copyable from '$lib/holocene/copyable/index.svelte'; import { translate } from '$lib/i18n/translate'; import type { ActivityExecutionInfo } from '$lib/types/activity-execution'; - import { - routeForStandaloneActivitiesWithQuery, - routeForTaskQueue, - } from '$lib/utilities/route-for'; + import { routeForStandaloneActivitiesWithQuery } from '$lib/utilities/route-for'; import type { StandaloneActivityPoller } from '$lib/utilities/standalone-activity-poller.svelte'; import { fromSeconds } from '$lib/utilities/to-duration'; @@ -36,6 +33,12 @@ `ActivityType="${activityType}"`, ), ); + const taskQueueFilterLink = $derived( + routeForStandaloneActivitiesWithQuery( + { namespace }, + `TaskQueue="${activityExecutionInfo.taskQueue}"`, + ), + );
@@ -57,20 +60,28 @@
- Scheduled Time + {translate('standalone-activities.scheduled-time')} - Last Started Time + {translate('standalone-activities.last-started-time')} - Close Time + {translate('standalone-activities.close-time')} - Duration + {translate('standalone-activities.duration')} - Run ID - - Activity Type + {translate('standalone-activities.run-id')} + \ + {#if activityType} + {translate('standalone-activities.activity-type')} + + {/if} + {translate('standalone-activities.task-queue')} - Task Queue - diff --git a/src/lib/components/workers/worker-details/worker-details.svelte b/src/lib/components/workers/worker-details/worker-details.svelte index 18ae8c0778..c0648fc68e 100644 --- a/src/lib/components/workers/worker-details/worker-details.svelte +++ b/src/lib/components/workers/worker-details/worker-details.svelte @@ -126,7 +126,6 @@ {translate('common.task-queue')} {translate('deployments.deployment')} 0 ? filters - : [createFilter({ attribute, value, conditional: '=' })], + : [createFilter({ attribute, value: value ?? '', conditional: '=' })], ); const isFiltered = $derived( @@ -119,7 +119,7 @@ copySuccessIconTitle={translate('common.copy-success-icon-title')} filterIconTitle={translate('common.filter-workflows')} show={filterOrCopyButtonsVisible} - content={value} + content={value ?? ''} onFilter={onRowFilterClick} {copyable} {filterable} diff --git a/src/lib/components/workers/workers-table/workers-table-row.svelte b/src/lib/components/workers/workers-table/workers-table-row.svelte index 954914a46d..95f81b5e78 100644 --- a/src/lib/components/workers/workers-table/workers-table-row.svelte +++ b/src/lib/components/workers/workers-table/workers-table-row.svelte @@ -79,16 +79,24 @@ copyable={false} {filterable} filters={[ - createFilter({ - attribute: 'SdkName', - value: worker.workerHeartbeat?.sdkName, - conditional: '=', - }), - createFilter({ - attribute: 'SdkVersion', - value: worker.workerHeartbeat?.sdkVersion, - conditional: '=', - }), + ...(worker.workerHeartbeat?.sdkName + ? [ + createFilter({ + attribute: 'SdkName', + value: worker.workerHeartbeat.sdkName, + conditional: '=', + }), + ] + : []), + ...(worker.workerHeartbeat?.sdkVersion + ? [ + createFilter({ + attribute: 'SdkVersion', + value: worker.workerHeartbeat.sdkVersion, + conditional: '=', + }), + ] + : []), ]} > Date: Fri, 27 Mar 2026 15:15:43 -0600 Subject: [PATCH 29/40] Add worker integration tests --- tests/integration/worker-instances.spec.ts | 58 +++++++++++++ tests/integration/workflow-workers.spec.ts | 94 ++++++++++++++++++++++ tests/test-utilities/mock-apis.ts | 22 ++++- tests/test-utilities/mocks/namespace.ts | 53 ++++++------ tests/test-utilities/mocks/task-queues.ts | 11 ++- tests/test-utilities/mocks/workers.ts | 56 +++++++++++++ 6 files changed, 261 insertions(+), 33 deletions(-) create mode 100644 tests/integration/worker-instances.spec.ts create mode 100644 tests/integration/workflow-workers.spec.ts create mode 100644 tests/test-utilities/mocks/workers.ts diff --git a/tests/integration/worker-instances.spec.ts b/tests/integration/worker-instances.spec.ts new file mode 100644 index 0000000000..9189bc4d78 --- /dev/null +++ b/tests/integration/worker-instances.spec.ts @@ -0,0 +1,58 @@ +import { expect, test } from '@playwright/test'; + +import { mockWorkersPageApis, WORKERS_API } from '~/test-utilities/mock-apis'; + +const workersUrl = '/namespaces/default/workers'; + +test.describe('Worker Instances Tab', () => { + test.describe('with worker heartbeats enabled and workers', () => { + test.beforeEach(async ({ page }) => { + await mockWorkersPageApis(page); + }); + + test('renders the workers table with data', async ({ page }) => { + await page.goto(workersUrl); + await page.waitForResponse(WORKERS_API); + + const table = page.getByRole('table', { name: 'Workers' }); + await expect(table).toBeVisible(); + + const rows = table.locator('tbody tr'); + await expect(rows).toHaveCount(2); + + await expect(rows.first()).toContainText('worker-instance-1'); + await expect(rows.first()).toContainText('my-deployment'); + await expect(rows.first()).toContainText('build-abc123'); + await expect(rows.first()).toContainText('my-task-queue'); + + await expect(rows.nth(1)).toContainText('worker-instance-2'); + }); + }); + + test.describe('with worker heartbeats enabled and no workers', () => { + test.beforeEach(async ({ page }) => { + await mockWorkersPageApis(page, { empty: true }); + }); + + test('renders the SDK warning empty state', async ({ page }) => { + await page.goto(workersUrl); + await page.waitForResponse(WORKERS_API); + + const sdkWarning = page.getByTestId('worker-heartbeats-sdk-warning'); + await expect(sdkWarning).toBeVisible(); + }); + }); + + test.describe('with worker heartbeats disabled', () => { + test.beforeEach(async ({ page }) => { + await mockWorkersPageApis(page, { heartbeatsEnabled: false }); + }); + + test('renders the heartbeats disabled empty state', async ({ page }) => { + await page.goto(workersUrl); + + const disabledState = page.getByTestId('worker-heartbeats-disabled'); + await expect(disabledState).toBeVisible(); + }); + }); +}); diff --git a/tests/integration/workflow-workers.spec.ts b/tests/integration/workflow-workers.spec.ts new file mode 100644 index 0000000000..4632076be2 --- /dev/null +++ b/tests/integration/workflow-workers.spec.ts @@ -0,0 +1,94 @@ +import { expect, test } from '@playwright/test'; + +import { + mockNamespaceWithNoWorkerHeartbeats, + mockTaskQueuesApi, + mockWorkersApi, + mockWorkflowApis, + TASK_QUEUES_API, + WORKERS_API, +} from '~/test-utilities/mock-apis'; +import { mockWorkflow } from '~/test-utilities/mocks/workflow'; + +const { workflowId, runId } = mockWorkflow.workflowExecutionInfo.execution; +const workersUrl = `/namespaces/default/workflows/${workflowId}/${runId}/workers`; + +test.describe('Workflow Workers Tab', () => { + test.describe('with worker heartbeats enabled and workers', () => { + test.beforeEach(async ({ page }) => { + await mockWorkflowApis(page); + await mockWorkersApi(page); + }); + + test('renders the workers table with data', async ({ page }) => { + await page.goto(workersUrl); + await page.waitForResponse(WORKERS_API); + + const table = page.getByRole('table', { name: 'Workers' }); + await expect(table).toBeVisible(); + + const rows = table.locator('tbody tr'); + await expect(rows).toHaveCount(2); + + await expect(rows.first()).toContainText('worker-instance-1'); + await expect(rows.first()).toContainText('my-deployment'); + await expect(rows.first()).toContainText('build-abc123'); + + await expect(rows.nth(1)).toContainText('worker-instance-2'); + }); + }); + + test.describe('with worker heartbeats enabled and no workers', () => { + test.beforeEach(async ({ page }) => { + await mockWorkflowApis(page); + await mockWorkersApi(page, true); + }); + + test('renders the SDK warning empty state', async ({ page }) => { + await page.goto(workersUrl); + await page.waitForResponse(WORKERS_API); + + const sdkWarning = page.getByTestId('worker-heartbeats-sdk-warning'); + await expect(sdkWarning).toBeVisible(); + await expect(sdkWarning).toContainText('No Workers Sending Heartbeats'); + }); + }); + + test.describe('with worker heartbeats disabled', () => { + test.beforeEach(async ({ page }) => { + await mockWorkflowApis(page); + await mockNamespaceWithNoWorkerHeartbeats(page); + }); + + test('renders the fallback pollers table', async ({ page }) => { + await page.goto(workersUrl); + await page.waitForResponse(TASK_QUEUES_API); + + const heading = page.getByTestId('workers'); + await expect(heading).toBeVisible(); + await expect(heading).toContainText('Workers'); + + const rows = page.getByTestId('worker-row'); + await expect(rows).toHaveCount(1); + + const identity = page.getByTestId('worker-identity'); + await expect(identity).toContainText('@poller'); + }); + }); + + test.describe('with no workers polling', () => { + test.beforeEach(async ({ page }) => { + await mockWorkflowApis(page); + await mockTaskQueuesApi(page, true); + }); + + test('renders the no workers polling alert', async ({ page }) => { + await page.goto(workersUrl); + + const alert = page + .getByRole('status') + .filter({ hasText: 'No workers polling' }); + await expect(alert).toBeVisible(); + }); + }); +}); diff --git a/tests/test-utilities/mock-apis.ts b/tests/test-utilities/mock-apis.ts index 3df262a803..09bf84de71 100644 --- a/tests/test-utilities/mock-apis.ts +++ b/tests/test-utilities/mock-apis.ts @@ -10,7 +10,10 @@ import { EVENT_HISTORY_API_REVERSE, mockEventHistoryApi, } from './mocks/event-history'; -import { mockNamespaceApi } from './mocks/namespace'; +import { + mockNamespaceApi, + mockNamespaceWithNoWorkerHeartbeats, +} from './mocks/namespace'; import { mockNamespacesApi, NAMESPACES_API } from './mocks/namespaces'; import { mockSchedulesApi } from './mocks/schedules'; import { mockSchedulesCountApi } from './mocks/schedules-count'; @@ -18,6 +21,7 @@ import { mockSearchAttributesApi } from './mocks/search-attributes'; import { mockSettingsApi, SETTINGS_API } from './mocks/settings'; import { mockSystemInfoApi } from './mocks/system-info'; import { mockTaskQueuesApi, TASK_QUEUES_API } from './mocks/task-queues'; +import { mockWorkersApi } from './mocks/workers'; import { mockWorkflow, mockWorkflowApi, WORKFLOW_API } from './mocks/workflow'; import { mockWorkflowsApi, WORKFLOWS_API } from './mocks/workflows'; import { @@ -34,6 +38,7 @@ export { mockClusterApi, CLUSTER_API } from './mocks/cluster'; export { mockNamespaceApi, mockNamespaceWithPauseCapability, + mockNamespaceWithNoWorkerHeartbeats, NAMESPACE_API, } from './mocks/namespace'; export { mockNamespacesApi, NAMESPACES_API } from './mocks/namespaces'; @@ -71,6 +76,7 @@ export { } from './mocks/batch-operations'; export { EVENT_HISTORY_API, mockEventHistoryApi } from './mocks/event-history'; export { mockTaskQueuesApi, TASK_QUEUES_API } from './mocks/task-queues'; +export { mockWorkersApi, WORKERS_API } from './mocks/workers'; export const mockGlobalApis = (page: Page) => { return Promise.all([ @@ -114,6 +120,20 @@ export const mockNamespaceApis = (page: Page) => { ]); }; +export const mockWorkersPageApis = ( + page: Page, + { empty = false, heartbeatsEnabled = true } = {}, +) => { + return Promise.all([ + mockGlobalApis(page), + heartbeatsEnabled + ? mockNamespaceApi(page) + : mockNamespaceWithNoWorkerHeartbeats(page), + mockSearchAttributesApi(page), + mockWorkersApi(page, empty), + ]); +}; + export const mockBatchOperationApis = (page: Page) => { return Promise.all([ mockCreateBatchOperationApi(page), diff --git a/tests/test-utilities/mocks/namespace.ts b/tests/test-utilities/mocks/namespace.ts index aa7bbf86b5..b652494e39 100644 --- a/tests/test-utilities/mocks/namespace.ts +++ b/tests/test-utilities/mocks/namespace.ts @@ -45,6 +45,7 @@ const MOCK_DEFAULT_NAMESPACE = { supportsSchedules: true, capabilities: { standaloneActivities: true, + workerHeartbeats: true, }, }, config: { @@ -73,41 +74,14 @@ const MOCK_DEFAULT_NAMESPACE = { }; const MOCK_NAMESPACE_WITH_PAUSE_CAPABILITY = { + ...MOCK_DEFAULT_NAMESPACE, namespaceInfo: { - name: 'default', - state: 'Registered', - description: '', - ownerEmail: '', - data: {}, - id: 'bbe0d4ea-c682-4c5a-bc4b-fadb8e8c8bfe', - supportsSchedules: true, + ...MOCK_DEFAULT_NAMESPACE.namespaceInfo, capabilities: { + ...MOCK_DEFAULT_NAMESPACE.namespaceInfo.capabilities, workflowPause: true, }, }, - config: { - workflowExecutionRetentionTtl: '86400s', - badBinaries: { - binaries: {}, - }, - historyArchivalState: 'Disabled', - historyArchivalUri: '', - visibilityArchivalState: 'Disabled', - visibilityArchivalUri: '', - customSearchAttributeAliases: {}, - }, - replicationConfig: { - activeClusterName: 'active', - clusters: [ - { - clusterName: 'active', - }, - ], - state: 'Unspecified', - }, - failoverVersion: '0', - isGlobalNamespace: false, - failoverHistory: [], }; export const mockNamespaceApi = (page: Page, archived = false) => { @@ -125,3 +99,22 @@ export const mockNamespaceWithPauseCapability = (page: Page) => { }); }); }; + +const MOCK_NAMESPACE_WITH_NO_WORKER_HEARTBEATS = { + ...MOCK_DEFAULT_NAMESPACE, + namespaceInfo: { + ...MOCK_DEFAULT_NAMESPACE.namespaceInfo, + capabilities: { + ...MOCK_DEFAULT_NAMESPACE.namespaceInfo.capabilities, + workerHeartbeats: false, + }, + }, +}; + +export const mockNamespaceWithNoWorkerHeartbeats = (page: Page) => { + return page.route(NAMESPACE_API, (route) => { + route.fulfill({ + json: MOCK_NAMESPACE_WITH_NO_WORKER_HEARTBEATS, + }); + }); +}; diff --git a/tests/test-utilities/mocks/task-queues.ts b/tests/test-utilities/mocks/task-queues.ts index fff3e9697a..d1ad09a4d2 100644 --- a/tests/test-utilities/mocks/task-queues.ts +++ b/tests/test-utilities/mocks/task-queues.ts @@ -14,8 +14,15 @@ const mockTaskQueues = { taskQueueStatus: null, }; -export const mockTaskQueuesApi = (page: Page) => { +const mockEmptyTaskQueues = { + pollers: [], + taskQueueStatus: null, +}; + +export const mockTaskQueuesApi = (page: Page, empty = false) => { return page.route(TASK_QUEUES_API, (route) => { - return route.fulfill({ json: mockTaskQueues }); + return route.fulfill({ + json: empty ? mockEmptyTaskQueues : mockTaskQueues, + }); }); }; diff --git a/tests/test-utilities/mocks/workers.ts b/tests/test-utilities/mocks/workers.ts new file mode 100644 index 0000000000..45440a7ad1 --- /dev/null +++ b/tests/test-utilities/mocks/workers.ts @@ -0,0 +1,56 @@ +import type { Page } from '@playwright/test'; + +export const WORKERS_API = /\/api\/v1\/namespaces\/[^/]+\/workers\?.*$/; + +const MOCK_WORKERS = { + workersInfo: [ + { + workerHeartbeat: { + workerInstanceKey: 'worker-instance-1', + status: 'WORKER_STATUS_ALIVE', + deploymentVersion: { + deploymentName: 'my-deployment', + buildId: 'build-abc123', + }, + taskQueue: 'my-task-queue', + workerIdentity: 'worker-1@host-1', + hostInfo: { + hostName: 'host-1.example.com', + }, + startTime: { seconds: '1711500000', nanos: 0 }, + sdkName: 'temporal-go', + sdkVersion: '1.41.0', + }, + }, + { + workerHeartbeat: { + workerInstanceKey: 'worker-instance-2', + status: 'WORKER_STATUS_ALIVE', + deploymentVersion: { + deploymentName: 'my-deployment', + buildId: 'build-abc123', + }, + taskQueue: 'my-task-queue', + workerIdentity: 'worker-2@host-2', + hostInfo: { + hostName: 'host-2.example.com', + }, + startTime: { seconds: '1711500100', nanos: 0 }, + sdkName: 'temporal-go', + sdkVersion: '1.41.0', + }, + }, + ], + nextPageToken: '', +}; + +const MOCK_EMPTY_WORKERS = { + workersInfo: [], + nextPageToken: '', +}; + +export const mockWorkersApi = (page: Page, empty = false) => { + return page.route(WORKERS_API, (route) => { + route.fulfill({ json: empty ? MOCK_EMPTY_WORKERS : MOCK_WORKERS }); + }); +}; From fa65d19b38059c5393e7648327c26c99f048d19d Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Fri, 27 Mar 2026 15:36:24 -0600 Subject: [PATCH 30/40] Add tooltip copy for slot supplier kind and autoscaling badges --- .../worker-details/worker-details.svelte | 25 ++++++++++++++++--- src/lib/i18n/locales/en/workers.ts | 6 +++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/lib/components/workers/worker-details/worker-details.svelte b/src/lib/components/workers/worker-details/worker-details.svelte index c0648fc68e..a59ed68041 100644 --- a/src/lib/components/workers/worker-details/worker-details.svelte +++ b/src/lib/components/workers/worker-details/worker-details.svelte @@ -19,6 +19,7 @@ import Copyable from '$lib/holocene/copyable/index.svelte'; import Icon from '$lib/holocene/icon/icon.svelte'; import Link from '$lib/holocene/link.svelte'; + import Tooltip from '$lib/holocene/tooltip.svelte'; import { translate } from '$lib/i18n/translate'; import type { WorkerInfo, @@ -68,6 +69,12 @@ 100 ).toFixed(1); }); + + const SupplierKindTooltipText: Record = { + Fixed: translate('workers.slot-supplier-kind-fixed'), + ResourceBased: translate('workers.slot-supplier-kind-resource-based'), + Custom: translate('workers.slot-supplier-kind-custom'), + };
@@ -207,7 +214,10 @@

{title}

{#if slots?.slotSupplierKind} - {slots.slotSupplierKind} + {@const tooltipText = SupplierKindTooltipText[slots.slotSupplierKind]} + + {slots.slotSupplierKind} + {/if}
@@ -267,9 +277,16 @@
{translate('workers.poller')} - - {poller.isAutoscaling ? 'Autoscaling' : 'Manual'} - + + + {poller.isAutoscaling ? 'Autoscaling' : 'Manual'} + +

diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index 6b51ca6dab..4756e78c02 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -78,4 +78,10 @@ export const Strings = { 'worker-heartbeats-disabled': 'Worker Heartbeats Disabled', 'worker-heartbeats-enablement': 'To enable Heartbeats for this Namespace, set the following value in Dynamic Config', + 'manual-poller': 'Poller count is set to a fixed number.', + 'autoscaling-poller': 'Poller count adjusts automatically based on workload.', + 'slot-supplier-kind-fixed': 'Poller count is set to a fixed number.', + 'slot-supplier-kind-resource-based': + 'Slot count adjusts based on CPU and memory usage.', + 'slot-supplier-kind-custom': 'Slot count is managed by custom logic.', } as const; From 9fbba25dcd5c4a0c1cb09add007357ef8a1edb3e Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Thu, 2 Apr 2026 13:29:04 -0600 Subject: [PATCH 31/40] Update todo comments --- src/lib/components/workers/worker-details/worker-details.svelte | 2 +- .../components/workers/workers-table/workers-table-row.svelte | 2 +- src/lib/stores/search-attributes.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/components/workers/worker-details/worker-details.svelte b/src/lib/components/workers/worker-details/worker-details.svelte index a59ed68041..c11408159b 100644 --- a/src/lib/components/workers/worker-details/worker-details.svelte +++ b/src/lib/components/workers/worker-details/worker-details.svelte @@ -151,7 +151,7 @@ - + {translate('deployments.build-id')} - + = readable({ StartTime: SEARCH_ATTRIBUTE_TYPE.DATETIME, LastHeartbeatTime: SEARCH_ATTRIBUTE_TYPE.DATETIME, DeploymentName: SEARCH_ATTRIBUTE_TYPE.KEYWORD, - // BuildId: SEARCH_ATTRIBUTE_TYPE.KEYWORD, // TODO: Add back when API supports it + // BuildId: SEARCH_ATTRIBUTE_TYPE.KEYWORD, // TODO: Add back with DT-3745 SdkName: SEARCH_ATTRIBUTE_TYPE.KEYWORD, SdkVersion: SEARCH_ATTRIBUTE_TYPE.KEYWORD, }); From ed0d9e67d284970b3d09d7a97754c8f4768befe4 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Thu, 2 Apr 2026 15:41:29 -0600 Subject: [PATCH 32/40] Pass hrefs to workers layout --- src/lib/layouts/workers-layout.svelte | 25 ++++--------------- .../[namespace]/workers/+page.svelte | 8 +++++- .../workers/deployments/+page.svelte | 8 +++++- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/lib/layouts/workers-layout.svelte b/src/lib/layouts/workers-layout.svelte index f99b936856..fbda75eab1 100644 --- a/src/lib/layouts/workers-layout.svelte +++ b/src/lib/layouts/workers-layout.svelte @@ -8,21 +8,14 @@ import Tab from '$lib/holocene/tab/tab.svelte'; import Tabs from '$lib/holocene/tab/tabs.svelte'; import { translate } from '$lib/i18n/translate'; - import { pathMatches } from '$lib/utilities/path-matches'; - import { - routeForWorkerDeployments, - routeForWorkers, - } from '$lib/utilities/route-for'; interface Props { - namespace: string; children: Snippet; + deploymentsHref: string; + workersHref: string; } - const { children, namespace }: Props = $props(); - - const workersHref = $derived(routeForWorkers({ namespace })); - const deploymentsHref = $derived(routeForWorkerDeployments({ namespace })); + const { children, deploymentsHref, workersHref }: Props = $props();

@@ -35,11 +28,7 @@ label={translate('workers.instance', { count: 2 })} id="workers-tab" href={workersHref} - active={pathMatches( - page.url.pathname, - routeForWorkers({ namespace }), - true, - )} + active={page.url.pathname.endsWith('/workers')} > Pre-Release @@ -47,11 +36,7 @@ label={translate('deployments.deployments')} id="deployments-tab" href={deploymentsHref} - active={pathMatches( - page.url.pathname, - routeForWorkerDeployments({ namespace }), - true, - )} + active={page.url.pathname.endsWith('/deployments')} /> diff --git a/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte index 6ce36a4496..6991765c41 100644 --- a/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workers/+page.svelte @@ -5,11 +5,17 @@ import { translate } from '$lib/i18n/translate'; import WorkersLayout from '$lib/layouts/workers-layout.svelte'; import Workers from '$lib/pages/workers.svelte'; + import { + routeForWorkerDeployments, + routeForWorkers, + } from '$lib/utilities/route-for'; const namespace = $derived(page.params.namespace); + const deploymentsHref = $derived(routeForWorkerDeployments({ namespace })); + const workersHref = $derived(routeForWorkers({ namespace })); - + diff --git a/src/routes/(app)/namespaces/[namespace]/workers/deployments/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workers/deployments/+page.svelte index 7c383d347a..d6992fe19d 100644 --- a/src/routes/(app)/namespaces/[namespace]/workers/deployments/+page.svelte +++ b/src/routes/(app)/namespaces/[namespace]/workers/deployments/+page.svelte @@ -5,14 +5,20 @@ import { translate } from '$lib/i18n/translate'; import WorkersLayout from '$lib/layouts/workers-layout.svelte'; import WorkerDeployments from '$lib/pages/deployments.svelte'; + import { + routeForWorkerDeployments, + routeForWorkers, + } from '$lib/utilities/route-for'; const namespace = $derived(page.params.namespace); + const deploymentsHref = $derived(routeForWorkerDeployments({ namespace })); + const workersHref = $derived(routeForWorkers({ namespace })); - + From d45a8083e8cbba53ce758eca25c7ede4866e40a3 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Thu, 2 Apr 2026 17:17:40 -0600 Subject: [PATCH 33/40] Add version check to SDK warning --- .../worker-heartbeats-sdk-warning.svelte | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/lib/components/workers/workers-table/worker-heartbeats-sdk-warning.svelte b/src/lib/components/workers/workers-table/worker-heartbeats-sdk-warning.svelte index 38bd6d519c..89ba228669 100644 --- a/src/lib/components/workers/workers-table/worker-heartbeats-sdk-warning.svelte +++ b/src/lib/components/workers/workers-table/worker-heartbeats-sdk-warning.svelte @@ -4,6 +4,7 @@ import Link from '$lib/holocene/link.svelte'; import { translate } from '$lib/i18n/translate'; import { sdkInfo } from '$lib/stores/events'; + import { minimumVersionRequired } from '$lib/utilities/version-check'; const supportedVersions = [ { @@ -33,7 +34,9 @@ }, ]; const currentSdk = $derived( - supportedVersions.find(({ sdk }) => sdk === $sdkInfo.sdk), + supportedVersions.find( + ({ sdk }) => sdk.toLowerCase() === $sdkInfo.sdk.toLowerCase(), + ), ); @@ -49,15 +52,15 @@
{translate('workers.no-worker-heartbeats')}
{#if currentSdk} {@const { href, sdk, version } = currentSdk} -

- {translate('workers.worker-heartbeats-sdk-link-preface')} - - - - {translate('workers.worker-heartbeats-sdk-link-postface')} -

+ {#if !minimumVersionRequired(version, $sdkInfo.version)} +

+ {translate('workers.worker-heartbeats-sdk-link-preface')} + + + + {translate('workers.worker-heartbeats-sdk-link-postface')} +

+ {/if} {:else}

{translate('workers.worker-heartbeats-sdk-list-preface')} From 047c35c633d93a719f476a7117312a36f815cc91 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Fri, 3 Apr 2026 13:55:43 -0600 Subject: [PATCH 34/40] UI updates from bug bash --- .../worker-details/worker-details.svelte | 103 +++++++++--------- .../workers/worker-heartbeats-disabled.svelte | 7 +- src/lib/holocene/icon/svg/settings.svelte | 2 +- src/lib/holocene/icon/svg/workers.svelte | 4 +- src/lib/i18n/locales/en/workers.ts | 10 +- src/lib/layouts/workers-layout.svelte | 7 +- src/lib/pages/deployments.svelte | 2 + src/lib/pages/workers.svelte | 7 ++ 8 files changed, 74 insertions(+), 68 deletions(-) diff --git a/src/lib/components/workers/worker-details/worker-details.svelte b/src/lib/components/workers/worker-details/worker-details.svelte index c11408159b..e2b391a995 100644 --- a/src/lib/components/workers/worker-details/worker-details.svelte +++ b/src/lib/components/workers/worker-details/worker-details.svelte @@ -59,7 +59,7 @@ if (total === 0) return '0'; return ((totalStickyCacheHit / total) * 100).toFixed(1); }); - const pollSuccessRate = $derived.by(() => { + const taskSuccessRate = $derived.by(() => { if (!heartbeat?.workflowTaskSlotsInfo?.totalProcessedTasks) return '0'; const successCount = heartbeat.workflowTaskSlotsInfo.totalProcessedTasks - @@ -153,11 +153,15 @@ {translate('deployments.build-id')} - + {@const buildId = heartbeat?.deploymentVersion?.buildId} + + + {#if heartbeat?.deploymentVersion?.deploymentName} {translate('deployments.deployment')}

{title}

- {#if slots?.slotSupplierKind} - {@const tooltipText = SupplierKindTooltipText[slots.slotSupplierKind]} - - {slots.slotSupplierKind} - - {/if}
-
+
{translate('workers.slots')} + {#if slots?.slotSupplierKind} + {@const tooltipText = + SupplierKindTooltipText[slots.slotSupplierKind]} + + {slots.slotSupplierKind} + + {/if}
-

- {slots?.currentUsedSlots ?? 0} -

-

- {#if slots?.currentAvailableSlots} - {slots.currentAvailableSlots - (slots?.currentUsedSlots ?? 0)} - {:else} - - - {/if} -

-
-
-

{translate('workers.used')}

-

- {#if slots?.currentAvailableSlots} - {translate('workers.available-out-of', { - count: slots.currentAvailableSlots, - })} - {:else} - {translate('workers.none-available')} - {/if} -

+
+

+ {slots?.currentUsedSlots ?? 0} +

+

{translate('workers.used')}

+
+
+

+ {#if slots?.currentAvailableSlots} + {slots.currentAvailableSlots - (slots?.currentUsedSlots ?? 0)} + {:else} + - + {/if} +

+

+ {#if slots?.currentAvailableSlots} + {translate('workers.available-out-of', { + count: slots.currentAvailableSlots, + })} + {:else} + {translate('workers.none-available')} + {/if} +

+
- {@render meterBar( - `slots-${title}`, - slots?.currentUsedSlots ?? 0, - slots?.currentAvailableSlots - ? slots.currentAvailableSlots - : (slots?.currentUsedSlots ?? 0), - )}
-
+
{translate('workers.tasks-processed')}
@@ -275,7 +277,7 @@ {#if poller}
-
+
{translate('workers.poller')} {translate('workers.process-id')}
{heartbeat?.hostInfo?.processId ?? '-'}
- -
- {translate('workers.worker-grouping')} -
-
- {heartbeat?.hostInfo?.workerGroupingKey ?? '-'} -
{@render hostUsage()} @@ -429,9 +424,9 @@ {translate('workers.diagnostics')}
-
{pollSuccessRate}%
+
{taskSuccessRate}%
- {translate('workers.poll-success-rate')} + {translate('workers.task-success-rate')}
diff --git a/src/lib/components/workers/worker-heartbeats-disabled.svelte b/src/lib/components/workers/worker-heartbeats-disabled.svelte index aa3bddea83..3476fcddc1 100644 --- a/src/lib/components/workers/worker-heartbeats-disabled.svelte +++ b/src/lib/components/workers/worker-heartbeats-disabled.svelte @@ -15,7 +15,12 @@ aria-live="polite" > - +
diff --git a/src/lib/holocene/icon/svg/settings.svelte b/src/lib/holocene/icon/svg/settings.svelte index 4793577200..c89f5dc7fe 100644 --- a/src/lib/holocene/icon/svg/settings.svelte +++ b/src/lib/holocene/icon/svg/settings.svelte @@ -5,7 +5,7 @@ diff --git a/src/lib/holocene/icon/svg/workers.svelte b/src/lib/holocene/icon/svg/workers.svelte index 82441dbb1a..4793577200 100644 --- a/src/lib/holocene/icon/svg/workers.svelte +++ b/src/lib/holocene/icon/svg/workers.svelte @@ -5,7 +5,7 @@ diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index 4756e78c02..3cfc22b2c6 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -18,6 +18,7 @@ export const Strings = { identity: 'Identity', instance_one: 'Instance', instance_other: 'Instances', + 'worker-instances': 'Worker Instances', 'task-queue': 'Task Queue', status: 'Status', 'shutting-down': 'Shutting Down', @@ -56,14 +57,13 @@ export const Strings = { 'no-activity': 'No Activity', 'host-info': 'Host Info', 'process-id': 'Process ID', - 'worker-grouping': 'Worker Grouping', 'cpu-usage': 'CPU Usage', 'memory-usage': 'Memory Usage', 'workflow-cache': 'Workflow Cache', 'cache-size': 'Cache size', 'cache-hits': 'Cache hits', diagnostics: 'Diagnostics', - 'poll-success-rate': 'Poll success rate', + 'task-success-rate': 'Task success rate', 'troubleshooting-workers-link': 'Troubleshooting workers', poller: 'Poller', 'go-dependency-warning': 'Missing Dependency', @@ -74,13 +74,13 @@ export const Strings = { 'worker-heartbeats-sdk-link-preface': 'Upgrade SDK to', 'worker-heartbeats-sdk-link-postface': 'to enable.', 'worker-heartbeats-sdk-list-preface': - 'Upgrade SDK to one of the following versions', + 'Start Workers and ensure SDK is one of the following versions', 'worker-heartbeats-disabled': 'Worker Heartbeats Disabled', 'worker-heartbeats-enablement': - 'To enable Heartbeats for this Namespace, set the following value in Dynamic Config', + 'To view Workers, enable Heartbeats for this Namespace. Set the following value in Dynamic Config.', 'manual-poller': 'Poller count is set to a fixed number.', 'autoscaling-poller': 'Poller count adjusts automatically based on workload.', - 'slot-supplier-kind-fixed': 'Poller count is set to a fixed number.', + 'slot-supplier-kind-fixed': 'Slot count is set to a fixed number.', 'slot-supplier-kind-resource-based': 'Slot count adjusts based on CPU and memory usage.', 'slot-supplier-kind-custom': 'Slot count is managed by custom logic.', diff --git a/src/lib/layouts/workers-layout.svelte b/src/lib/layouts/workers-layout.svelte index fbda75eab1..c708d355d4 100644 --- a/src/lib/layouts/workers-layout.svelte +++ b/src/lib/layouts/workers-layout.svelte @@ -3,7 +3,6 @@ import { page } from '$app/state'; - import Badge from '$lib/holocene/badge.svelte'; import TabList from '$lib/holocene/tab/tab-list.svelte'; import Tab from '$lib/holocene/tab/tab.svelte'; import Tabs from '$lib/holocene/tab/tabs.svelte'; @@ -19,7 +18,7 @@
-

+

{translate('workers.workers')}

@@ -29,9 +28,7 @@ id="workers-tab" href={workersHref} active={page.url.pathname.endsWith('/workers')} - > - Pre-Release - + > +

{translate('deployments.worker-deployments')}

+ {#key [namespace]} +

+ {translate('workers.worker-instances')} + Pre-Release +

+ {#if workerHeartbeatsEnabled} Date: Fri, 3 Apr 2026 15:05:19 -0600 Subject: [PATCH 35/40] Add outdated worker data alert with refresh --- .../worker-details/worker-details.svelte | 40 ++++++++++++++++++- src/lib/i18n/locales/en/workers.ts | 3 ++ src/lib/pages/worker-details.svelte | 14 +++++-- 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/lib/components/workers/worker-details/worker-details.svelte b/src/lib/components/workers/worker-details/worker-details.svelte index e2b391a995..9aa55bbf03 100644 --- a/src/lib/components/workers/worker-details/worker-details.svelte +++ b/src/lib/components/workers/worker-details/worker-details.svelte @@ -15,6 +15,7 @@ import WorkerStatus from '$lib/components/workers/worker-status.svelte'; import Alert from '$lib/holocene/alert.svelte'; import Badge from '$lib/holocene/badge.svelte'; + import Button from '$lib/holocene/button.svelte'; import Card from '$lib/holocene/card.svelte'; import Copyable from '$lib/holocene/copyable/index.svelte'; import Icon from '$lib/holocene/icon/icon.svelte'; @@ -33,9 +34,10 @@ type Props = { breadcrumb: Snippet; worker: WorkerInfo | null | undefined; + onrefresh: () => void; }; - let { breadcrumb, worker }: Props = $props(); + let { breadcrumb, worker, onrefresh }: Props = $props(); const { namespace } = $derived(page.params); const heartbeat = $derived(worker?.workerHeartbeat); @@ -70,6 +72,24 @@ ).toFixed(1); }); + let isHeartbeatStale = $state(false); + + function checkHeartbeatStaleness() { + if (!heartbeat?.heartbeatTime) { + isHeartbeatStale = false; + return; + } + const elapsed = + Date.now() - new Date(heartbeat.heartbeatTime as string).getTime(); + isHeartbeatStale = elapsed >= 60_000; + } + + $effect(() => { + checkHeartbeatStaleness(); + const interval = setInterval(checkHeartbeatStaleness, 10_000); + return () => clearInterval(interval); + }); + const SupplierKindTooltipText: Record = { Fixed: translate('workers.slot-supplier-kind-fixed'), ResourceBased: translate('workers.slot-supplier-kind-resource-based'), @@ -108,6 +128,24 @@
+ + {translate('workflows.last-heartbeat')} diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index 3cfc22b2c6..6eae9c4a35 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -84,4 +84,7 @@ export const Strings = { 'slot-supplier-kind-resource-based': 'Slot count adjusts based on CPU and memory usage.', 'slot-supplier-kind-custom': 'Slot count is managed by custom logic.', + 'stale-heartbeat-title': 'Worker data may be outdated', + 'stale-heartbeat-description': + 'More than a minute has elapsed since the last heartbeat. Refresh to see the latest.', } as const; diff --git a/src/lib/pages/worker-details.svelte b/src/lib/pages/worker-details.svelte index 6d3c1c9b9d..6abb92d022 100644 --- a/src/lib/pages/worker-details.svelte +++ b/src/lib/pages/worker-details.svelte @@ -10,9 +10,11 @@ import { routeForWorkers } from '$lib/utilities/route-for'; const { namespace, workerInstanceKey } = $derived(page.params); - const workerDetailsPromise = $derived( - describeWorker({ namespace, workerInstanceKey }), - ); + let refresh = $state(Date.now()); + const workerDetailsPromise = $derived.by(() => { + void refresh; + return describeWorker({ namespace, workerInstanceKey }); + }); {#snippet breadcrumb()} @@ -28,7 +30,11 @@ {#await workerDetailsPromise} {:then data} - + (refresh = Date.now())} + /> {:catch error} {@render breadcrumb()} From cd8d13a86c2f9ffea8187287d8b78f856d3f2c36 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Mon, 6 Apr 2026 15:17:26 -0600 Subject: [PATCH 36/40] Remove diagnostics section and add workflow cache link --- .../worker-details/worker-details.svelte | 25 ++++++++++++------- src/lib/i18n/locales/en/workers.ts | 4 +-- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/lib/components/workers/worker-details/worker-details.svelte b/src/lib/components/workers/worker-details/worker-details.svelte index 9aa55bbf03..7cb68b57bb 100644 --- a/src/lib/components/workers/worker-details/worker-details.svelte +++ b/src/lib/components/workers/worker-details/worker-details.svelte @@ -61,13 +61,11 @@ if (total === 0) return '0'; return ((totalStickyCacheHit / total) * 100).toFixed(1); }); - const taskSuccessRate = $derived.by(() => { + const taskFailureRate = $derived.by(() => { if (!heartbeat?.workflowTaskSlotsInfo?.totalProcessedTasks) return '0'; - const successCount = - heartbeat.workflowTaskSlotsInfo.totalProcessedTasks - - (heartbeat.workflowTaskSlotsInfo.totalFailedTasks ?? 0); + const failureCount = heartbeat.workflowTaskSlotsInfo.totalFailedTasks ?? 0; return ( - (successCount / heartbeat.workflowTaskSlotsInfo.totalProcessedTasks) * + (failureCount / heartbeat.workflowTaskSlotsInfo.totalProcessedTasks) * 100 ).toFixed(1); }); @@ -242,7 +240,8 @@
@@ -377,7 +376,7 @@ href="https://docs.temporal.io/cloud/worker-health#enable-host-resource-reporting" newTab > - {translate('workers.go-dependency-warning-link')} + {translate('workers.learn-more-link')} {/snippet} @@ -453,6 +452,14 @@
+ + {translate('workers.learn-more-link')} + {/snippet} @@ -462,9 +469,9 @@ {translate('workers.diagnostics')}
-
{taskSuccessRate}%
+
{taskFailureRate}%
- {translate('workers.task-success-rate')} + {translate('workers.task-failure-rate')}
diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts index 6eae9c4a35..6f63100a09 100644 --- a/src/lib/i18n/locales/en/workers.ts +++ b/src/lib/i18n/locales/en/workers.ts @@ -63,13 +63,13 @@ export const Strings = { 'cache-size': 'Cache size', 'cache-hits': 'Cache hits', diagnostics: 'Diagnostics', - 'task-success-rate': 'Task success rate', + 'task-failure-rate': 'Task failure rate', 'troubleshooting-workers-link': 'Troubleshooting workers', poller: 'Poller', 'go-dependency-warning': 'Missing Dependency', 'go-dependency-warning-description': 'No usage data is available for this Worker. A Go SDK dependency may need to be enabled to surface this data.', - 'go-dependency-warning-link': 'Learn more.', + 'learn-more-link': 'Learn more.', 'no-worker-heartbeats': 'No Workers Sending Heartbeats', 'worker-heartbeats-sdk-link-preface': 'Upgrade SDK to', 'worker-heartbeats-sdk-link-postface': 'to enable.', From 525e8d0408f0cba79baf826f1da17c99e0fdd5cc Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Tue, 7 Apr 2026 12:02:07 -0600 Subject: [PATCH 37/40] UI updates --- .../workers/worker-details/worker-details.svelte | 2 +- .../components/workers/worker-heartbeats-disabled.svelte | 2 +- src/lib/i18n/locales/en/workers.ts | 5 ++--- src/lib/layouts/workers-layout.svelte | 5 ++++- src/lib/pages/deployments.svelte | 2 -- src/lib/pages/workers.svelte | 7 ------- 6 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/lib/components/workers/worker-details/worker-details.svelte b/src/lib/components/workers/worker-details/worker-details.svelte index 7cb68b57bb..4a49a5d9d9 100644 --- a/src/lib/components/workers/worker-details/worker-details.svelte +++ b/src/lib/components/workers/worker-details/worker-details.svelte @@ -135,7 +135,7 @@

{translate('workers.stale-heartbeat-description')}