-
-
-
+
+
+
+
+
+
+
+ {#if $workflowCount?.totalCount >= 0 && $supportsAdvancedVisibility && !$groupByCountEnabled}
+
+ {#if $loading || $updating}
+
+ {:else if query}
+
+ {:else}
+
+ {/if}
+
+ {/if}
+
+
- {#if $workflowCount?.totalCount >= 0 && $supportsAdvancedVisibility}
-
- {#if $loading || $updating}
-
- {:else if query}
-
- {:else}
-
- {/if}
-
- {/if}
- {#if !$loading && !$updating && $workflows.length > 0}
-
-
exportWorkflows($workflows)}
- >{translate('download-json')}
- {/if}
+
exportWorkflows($workflows)}
+ >{translate('download-json')}
+
-
-
-
+ {#if $groupByCountEnabled}
+
+ {/if}
+
diff --git a/src/lib/services/cluster-service.ts b/src/lib/services/cluster-service.ts
index b7e05e2cd..dfa447326 100644
--- a/src/lib/services/cluster-service.ts
+++ b/src/lib/services/cluster-service.ts
@@ -1,5 +1,4 @@
-import { cluster } from '$lib/stores/cluster';
-import type { GetClusterInfoResponse } from '$lib/types';
+import type { GetClusterInfoResponse, GetSystemInfoResponse } from '$lib/types';
import type { Settings } from '$lib/types/global';
import { requestFromAPI } from '$lib/utilities/request-from-api';
import { routeForApi } from '$lib/utilities/route-for-api';
@@ -14,7 +13,20 @@ export const fetchCluster = async (
return await requestFromAPI(route, {
request,
}).then((clusterInformation) => {
- cluster.set(clusterInformation);
return clusterInformation;
});
};
+
+export const fetchSystemInfo = async (
+ settings: Settings,
+ request = fetch,
+): Promise => {
+ if (settings.runtimeEnvironment.isCloud) return;
+
+ const route = routeForApi('systemInfo');
+ return await requestFromAPI(route, {
+ request,
+ }).then((systemInformation) => {
+ return systemInformation;
+ });
+};
diff --git a/src/lib/services/settings-service.ts b/src/lib/services/settings-service.ts
index 71889146a..9ade90711 100644
--- a/src/lib/services/settings-service.ts
+++ b/src/lib/services/settings-service.ts
@@ -1,6 +1,5 @@
import { BROWSER } from 'esm-env';
-import { settings } from '$lib/stores/settings';
import type { SettingsResponse } from '$lib/types';
import type { Settings } from '$lib/types/global';
import { getApiOrigin } from '$lib/utilities/get-api-origin';
@@ -62,7 +61,5 @@ export const fetchSettings = async (request = fetch): Promise => {
version: settingsResponse?.Version,
};
- settings.set(settingsInformation);
-
return settingsInformation;
};
diff --git a/src/lib/services/workflow-counts.ts b/src/lib/services/workflow-counts.ts
new file mode 100644
index 000000000..e7925aa69
--- /dev/null
+++ b/src/lib/services/workflow-counts.ts
@@ -0,0 +1,81 @@
+import { noop } from 'svelte/internal';
+
+import type { WorkflowFilter } from '$lib/models/workflow-filters';
+import type { CountWorkflowExecutionsResponse } from '$lib/types/workflows';
+import { toListWorkflowQueryFromFilters } from '$lib/utilities/query/filter-workflow-query';
+import { combineFilters } from '$lib/utilities/query/to-list-workflow-filters';
+import { requestFromAPI } from '$lib/utilities/request-from-api';
+import { routeForApi } from '$lib/utilities/route-for-api';
+
+export const fetchWorkflowCount = async (
+ namespace: string,
+ query: string,
+ request = fetch,
+): Promise<{ totalCount: number; count: number }> => {
+ let totalCount = 0;
+ let count = 0;
+ try {
+ const countRoute = routeForApi('workflows.count', { namespace });
+ if (!query) {
+ const totalCountResult = await requestFromAPI<{ count: string }>(
+ countRoute,
+ {
+ params: {},
+ onError: noop,
+ handleError: noop,
+ request,
+ },
+ );
+ totalCount = parseInt(totalCountResult?.count);
+ } else {
+ const countPromise = requestFromAPI<{ count: string }>(countRoute, {
+ params: { query },
+ onError: noop,
+ handleError: noop,
+ request,
+ });
+ const totalCountPromise = requestFromAPI<{ count: string }>(countRoute, {
+ params: { query: '' },
+ onError: noop,
+ handleError: noop,
+ request,
+ });
+ const [countResult, totalCountResult] = await Promise.all([
+ countPromise,
+ totalCountPromise,
+ ]);
+ count = parseInt(countResult?.count ?? '0');
+ totalCount = parseInt(totalCountResult?.count);
+ }
+ } catch (e) {
+ // Don't fail the workflows call due to count
+ }
+
+ return { count, totalCount };
+};
+
+type WorkflowCountByExecutionStatusOptions = {
+ namespace: string;
+ filters: WorkflowFilter[];
+};
+
+export const fetchWorkflowCountByExecutionStatus = async ({
+ namespace,
+ filters,
+}: WorkflowCountByExecutionStatusOptions): Promise => {
+ try {
+ const groupByClause = 'GROUP BY ExecutionStatus';
+ const countRoute = routeForApi('workflows.count', {
+ namespace,
+ });
+ const query = toListWorkflowQueryFromFilters(combineFilters(filters));
+ const { count, groups } =
+ await requestFromAPI(countRoute, {
+ params: { query: `${query} ${groupByClause}` },
+ notifyOnError: false,
+ });
+ return { count, groups };
+ } catch (e) {
+ return { count: '0', groups: [] };
+ }
+};
diff --git a/src/lib/services/workflow-service.ts b/src/lib/services/workflow-service.ts
index a05785f4f..cba87c175 100644
--- a/src/lib/services/workflow-service.ts
+++ b/src/lib/services/workflow-service.ts
@@ -1,4 +1,3 @@
-import { noop } from 'svelte/internal';
import { get } from 'svelte/store';
import { v4 } from 'uuid';
@@ -88,53 +87,6 @@ export type FetchWorkflow =
| typeof fetchAllWorkflows
| typeof fetchAllArchivedWorkflows;
-export const fetchWorkflowCount = async (
- namespace: string,
- query: string,
- request = fetch,
-): Promise<{ totalCount: number; count: number }> => {
- let totalCount = 0;
- let count = 0;
- try {
- const countRoute = routeForApi('workflows.count', { namespace });
- if (!query) {
- const totalCountResult = await requestFromAPI<{ count: string }>(
- countRoute,
- {
- params: {},
- onError: noop,
- handleError: noop,
- request,
- },
- );
- totalCount = parseInt(totalCountResult?.count);
- } else {
- const countPromise = requestFromAPI<{ count: string }>(countRoute, {
- params: { query },
- onError: noop,
- handleError: noop,
- request,
- });
- const totalCountPromise = requestFromAPI<{ count: string }>(countRoute, {
- params: { query: '' },
- onError: noop,
- handleError: noop,
- request,
- });
- const [countResult, totalCountResult] = await Promise.all([
- countPromise,
- totalCountPromise,
- ]);
- count = parseInt(countResult?.count ?? '0');
- totalCount = parseInt(totalCountResult?.count);
- }
- } catch (e) {
- // Don't fail the workflows call due to count
- }
-
- return { count, totalCount };
-};
-
export const fetchAllWorkflows = async (
namespace: string,
parameters: ValidWorkflowParameters,
diff --git a/src/lib/stores/bulk-actions.test.ts b/src/lib/stores/bulk-actions.test.ts
index abf60b5d4..aa4a8f2f1 100644
--- a/src/lib/stores/bulk-actions.test.ts
+++ b/src/lib/stores/bulk-actions.test.ts
@@ -1,10 +1,8 @@
import { get, type writable as writableFunc } from 'svelte/store';
-import { beforeEach, describe, expect, test, vi } from 'vitest';
+import { describe, expect, test, vi } from 'vitest';
import { supportsBulkActions } from './bulk-actions';
-import { cluster } from './cluster';
-import { settings } from './settings';
const mockedPageStore = await vi.hoisted(async () => {
const { writable } = await vi.importActual<{
@@ -24,16 +22,15 @@ vi.mock('$app/stores', () => ({
describe('supportsBulkActions store', () => {
describe('for Cloud', () => {
- beforeEach(() => {
- mockedPageStore.mockSetSubscribeValue({
- data: { settings: { runtimeEnvironment: { isCloud: true } } },
- });
- });
-
test('returns true when batch actions are enabled, and visibility store is elasticsearch regardless of server version', () => {
- cluster.set({ serverVersion: '1.0.0', visibilityStore: 'elasticsearch' });
- settings.set({
- batchActionsDisabled: false,
+ mockedPageStore.mockSetSubscribeValue({
+ data: {
+ settings: {
+ runtimeEnvironment: { isCloud: true },
+ batchActionsDisabled: false,
+ },
+ cluster: { serverVersion: '1.0.0', visibilityStore: 'elasticsearch' },
+ },
});
expect(get(supportsBulkActions)).toBe(true);
@@ -41,50 +38,67 @@ describe('supportsBulkActions store', () => {
});
describe('for Local', () => {
- beforeEach(() => {
- mockedPageStore.mockSetSubscribeValue({
- data: { settings: { runtimeEnvironment: { isCloud: false } } },
- });
- });
test('returns true when version is newer than 1.18.0, advanced visibility is supported, and batch actions are enabled', () => {
- cluster.set({
- serverVersion: '1.19.0',
- visibilityStore: 'elasticsearch',
- });
- settings.set({
- batchActionsDisabled: false,
+ mockedPageStore.mockSetSubscribeValue({
+ data: {
+ settings: {
+ runtimeEnvironment: { isCloud: false },
+ batchActionsDisabled: false,
+ },
+ cluster: {
+ serverVersion: '1.19.0',
+ visibilityStore: 'elasticsearch',
+ },
+ },
});
expect(get(supportsBulkActions)).toBe(true);
});
test('returns false when version is older, even if visibility store is elasticsearch and batch actions are enabled', () => {
- cluster.set({
- serverVersion: '1.17.0',
- visibilityStore: 'elasticsearch',
- });
- settings.set({
- batchActionsDisabled: false,
+ mockedPageStore.mockSetSubscribeValue({
+ data: {
+ settings: {
+ runtimeEnvironment: { isCloud: false },
+ batchActionsDisabled: false,
+ },
+ cluster: {
+ serverVersion: '1.17.0',
+ visibilityStore: 'elasticsearch',
+ },
+ },
});
expect(get(supportsBulkActions)).toBe(false);
});
test('returns false when advanced visibility store is not elasticsearch, even if version is newer and batch actions are enabled', () => {
- cluster.set({ serverVersion: '1.19.0', visibilityStore: 'mysql' });
- settings.set({
- batchActionsDisabled: false,
+ mockedPageStore.mockSetSubscribeValue({
+ data: {
+ settings: {
+ runtimeEnvironment: { isCloud: false },
+ batchActionsDisabled: false,
+ },
+ cluster: { serverVersion: '1.19.0', visibilityStore: 'mysql' },
+ },
});
expect(get(supportsBulkActions)).toBe(false);
});
test('returns false when batch actions are not enabled, even if version is newer and advanced visibility is supported', () => {
- cluster.set({
- serverVersion: '1.19.0',
- visibilityStore: 'elasticsearch',
+ mockedPageStore.mockSetSubscribeValue({
+ data: {
+ settings: {
+ runtimeEnvironment: { isCloud: false },
+ batchActionsDisabled: true,
+ },
+ cluster: {
+ serverVersion: '1.19.0',
+ visibilityStore: 'elasticsearch',
+ },
+ },
});
- settings.set({ batchActionsDisabled: true });
expect(get(supportsBulkActions)).toBe(false);
});
diff --git a/src/lib/stores/cluster.ts b/src/lib/stores/cluster.ts
index 8cadb61f9..297683249 100644
--- a/src/lib/stores/cluster.ts
+++ b/src/lib/stores/cluster.ts
@@ -1,5 +1,7 @@
-import { writable } from 'svelte/store';
+import { derived } from 'svelte/store';
-import type { GetClusterInfoResponse } from '$lib/types';
+import { page } from '$app/stores';
-export const cluster = writable({});
+export const cluster = derived([page], ([$page]) => {
+ return $page.data?.cluster;
+});
diff --git a/src/lib/stores/group-by-enabled.ts b/src/lib/stores/group-by-enabled.ts
new file mode 100644
index 000000000..173756a7f
--- /dev/null
+++ b/src/lib/stores/group-by-enabled.ts
@@ -0,0 +1,9 @@
+import { derived } from 'svelte/store';
+
+import { page } from '$app/stores';
+
+export const groupByCountEnabled = derived([page], ([$page]) => {
+ return Boolean(
+ $page.data?.systemInfo?.capabilities?.countGroupByExecutionStatus,
+ );
+});
diff --git a/src/lib/stores/settings.ts b/src/lib/stores/settings.ts
index 90bd0f49d..15242ad54 100644
--- a/src/lib/stores/settings.ts
+++ b/src/lib/stores/settings.ts
@@ -1,33 +1,5 @@
-import { writable } from 'svelte/store';
+import { derived } from 'svelte/store';
-import type { Settings } from '$lib/types/global';
+import { page } from '$app/stores';
-export const settings = writable({
- auth: {
- enabled: false,
- options: null,
- },
- baseUrl: '',
- codec: {
- endpoint: '',
- passAccessToken: false,
- includeCredentials: false,
- },
- defaultNamespace: null,
- disableWriteActions: false,
- batchActionsDisabled: false,
- workflowTerminateDisabled: false,
- workflowCancelDisabled: false,
- workflowSignalDisabled: false,
- workflowResetDisabled: false,
- hideWorkflowQueryErrors: false,
- showTemporalSystemNamespace: false,
- notifyOnNewVersion: false,
- feedbackURL: '',
- runtimeEnvironment: {
- isCloud: false,
- isLocal: true,
- envOverride: true,
- },
- version: '',
-});
+export const settings = derived([page], ([$page]) => $page.data.settings);
diff --git a/src/lib/stores/workflows.ts b/src/lib/stores/workflows.ts
index 9a5c2a323..abce870bc 100644
--- a/src/lib/stores/workflows.ts
+++ b/src/lib/stores/workflows.ts
@@ -1,17 +1,16 @@
import type { StartStopNotifier } from 'svelte/store';
-import { derived, readable, writable } from 'svelte/store';
+import { derived, get, readable, writable } from 'svelte/store';
import { page } from '$app/stores';
import { translate } from '$lib/i18n/translate';
-import {
- fetchAllWorkflows,
- fetchWorkflowCount,
-} from '$lib/services/workflow-service';
+import { fetchWorkflowCount } from '$lib/services/workflow-counts';
+import { fetchAllWorkflows } from '$lib/services/workflow-service';
import type { FilterParameters, WorkflowExecution } from '$lib/types/workflows';
import { withLoading } from '$lib/utilities/stores/with-loading';
import { supportsAdvancedVisibility } from './advanced-visibility';
+import { groupByCountEnabled } from './group-by-enabled';
export const refresh = writable(0);
export const hideWorkflowQueryErrors = derived(
@@ -64,7 +63,7 @@ const updateWorkflows: StartStopNotifier = (set) => {
});
set(workflows);
- if (supportsAdvancedVisibility) {
+ if (supportsAdvancedVisibility && !get(groupByCountEnabled)) {
const workflowCount = await fetchWorkflowCount(namespace, query);
setCounts(workflowCount);
}
diff --git a/src/lib/types/api.ts b/src/lib/types/api.ts
index daebd3dc4..0c2e9f96c 100644
--- a/src/lib/types/api.ts
+++ b/src/lib/types/api.ts
@@ -25,6 +25,7 @@ export type NamespaceAPIRoutePath = 'namespace';
export type TaskQueueAPIRoutePath = 'task-queue' | 'task-queue.compatibility';
export type ParameterlessAPIRoutePath =
+ | 'systemInfo'
| 'cluster'
| 'settings'
| 'user'
diff --git a/src/lib/types/global.ts b/src/lib/types/global.ts
index 94015b46e..3942f2df5 100644
--- a/src/lib/types/global.ts
+++ b/src/lib/types/global.ts
@@ -106,6 +106,7 @@ export type User = {
[key: string]: any;
};
export type ClusterInformation = import('$lib/types').GetClusterInfoResponse;
+export type SystemInformation = import('$lib/types').GetSystemInfoResponse;
export type SelectOptionValue = number | string | boolean;
diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts
index 907cf39db..077f82075 100644
--- a/src/lib/types/index.ts
+++ b/src/lib/types/index.ts
@@ -10,6 +10,8 @@ export type ListNamespacesResponse =
temporal.api.workflowservice.v1.IListNamespacesResponse;
export type GetClusterInfoResponse =
temporal.api.workflowservice.v1.IGetClusterInfoResponse;
+export type GetSystemInfoResponse =
+ temporal.api.workflowservice.v1.IGetSystemInfoResponse;
export type GetWorkflowExecutionHistoryResponse =
temporal.api.workflowservice.v1.IGetWorkflowExecutionHistoryResponse;
export type GetSearchAttributesResponse =
diff --git a/src/lib/types/workflows.ts b/src/lib/types/workflows.ts
index 41280ef05..4d7c69dff 100644
--- a/src/lib/types/workflows.ts
+++ b/src/lib/types/workflows.ts
@@ -1,4 +1,4 @@
-import type { WorkflowVersionTimpstamp } from '$lib/types';
+import type { Payloads, WorkflowVersionTimpstamp } from '$lib/types';
import type {
Payload,
@@ -30,6 +30,11 @@ export type ListWorkflowExecutionsResponse = Replace<
Optional<{ executions: WorkflowExecutionInfo[] }>
>;
+export type CountWorkflowExecutionsResponse = {
+ count: string;
+ groups: { count: string; groupValues: Payloads }[];
+};
+
export type WorkflowExecutionConfig = Replace<
import('$lib/types').WorkflowExecutionConfig,
{ defaultWorkflowTaskTimeout: Duration }
diff --git a/src/lib/utilities/route-for-api.ts b/src/lib/utilities/route-for-api.ts
index c47fd81d5..f5315bf31 100644
--- a/src/lib/utilities/route-for-api.ts
+++ b/src/lib/utilities/route-for-api.ts
@@ -100,6 +100,7 @@ export function pathForApi(
const routes: { [K in APIRoutePath]: string } = {
cluster: '/cluster',
+ systemInfo: '/system-info',
'events.ascending': `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/runs/${parameters?.runId}/events`,
'events.descending': `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/runs/${parameters?.runId}/events/reverse`,
namespaces: '/namespaces',
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte
index 8f5564a72..d33367ada 100644
--- a/src/routes/(app)/+layout.svelte
+++ b/src/routes/(app)/+layout.svelte
@@ -2,8 +2,6 @@
import { afterNavigate, goto } from '$app/navigation';
import { page, updated } from '$app/stores';
- import type { PageData } from './$types';
-
import DataEncoderSettings from '$lib/components/data-encoder-settings.svelte';
import SideNavigation from '$lib/components/side-nav.svelte';
import SkipNavigation from '$lib/components/skip-nav.svelte';
@@ -28,8 +26,6 @@
import type { DescribeNamespaceResponse as Namespace } from '$types';
- export let data: PageData;
-
let namespaceList: NamespaceListItem[];
$: isCloud = $page.data?.settings?.runtimeEnvironment?.isCloud;
diff --git a/src/routes/(app)/+layout.ts b/src/routes/(app)/+layout.ts
index 5ae99d1a4..23595c788 100644
--- a/src/routes/(app)/+layout.ts
+++ b/src/routes/(app)/+layout.ts
@@ -2,11 +2,11 @@ import { redirect } from '@sveltejs/kit';
import type { LayoutData, LayoutLoad } from './$types';
-import { fetchCluster } from '$lib/services/cluster-service';
+import { fetchCluster, fetchSystemInfo } from '$lib/services/cluster-service';
import { fetchNamespaces } from '$lib/services/namespaces-service';
import { fetchSettings } from '$lib/services/settings-service';
import { getAuthUser, setAuthUser } from '$lib/stores/auth-user';
-import type { GetClusterInfoResponse } from '$lib/types';
+import type { GetClusterInfoResponse, GetSystemInfoResponse } from '$lib/types';
import type { Settings } from '$lib/types/global';
import {
cleanAuthUserCookie,
@@ -37,10 +37,15 @@ export const load: LayoutLoad = async function ({
fetchNamespaces(settings, fetch);
const cluster: GetClusterInfoResponse = await fetchCluster(settings, fetch);
+ const systemInfo: GetSystemInfoResponse = await fetchSystemInfo(
+ settings,
+ fetch,
+ );
return {
user,
settings,
cluster,
+ systemInfo,
};
};
diff --git a/tests/integration/workflows-count.spec.ts b/tests/integration/workflows-count.spec.ts
new file mode 100644
index 000000000..d4f0fe8ae
--- /dev/null
+++ b/tests/integration/workflows-count.spec.ts
@@ -0,0 +1,96 @@
+import { expect, test } from '@playwright/test';
+
+import {
+ mockClusterApi,
+ mockSystemInfoApi,
+ mockWorkflowApis,
+ mockWorkflowsApis,
+ mockWorkflowsGroupByCountApi,
+ waitForWorkflowsApis,
+} from '~/test-utilities/mock-apis';
+
+test.describe('Workflows List with Counts when countGroupByExecutionStatus is disabled', () => {
+ test.beforeEach(async ({ page }) => {
+ await mockWorkflowsApis(page);
+ await mockWorkflowApis(page);
+
+ await mockClusterApi(page, {
+ visibilityStore: 'elasticsearch',
+ persistenceStore: 'postgres,elasticsearch',
+ });
+
+ await mockSystemInfoApi(page, {
+ capabilities: {
+ countGroupByExecutionStatus: false,
+ },
+ });
+
+ page.goto('/namespaces/default/workflows');
+
+ await waitForWorkflowsApis(page);
+ });
+
+ test.describe('Shows total count and result count', () => {
+ test('Results of workflows ', async ({ page }) => {
+ await page.waitForSelector(
+ '[data-testid="workflow-count"][data-loaded="true"]',
+ );
+
+ await expect(page.getByTestId('workflow-count')).toHaveText(
+ '15 workflows',
+ );
+
+ await page.fill('#manual-search', 'WorkflowType="ImportantWorkflowType"');
+ await page.click('[data-testid="manual-search-button"]');
+
+ await expect(page).toHaveURL(
+ /WorkflowType%3D%22ImportantWorkflowType%22/,
+ );
+
+ await page.getByTestId('workflow-type-filter-button').click();
+ const workflowTypeValue = await page.inputValue('#workflow-type');
+ expect(workflowTypeValue).toBe('ImportantWorkflowType');
+ await page.waitForSelector(
+ '[data-testid="workflow-count"][data-loaded="true"]',
+ );
+
+ await expect(page.getByTestId('workflow-count')).toHaveText(
+ 'Results 15 of 15 workflows',
+ );
+ });
+ });
+});
+
+test.describe('Workflows List with Counts when countGroupByExecutionStatus is enabled', () => {
+ test.beforeEach(async ({ page }) => {
+ await mockWorkflowsApis(page);
+ await mockWorkflowApis(page);
+
+ await mockClusterApi(page, {
+ visibilityStore: 'elasticsearch',
+ persistenceStore: 'postgres,elasticsearch',
+ });
+
+ page.goto('/namespaces/default/workflows');
+
+ await mockWorkflowsGroupByCountApi(page);
+ await waitForWorkflowsApis(page);
+ });
+
+ test.describe('Shows only result count', () => {
+ test('Counts of workflows ', async ({ page }) => {
+ await page.waitForSelector(
+ '[data-testid="workflow-count"][data-loaded="true"]',
+ );
+ await expect(page.getByTestId('workflow-count')).toHaveText(
+ '31,230 workflows',
+ );
+ await expect(page.getByTestId('workflow-status-Running')).toHaveText(
+ '6 Running',
+ );
+ await expect(page.getByTestId('workflow-status-Completed')).toHaveText(
+ '21,652 Completed',
+ );
+ });
+ });
+});
diff --git a/tests/integration/workflows-list-with-advanced-visibility.spec.ts b/tests/integration/workflows-list-with-advanced-visibility.spec.ts
index 1d57ab3a5..f335025b2 100644
--- a/tests/integration/workflows-list-with-advanced-visibility.spec.ts
+++ b/tests/integration/workflows-list-with-advanced-visibility.spec.ts
@@ -39,10 +39,6 @@ test.describe('Workflows List with Advanced Visibility', () => {
await page.waitForSelector(
'[data-testid="workflow-count"][data-loaded="true"]',
);
-
- await expect(page.getByTestId('workflow-count')).toHaveText(
- 'Results 15 of 15 workflows',
- );
});
});
@@ -158,10 +154,6 @@ test.describe('Workflows List with Advanced Visibility', () => {
await page.waitForSelector(
'[data-testid="workflow-count"][data-loaded="true"]',
);
-
- await expect(page.getByTestId('workflow-count')).toHaveText(
- 'Results 15 of 15 workflows',
- );
});
test('keeps workflow datetime filter after navigating away and back to workflow list', async ({
diff --git a/tests/test-utilities/mock-apis.ts b/tests/test-utilities/mock-apis.ts
index b69fb44c4..078778f1a 100644
--- a/tests/test-utilities/mock-apis.ts
+++ b/tests/test-utilities/mock-apis.ts
@@ -14,6 +14,7 @@ import { mockNamespaceApi } from './mocks/namespace';
import { mockNamespacesApi, NAMESPACES_API } from './mocks/namespaces';
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 { mockWorkflowApi, WORKFLOW_API } from './mocks/workflow';
import { mockWorkflowsApi, WORKFLOWS_API } from './mocks/workflows';
@@ -26,6 +27,8 @@ export { mockClusterApi, CLUSTER_API } from './mocks/cluster';
export { mockNamespaceApi, NAMESPACE_API } from './mocks/namespace';
export { mockNamespacesApi, NAMESPACES_API } from './mocks/namespaces';
export { mockSettingsApi, SETTINGS_API } from './mocks/settings';
+export { mockSystemInfoApi } from './mocks/system-info';
+
export {
mockSearchAttributesApi,
SEARCH_ATTRIBUTES_API,
@@ -34,6 +37,7 @@ export { mockWorkflowsApi, WORKFLOWS_API } from './mocks/workflows';
export { mockWorkflowApi, WORKFLOW_API } from './mocks/workflow';
export {
mockWorkflowsCountApi,
+ mockWorkflowsGroupByCountApi,
WORKFLOWS_COUNT_API,
} from './mocks/workflows-count';
export {
@@ -50,6 +54,7 @@ export const mockGlobalApis = (page: Page) => {
mockClusterApi(page),
mockNamespacesApi(page),
mockSettingsApi(page),
+ mockSystemInfoApi(page),
]);
};
diff --git a/tests/test-utilities/mocks/system-info.ts b/tests/test-utilities/mocks/system-info.ts
new file mode 100644
index 000000000..a2ac1039a
--- /dev/null
+++ b/tests/test-utilities/mocks/system-info.ts
@@ -0,0 +1,29 @@
+import type { Page } from '@playwright/test';
+
+import { GetSystemInfoResponse } from '$src/lib/types';
+
+export const SYSTEM_INFO_API = '**/api/v1/system-info**';
+
+const defaultSystemInfo = {
+ serverVersion: '1.22.0',
+ capabilities: {
+ signalAndQueryHeader: true,
+ internalErrorDifferentiation: true,
+ activityFailureIncludeHeartbeat: true,
+ supportsSchedules: true,
+ encodedFailureAttributes: true,
+ buildIdBasedVersioning: true,
+ upsertMemo: true,
+ eagerWorkflowStart: true,
+ sdkMetadata: true,
+ countGroupByExecutionStatus: true,
+ },
+};
+export const mockSystemInfoApi = async (
+ page: Page,
+ systemInfo: Partial = {},
+) => {
+ await page.route(SYSTEM_INFO_API, async (route) => {
+ route.fulfill({ json: { ...defaultSystemInfo, ...systemInfo } });
+ });
+};
diff --git a/tests/test-utilities/mocks/workflows-count.ts b/tests/test-utilities/mocks/workflows-count.ts
index 77dc1b268..a4f99fc05 100644
--- a/tests/test-utilities/mocks/workflows-count.ts
+++ b/tests/test-utilities/mocks/workflows-count.ts
@@ -7,3 +7,87 @@ export const mockWorkflowsCountApi = (page: Page) => {
return route.fulfill({ json: { count: '15' } });
});
};
+
+const groupByCountResponse = {
+ count: '31230',
+ groups: [
+ {
+ groupValues: [
+ {
+ metadata: {
+ encoding: 'anNvbi9wbGFpbg==',
+ type: 'S2V5d29yZA==',
+ },
+ data: 'IlJ1bm5pbmci',
+ },
+ ],
+ count: '6',
+ },
+ {
+ groupValues: [
+ {
+ metadata: {
+ encoding: 'anNvbi9wbGFpbg==',
+ type: 'S2V5d29yZA==',
+ },
+ data: 'IkNvbXBsZXRlZCI=',
+ },
+ ],
+ count: '21652',
+ },
+ {
+ groupValues: [
+ {
+ metadata: {
+ encoding: 'anNvbi9wbGFpbg==',
+ type: 'S2V5d29yZA==',
+ },
+ data: 'IkZhaWxlZCI=',
+ },
+ ],
+ count: '1932',
+ },
+ {
+ groupValues: [
+ {
+ metadata: {
+ encoding: 'anNvbi9wbGFpbg==',
+ type: 'S2V5d29yZA==',
+ },
+ data: 'IkNhbmNlbGVkIg==',
+ },
+ ],
+ count: '6215',
+ },
+ {
+ groupValues: [
+ {
+ metadata: {
+ encoding: 'anNvbi9wbGFpbg==',
+ type: 'S2V5d29yZA==',
+ },
+ data: 'IlRlcm1pbmF0ZWQi',
+ },
+ ],
+ count: '917',
+ },
+ {
+ groupValues: [
+ {
+ metadata: {
+ encoding: 'anNvbi9wbGFpbg==',
+ type: 'S2V5d29yZA==',
+ },
+ data: 'IlRpbWVkT3V0Ig==',
+ },
+ ],
+ count: '508',
+ },
+ ],
+};
+
+export const mockWorkflowsGroupByCountApi = (page: Page) => {
+ return page.route(WORKFLOWS_COUNT_API, (route) => {
+ return route.fulfill({ json: groupByCountResponse });
+ });
+};