diff --git a/src/lib/services/workflow-counts.ts b/src/lib/services/workflow-counts.ts index 2e987d8167..cc47336df3 100644 --- a/src/lib/services/workflow-counts.ts +++ b/src/lib/services/workflow-counts.ts @@ -1,4 +1,8 @@ -import type { CountWorkflowExecutionsResponse } from '$lib/types/workflows'; +import type { + CountSchedulesResponse, + CountWorkflowExecutionsResponse, +} from '$lib/types/workflows'; +import { isNotFound, isNotImplemented } from '$lib/utilities/handle-error'; import { requestFromAPI } from '$lib/utilities/request-from-api'; import { routeForApi } from '$lib/utilities/route-for-api'; import { TASK_FAILURES_QUERY } from '$lib/utilities/workflow-task-failures'; @@ -66,30 +70,45 @@ export const fetchWorkflowCountByExecutionStatus = async ({ return { count: count ?? '0', groups }; }; -export const fetchScheduleCount = async ({ - namespace, - query, -}: { - namespace: string; - query?: string; -}): Promise => { +// Uses the API in a private/unsupported way that will stop working in a future server release. +const fetchScheduleCountLegacy = async ( + namespace: string, + query?: string, +): Promise => { const scheduleFixedQuery = 'TemporalNamespaceDivision="TemporalScheduler" AND ExecutionStatus="Running"'; - const fullQuery = query ? `${scheduleFixedQuery} AND ${query}` : scheduleFixedQuery; - const countRoute = routeForApi('workflows.count', { - namespace, - }); + const countRoute = routeForApi('workflows.count', { namespace }); const { count } = await requestFromAPI( countRoute, { - params: { - query: fullQuery, - }, + params: { query: fullQuery }, notifyOnError: false, }, ); return count ?? '0'; }; + +export const fetchScheduleCount = async ({ + namespace, + query, +}: { + namespace: string; + query?: string; +}): Promise => { + try { + const countRoute = routeForApi('schedules.count', { namespace }); + const { count } = await requestFromAPI(countRoute, { + params: query ? { query } : {}, + notifyOnError: false, + }); + return count ?? '0'; + } catch (error: unknown) { + if (isNotImplemented(error) || isNotFound(error)) { + return fetchScheduleCountLegacy(namespace, query); + } + throw error; + } +}; diff --git a/src/lib/types/api.ts b/src/lib/types/api.ts index ad8b19c279..7aa11f7afa 100644 --- a/src/lib/types/api.ts +++ b/src/lib/types/api.ts @@ -44,7 +44,7 @@ export type ParameterlessAPIRoutePath = | 'nexus-endpoints' | 'namespaces'; export type WorkerAPIRoutePath = 'worker-task-reachability'; -export type SchedulesAPIRoutePath = 'schedules'; +export type SchedulesAPIRoutePath = 'schedules' | 'schedules.count'; export type ScheduleAPIRoutePath = | 'schedule' | 'schedule.patch' diff --git a/src/lib/types/workflows.ts b/src/lib/types/workflows.ts index 53110ba81f..00d8adb306 100644 --- a/src/lib/types/workflows.ts +++ b/src/lib/types/workflows.ts @@ -54,6 +54,11 @@ export type CountWorkflowExecutionsResponse = { groups?: { count: string; groupValues: Payloads }[]; }; +export type CountSchedulesResponse = { + count?: string; + groups?: { count: string; groupValues: Payloads }[]; +}; + export type WorkflowExecutionConfig = Replace< import('$lib/types').WorkflowExecutionConfig, { diff --git a/src/lib/utilities/handle-error.ts b/src/lib/utilities/handle-error.ts index 0269fa440c..26a1c369f3 100644 --- a/src/lib/utilities/handle-error.ts +++ b/src/lib/utilities/handle-error.ts @@ -76,6 +76,14 @@ export const isForbidden = (error: unknown): error is TemporalAPIError => { return hasStatusCode(error, 403); }; +export const isNotFound = (error: unknown): error is TemporalAPIError => { + return hasStatusCode(error, 404); +}; + +export const isNotImplemented = (error: unknown): error is TemporalAPIError => { + return hasStatusCode(error, 501); +}; + const hasStatusCode = ( error: unknown, statusCode: number | string, diff --git a/src/lib/utilities/route-for-api.ts b/src/lib/utilities/route-for-api.ts index 68241a08d2..ec198ddbb3 100644 --- a/src/lib/utilities/route-for-api.ts +++ b/src/lib/utilities/route-for-api.ts @@ -145,6 +145,7 @@ export function pathForApi( 'schedule.patch': `/namespaces/${parameters?.namespace}/schedules/${parameters?.scheduleId}/patch`, 'schedule.edit': `/namespaces/${parameters?.namespace}/schedules/${parameters?.scheduleId}/update`, schedules: `/namespaces/${parameters?.namespace}/schedules`, + 'schedules.count': `/namespaces/${parameters?.namespace}/schedule-count`, settings: '/settings', 'task-queue': `/namespaces/${parameters?.namespace}/task-queues/${parameters?.queue}`, 'task-queue.compatibility': `/namespaces/${parameters?.namespace}/task-queues/${parameters?.queue}/worker-build-id-compatibility`, diff --git a/tests/integration/schedule-edit.spec.ts b/tests/integration/schedule-edit.spec.ts index 9a54d75bd4..11a287efe8 100644 --- a/tests/integration/schedule-edit.spec.ts +++ b/tests/integration/schedule-edit.spec.ts @@ -1,6 +1,10 @@ import { expect, test } from '@playwright/test'; -import { mockScheduleApi, mockSchedulesApis } from '~/test-utilities/mock-apis'; +import { + mockScheduleApi, + mockSchedulesApis, + SCHEDULES_COUNT_API, +} from '~/test-utilities/mock-apis'; const schedulesUrl = '/namespaces/default/schedules'; const scheduleEditUrl = '/namespaces/default/schedules/test-schedule/edit'; @@ -14,6 +18,8 @@ test.describe('Schedules List with schedules', () => { test('selects schedule and edits', async ({ page }) => { await page.goto(schedulesUrl); + await page.waitForResponse(SCHEDULES_COUNT_API); + const createButton = page.getByTestId('create-schedule'); await expect(createButton).toBeEnabled(); diff --git a/tests/integration/schedules-list.spec.ts b/tests/integration/schedules-list.spec.ts index 8e62658e0e..1b8b5f3a06 100644 --- a/tests/integration/schedules-list.spec.ts +++ b/tests/integration/schedules-list.spec.ts @@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test'; import { mockSchedulesApis, - WORKFLOWS_COUNT_API, + SCHEDULES_COUNT_API, } from '~/test-utilities/mock-apis'; const schedulesUrl = '/namespaces/default/schedules'; @@ -17,7 +17,7 @@ test.describe('Schedules List with no schedules', () => { }) => { await page.goto(schedulesUrl); - await page.waitForResponse(WORKFLOWS_COUNT_API); + await page.waitForResponse(SCHEDULES_COUNT_API); const namespace = page.locator('h1'); await expect(namespace).toHaveText('0 Schedules'); @@ -36,7 +36,7 @@ test.describe('Schedules List with schedules', () => { }) => { await page.goto(schedulesUrl); - await page.waitForResponse(WORKFLOWS_COUNT_API); + await page.waitForResponse(SCHEDULES_COUNT_API); const namespace = page.locator('h1'); await expect(namespace).toHaveText('15 Schedules'); diff --git a/tests/test-utilities/mock-apis.ts b/tests/test-utilities/mock-apis.ts index b7dfb33e22..8c8bd81804 100644 --- a/tests/test-utilities/mock-apis.ts +++ b/tests/test-utilities/mock-apis.ts @@ -13,6 +13,7 @@ import { import { mockNamespaceApi } from './mocks/namespace'; import { mockNamespacesApi, NAMESPACES_API } from './mocks/namespaces'; import { mockSchedulesApi } from './mocks/schedules'; +import { mockSchedulesCountApi } from './mocks/schedules-count'; import { mockSearchAttributesApi } from './mocks/search-attributes'; import { mockSettingsApi, SETTINGS_API } from './mocks/settings'; import { mockSystemInfoApi } from './mocks/system-info'; @@ -44,6 +45,10 @@ export { SEARCH_ATTRIBUTES_API, } from './mocks/search-attributes'; export { mockScheduleApi, SCHEDULE_API } from './mocks/schedules'; +export { + mockSchedulesCountApi, + SCHEDULES_COUNT_API, +} from './mocks/schedules-count'; export { mockWorkflowsApi, WORKFLOWS_API } from './mocks/workflows'; export { mockWorkflowApi, @@ -89,7 +94,7 @@ export const mockWorkflowsApis = (page: Page) => { export const mockSchedulesApis = ( page: Page, empty = false, - emptyWorkflowsCount = false, + emptySchedulesCount = false, customSearchAttributes?: Partial, ) => { return Promise.all([ @@ -97,7 +102,7 @@ export const mockSchedulesApis = ( mockNamespaceApis(page), mockSearchAttributesApi(page, customSearchAttributes), mockSchedulesApi(page, empty), - mockWorkflowsCountApi(page, emptyWorkflowsCount), + mockSchedulesCountApi(page, emptySchedulesCount), ]); }; diff --git a/tests/test-utilities/mocks/schedules-count.ts b/tests/test-utilities/mocks/schedules-count.ts new file mode 100644 index 0000000000..bf909ea3d7 --- /dev/null +++ b/tests/test-utilities/mocks/schedules-count.ts @@ -0,0 +1,10 @@ +import type { Page } from '@playwright/test'; + +export const SCHEDULES_COUNT_API = + /\/api\/v1\/namespaces\/[^/]+\/schedule-count(\?.*)?$/; + +export const mockSchedulesCountApi = (page: Page, empty = false) => { + return page.route(SCHEDULES_COUNT_API, (route) => { + return route.fulfill({ json: { count: empty ? '0' : '15' } }); + }); +};