diff --git a/src/lib/components/schedule/schedule-basic-frequency.svelte b/src/lib/components/schedule/schedule-basic-frequency.svelte index 922d879f2..1f4322a52 100644 --- a/src/lib/components/schedule/schedule-basic-frequency.svelte +++ b/src/lib/components/schedule/schedule-basic-frequency.svelte @@ -10,12 +10,18 @@ {#key [calendar, interval]}
-
{stringifyWithBigInt(calendar || interval)}
+
{stringifyWithBigInt(
+          calendar || interval,
+          undefined,
+          2,
+        )}
{/key} diff --git a/src/lib/components/schedule/schedule-form-view.svelte b/src/lib/components/schedule/schedule-form-view.svelte index a0f7e8edc..58ecd50ae 100644 --- a/src/lib/components/schedule/schedule-form-view.svelte +++ b/src/lib/components/schedule/schedule-form-view.svelte @@ -22,6 +22,8 @@ } from '$lib/utilities/route-for'; import { writeActionsAreAllowed } from '$lib/utilities/write-actions-are-allowed'; + import ScheduleInputPayload from './schedule-input-payload.svelte'; + import type { Schedule } from '$types'; export let schedule: FullSchedule | null = null; @@ -53,6 +55,7 @@ let workflowType = schedule?.action?.startWorkflow?.workflowType?.name ?? ''; let workflowId = schedule?.action?.startWorkflow?.workflowId ?? ''; let taskQueue = schedule?.action?.startWorkflow?.taskQueue?.name ?? ''; + let input = ''; let daysOfWeek: string[] = []; let daysOfMonth: number[] = []; let months: string[] = []; @@ -69,6 +72,7 @@ workflowType, workflowId, taskQueue, + input, hour, minute, second, @@ -101,8 +105,25 @@ } }; + const isValidInput = (value: string) => { + if (!input) { + errors['input'] = false; + return true; + } + + try { + JSON.parse(value); + errors['input'] = false; + return true; + } catch (e) { + errors['input'] = true; + return false; + } + }; + $: isDisabled = (preset: SchedulePreset) => { if (!name || !workflowType || !workflowId || !taskQueue) return true; + if (!isValidInput(input)) return true; if (preset === 'interval') return !days && !hour && !minute && !second; if (preset === 'week') return !daysOfWeek.length; if (preset === 'month') return !daysOfMonth.length || !months.length; @@ -169,6 +190,11 @@ on:blur={onBlur} /> + -

{translate('schedules.schedule-spec')}

+

{translate('schedules.schedule-spec')}

diff --git a/src/lib/components/schedule/schedule-input-payload.svelte b/src/lib/components/schedule/schedule-input-payload.svelte new file mode 100644 index 000000000..8d3dcdfc8 --- /dev/null +++ b/src/lib/components/schedule/schedule-input-payload.svelte @@ -0,0 +1,38 @@ + + +
+ + + {#key decodedValue} + + {/key} + + + {translate('workflows.signal-payload-input-label-hint')} + +
diff --git a/src/lib/components/schedule/schedule-input.svelte b/src/lib/components/schedule/schedule-input.svelte new file mode 100644 index 000000000..2939cd633 --- /dev/null +++ b/src/lib/components/schedule/schedule-input.svelte @@ -0,0 +1,17 @@ + + + + + diff --git a/src/lib/components/schedule/schedule-notes.svelte b/src/lib/components/schedule/schedule-notes.svelte index d3a7e617c..730a62492 100644 --- a/src/lib/components/schedule/schedule-notes.svelte +++ b/src/lib/components/schedule/schedule-notes.svelte @@ -5,6 +5,6 @@
-

{translate('common.notes')}

+

{translate('common.notes')}

{notes}

diff --git a/src/lib/components/schedule/schedule-recent-runs.svelte b/src/lib/components/schedule/schedule-recent-runs.svelte index 866293bd7..f57fc0885 100644 --- a/src/lib/components/schedule/schedule-recent-runs.svelte +++ b/src/lib/components/schedule/schedule-recent-runs.svelte @@ -35,7 +35,7 @@
-

{translate('schedules.recent-runs')}

+

{translate('schedules.recent-runs')}

-

{translate('schedules.upcoming-runs')}

+

{translate('schedules.upcoming-runs')}

{#each futureRuns.slice(0, 5) as run}

diff --git a/src/lib/components/schedule/schedules-calendar-view.svelte b/src/lib/components/schedule/schedules-calendar-view.svelte index 0ce95bc10..de1d5f5c6 100644 --- a/src/lib/components/schedule/schedules-calendar-view.svelte +++ b/src/lib/components/schedule/schedules-calendar-view.svelte @@ -45,7 +45,7 @@ -

{translate('schedules.schedule-spec')}

+

{translate('schedules.schedule-spec')}

{#if schedule} @@ -95,10 +103,13 @@
diff --git a/src/lib/components/schedule/schedules-table-row.svelte b/src/lib/components/schedule/schedules-table-row.svelte index cb659dbaa..41e635b9a 100644 --- a/src/lib/components/schedule/schedules-table-row.svelte +++ b/src/lib/components/schedule/schedules-table-row.svelte @@ -82,15 +82,7 @@
{/each} - - - - - - - - - + diff --git a/src/lib/components/schedule/schedules-table.svelte b/src/lib/components/schedule/schedules-table.svelte index d4d4be53d..4b68a70c4 100644 --- a/src/lib/components/schedule/schedules-table.svelte +++ b/src/lib/components/schedule/schedules-table.svelte @@ -11,11 +11,10 @@ {translate('common.status')} {translate('schedules.name')} - {translate('common.workflow-type')} + {translate('common.workflow-type')} {translate('schedules.recent-runs')} {translate('schedules.upcoming-runs')} + {translate('schedules.schedule-spec')} diff --git a/src/lib/components/schedule/schedules-time-view.svelte b/src/lib/components/schedule/schedules-time-view.svelte index 13c7d2f74..d3b0700bb 100644 --- a/src/lib/components/schedule/schedules-time-view.svelte +++ b/src/lib/components/schedule/schedules-time-view.svelte @@ -41,23 +41,27 @@
diff --git a/src/lib/components/workflow/client-actions/signal-confirmation-modal.svelte b/src/lib/components/workflow/client-actions/signal-confirmation-modal.svelte index 03aab8ca2..24ad3471e 100644 --- a/src/lib/components/workflow/client-actions/signal-confirmation-modal.svelte +++ b/src/lib/components/workflow/client-actions/signal-confirmation-modal.svelte @@ -6,7 +6,6 @@ import Modal from '$lib/holocene/modal.svelte'; import { translate } from '$lib/i18n/translate'; import { signalWorkflow } from '$lib/services/workflow-service'; - import { codecEndpoint } from '$lib/stores/data-encoder-config'; import { toaster } from '$lib/stores/toaster'; import type { WorkflowExecution } from '$lib/types/workflows'; import { isNetworkError } from '$lib/utilities/is-network-error'; @@ -22,12 +21,9 @@ let input = ''; let codeBlock: CodeBlock; - const getDefaultSignalInput = () => - $codecEndpoint ? '{"metadata": {"encoding": ""}, "data": ""}' : ''; - const hideSignalModal = () => { open = false; - input = getDefaultSignalInput(); + input = ''; name = ''; codeBlock?.resetView(input); }; diff --git a/src/lib/components/workflow/input-and-results.svelte b/src/lib/components/workflow/input-and-results.svelte index ae10c94e3..f45652bd7 100644 --- a/src/lib/components/workflow/input-and-results.svelte +++ b/src/lib/components/workflow/input-and-results.svelte @@ -13,6 +13,7 @@ export let content: string; export let title: string; + export let running = false; $: parsedContent = parseContent(content); $: payloads = getPayloads(parsedContent); @@ -46,7 +47,7 @@ }; -
+

{title} {#if showParsedContentCount} @@ -79,7 +80,7 @@

{:else}
diff --git a/src/lib/pages/schedule-edit.svelte b/src/lib/pages/schedule-edit.svelte index b3a0e1bce..544dda9a7 100644 --- a/src/lib/pages/schedule-edit.svelte +++ b/src/lib/pages/schedule-edit.svelte @@ -35,6 +35,7 @@ workflowType, workflowId, taskQueue, + input, hour, minute, second, @@ -51,6 +52,7 @@ workflowType, workflowId, taskQueue, + input, }; const spec: Partial = { hour, diff --git a/src/lib/pages/schedule-view.svelte b/src/lib/pages/schedule-view.svelte index 7e24401b2..53c73d92f 100644 --- a/src/lib/pages/schedule-view.svelte +++ b/src/lib/pages/schedule-view.svelte @@ -7,6 +7,7 @@ import ScheduleAdvancedSettings from '$lib/components/schedule/schedule-advanced-settings.svelte'; import ScheduleError from '$lib/components/schedule/schedule-error.svelte'; import ScheduleFrequencyPanel from '$lib/components/schedule/schedule-frequency-panel.svelte'; + import ScheduleInput from '$lib/components/schedule/schedule-input.svelte'; import ScheduleRecentRuns from '$lib/components/schedule/schedule-recent-runs.svelte'; import ScheduleUpcomingRuns from '$lib/components/schedule/schedule-upcoming-runs.svelte'; import WorkflowCounts from '$lib/components/workflow/workflow-counts.svelte'; @@ -299,7 +300,10 @@ notes={schedule?.schedule?.state?.notes} />
-
+
+ = { hour, diff --git a/src/lib/services/data-encoder.ts b/src/lib/services/data-encoder.ts index f41639fda..3287c6b82 100644 --- a/src/lib/services/data-encoder.ts +++ b/src/lib/services/data-encoder.ts @@ -1,31 +1,39 @@ +import { get } from 'svelte/store'; + +import { page } from '$app/stores'; + import { translate } from '$lib/i18n/translate'; +import { authUser } from '$lib/stores/auth-user'; import { setLastDataEncoderFailure, setLastDataEncoderSuccess, } from '$lib/stores/data-encoder-config'; import type { NetworkError, Settings } from '$lib/types/global'; +import { + getCodecEndpoint, + getCodecIncludeCredentials, + getCodecPassAccessToken, +} from '$lib/utilities/get-codec'; import { has } from '$lib/utilities/has'; import { validateHttps } from '$lib/utilities/is-http'; import { stringifyWithBigInt } from '$lib/utilities/parse-with-big-int'; export type PotentialPayloads = { payloads: unknown[] }; -export async function convertPayloadsWithCodec({ +export async function decodePayloadsWithCodec({ payloads, - namespace, - settings, - accessToken, - encode = false, + namespace = get(page).params.namespace, + settings = get(page).data.settings, + accessToken = get(authUser).accessToken, }: { payloads: PotentialPayloads; - namespace: string; - settings: Settings; - accessToken: string; - encode?: boolean; + namespace?: string; + settings?: Settings; + accessToken?: string; }): Promise { - const endpoint = settings?.codec?.endpoint; - const passAccessToken = settings?.codec?.passAccessToken; - const includeCredentials = settings?.codec?.includeCredentials; + const endpoint = getCodecEndpoint(settings); + const passAccessToken = getCodecPassAccessToken(settings); + const includeCredentials = getCodecIncludeCredentials(settings); const headers = { 'Content-Type': 'application/json', @@ -54,8 +62,8 @@ export async function convertPayloadsWithCodec({ body: stringifyWithBigInt(payloads), }; - const encoderResponse: Promise = fetch( - endpoint + (encode ? '/encode' : '/decode'), + const decoderResponse: Promise = fetch( + endpoint + '/decode', requestOptions, ) .then((response) => { @@ -64,9 +72,7 @@ export async function convertPayloadsWithCodec({ statusCode: response.status, statusText: response.statusText, response, - message: encode - ? translate('common.encode-failed') - : translate('common.decode-failed'), + message: translate('common.decode-failed'), } as NetworkError; } else { return response.json(); @@ -83,5 +89,76 @@ export async function convertPayloadsWithCodec({ return payloads; }); + return decoderResponse; +} + +export async function encodePayloadsWithCodec({ + payloads, + namespace = get(page).params.namespace, + settings = get(page).data.settings, + accessToken = get(authUser).accessToken, +}: { + payloads: PotentialPayloads; + namespace?: string; + settings?: Settings; + accessToken?: string; +}): Promise { + const endpoint = getCodecEndpoint(settings); + const passAccessToken = getCodecPassAccessToken(settings); + const includeCredentials = getCodecIncludeCredentials(settings); + + const headers = { + 'Content-Type': 'application/json', + 'X-Namespace': namespace, + }; + + if (passAccessToken) { + if (validateHttps(endpoint)) { + headers['Authorization'] = `Bearer ${accessToken}`; + } else { + setLastDataEncoderFailure(); + return payloads; + } + } + + const requestOptions = includeCredentials + ? { + headers, + credentials: 'include' as RequestCredentials, + method: 'POST', + body: stringifyWithBigInt(payloads), + } + : { + headers, + method: 'POST', + body: stringifyWithBigInt(payloads), + }; + + const encoderResponse: Promise = fetch( + endpoint + '/encode', + requestOptions, + ) + .then((response) => { + if (has(response, 'ok') && !response.ok) { + throw { + statusCode: response.status, + statusText: response.statusText, + response, + message: translate('common.encode-failed'), + } as NetworkError; + } else { + return response.json(); + } + }) + .then((response) => { + setLastDataEncoderSuccess(); + + return response; + }) + .catch((err: unknown) => { + setLastDataEncoderFailure(err); + throw err; + }); + return encoderResponse; } diff --git a/src/lib/services/workflow-service.ts b/src/lib/services/workflow-service.ts index 4a5293ed7..8fd0ed6c3 100644 --- a/src/lib/services/workflow-service.ts +++ b/src/lib/services/workflow-service.ts @@ -4,18 +4,12 @@ import { v4 } from 'uuid'; import { page } from '$app/stores'; -import { translate } from '$lib/i18n/translate'; import { Action, type ResetReapplyType } from '$lib/models/workflow-actions'; import { toWorkflowExecution, toWorkflowExecutions, } from '$lib/models/workflow-execution'; -import { convertPayloadsWithCodec } from '$lib/services/data-encoder'; import { authUser } from '$lib/stores/auth-user'; -import { - lastDataEncoderError, - lastDataEncoderStatus, -} from '$lib/stores/data-encoder-config'; import type { ResetWorkflowRequest } from '$lib/types'; import type { ValidWorkflowEndpoints, @@ -31,7 +25,7 @@ import type { ListWorkflowExecutionsResponse, WorkflowExecution, } from '$lib/types/workflows'; -import { btoa } from '$lib/utilities/btoa'; +import { encodePayloads } from '$lib/utilities/encode-payload'; import { handleUnauthorizedOrForbiddenError, isForbidden, @@ -293,39 +287,9 @@ export async function signalWorkflow({ workflowId, signalName: name, }); - - let payloads = null; + const payloads = await encodePayloads(input); const settings = get(page).data.settings; - const accessToken = get(authUser).accessToken; - - if (input) { - if (settings?.codec?.endpoint) { - const awaitData = await convertPayloadsWithCodec({ - payloads: { payloads: [JSON.parse(input)] }, - namespace, - settings, - accessToken, - encode: true, - }); - if (get(lastDataEncoderStatus) === 'error') { - throw new Error( - get(lastDataEncoderError) || translate('common.encode-failed'), - ); - } - payloads = awaitData?.payloads ?? null; - } else { - payloads = [ - { - metadata: { - encoding: btoa('json/plain'), - }, - data: btoa(input), - }, - ]; - } - } - - const version = get(page).data?.settings?.version ?? ''; + const version = settings?.version ?? ''; const newVersion = isVersionNewer(version, '2.22'); const body = newVersion ? { diff --git a/src/lib/stores/schedules.ts b/src/lib/stores/schedules.ts index 76ae5761f..c2a07cb2f 100644 --- a/src/lib/stores/schedules.ts +++ b/src/lib/stores/schedules.ts @@ -2,6 +2,7 @@ import { writable } from 'svelte/store'; import { goto } from '$app/navigation'; +import { translate } from '$lib/i18n/translate'; import { createSchedule, editSchedule } from '$lib/services/schedule-service'; import type { Schedule } from '$lib/types'; import type { @@ -11,6 +12,7 @@ import type { SchedulePresetsParameters, ScheduleSpecParameters, } from '$lib/types/schedule'; +import { encodePayloads } from '$lib/utilities/encode-payload'; import { routeForSchedule, routeForSchedules } from '$lib/utilities/route-for'; import { convertDaysAndMonths, @@ -78,7 +80,17 @@ export const submitCreateSchedule = async ({ spec, presets, }: ScheduleParameterArgs): Promise => { - const { namespace, name, workflowId, workflowType, taskQueue } = action; + const { namespace, name, workflowId, workflowType, taskQueue, input } = + action; + + let payloads; + try { + payloads = await encodePayloads(input); + } catch (e) { + error.set(`${translate('data-encoder.encode-error')}: ${e?.message}`); + return; + } + const body: DescribeFullSchedule = { schedule_id: name.trim(), schedule: { @@ -92,6 +104,7 @@ export const submitCreateSchedule = async ({ workflowId: workflowId, workflowType: { name: workflowType }, taskQueue: { name: taskQueue }, + input: { payloads }, }, }, }, @@ -124,7 +137,15 @@ export const submitEditSchedule = async ( schedule: Schedule, scheduleId: string, ): Promise => { - const { namespace, name, workflowId, workflowType, taskQueue } = action; + const { namespace, name, workflowId, workflowType, taskQueue, input } = + action; + let payloads; + try { + payloads = await encodePayloads(input); + } catch (e) { + error.set(`${translate('data-encoder.encode-error')}: ${e?.message}`); + return; + } const { preset } = presets; const body: DescribeFullSchedule = { @@ -137,6 +158,7 @@ export const submitEditSchedule = async ( workflowId, workflowType: { name: workflowType }, taskQueue: { name: taskQueue }, + input: { payloads }, }, }, }, diff --git a/src/lib/theme/plugin.ts b/src/lib/theme/plugin.ts index 6c072586a..0bd43bcce 100644 --- a/src/lib/theme/plugin.ts +++ b/src/lib/theme/plugin.ts @@ -16,7 +16,7 @@ export const variables = { '--color-text-primary': rgb(colors.primary), '--color-text-secondary': rgb(colors.secondary), '--color-text-inverse': rgb(colors.offWhite), - '--color-text-subtle': rgb(getColor('slate', 900)), + '--color-text-subtle': rgb(getColor('slate', 300)), '--color-text-disabled': rgb(getColor('slate', 500)), '--color-text-error': rgb(getColor('red', 700)), '--color-text-information': rgb(getColor('blue', 700)), diff --git a/src/lib/types/schedule.ts b/src/lib/types/schedule.ts index 7ff47841f..50b70d581 100644 --- a/src/lib/types/schedule.ts +++ b/src/lib/types/schedule.ts @@ -36,6 +36,7 @@ export type ScheduleActionParameters = { workflowType: string; workflowId: string; taskQueue: string; + input: string; }; export type ScheduleSpecParameters = { diff --git a/src/lib/utilities/decode-payload.ts b/src/lib/utilities/decode-payload.ts index 768e0a082..0b88427a3 100644 --- a/src/lib/utilities/decode-payload.ts +++ b/src/lib/utilities/decode-payload.ts @@ -2,7 +2,7 @@ import { get } from 'svelte/store'; import { page } from '$app/stores'; -import { convertPayloadsWithCodec } from '$lib/services/data-encoder'; +import { decodePayloadsWithCodec } from '$lib/services/data-encoder'; import { authUser } from '$lib/stores/auth-user'; import type { codecEndpoint, @@ -19,11 +19,7 @@ import type { import type { Optional, Replace, Settings } from '$lib/types/global'; import { atob } from './atob'; -import { - getCodecEndpoint, - getCodecIncludeCredentials, - getCodecPassAccessToken, -} from './get-codec'; +import { getCodecEndpoint } from './get-codec'; import { has } from './has'; import { isObject } from './is'; import { parseWithBigInt } from './parse-with-big-int'; @@ -145,19 +141,17 @@ export const decodePayloadAttributes = < return eventAttribute; }; -const decodePayloadWithCodec = - (namespace: string, settings: Settings, accessToken: string) => +const decodePayloads = + (settings: Settings) => async ( payloads: unknown[], returnDataOnly: boolean = true, ): Promise => { - if (settings?.codec?.endpoint) { + if (getCodecEndpoint(settings)) { // Convert Payload data - const awaitData = await convertPayloadsWithCodec({ + const awaitData = await decodePayloadsWithCodec({ payloads: { payloads }, - namespace, settings, - accessToken, }); return (awaitData?.payloads ?? []).map((p) => decodePayload(p, returnDataOnly), @@ -180,24 +174,8 @@ export const decodeAllPotentialPayloadsWithCodec = async ( settings: Settings = get(page).data.settings, accessToken: string = get(authUser).accessToken, ): Promise => { - const endpoint = getCodecEndpoint(settings); - const passAccessToken = getCodecPassAccessToken(settings); - const includeCredentials = getCodecIncludeCredentials(settings); - const settingsWithLocalConfig = { - ...settings, - codec: { - ...settings?.codec, - endpoint, - passAccessToken, - includeCredentials, - }, - }; + const decode = decodePayloads(settings); - const decode = decodePayloadWithCodec( - namespace, - settingsWithLocalConfig, - accessToken, - ); if (anyAttributes) { for (const key of Object.keys(anyAttributes)) { if (keyIs(key, 'payloads', 'encodedAttributes') && anyAttributes[key]) { @@ -232,7 +210,7 @@ export const cloneAllPotentialPayloadsWithCodec = async ( ): Promise => { if (!anyAttributes) return anyAttributes; - const decode = decodePayloadWithCodec(namespace, settings, accessToken); + const decode = decodePayloads(settings); const clone = { ...anyAttributes }; if (anyAttributes) { for (const key of Object.keys(clone)) { diff --git a/src/lib/utilities/encode-payload.test.ts b/src/lib/utilities/encode-payload.test.ts new file mode 100644 index 000000000..105283297 --- /dev/null +++ b/src/lib/utilities/encode-payload.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from 'vitest'; + +import { getSinglePayload } from './encode-payload'; + +describe('getSinglePayload', () => { + it('should return single payload from single payload', () => { + const payload = [ + { + metadata: { encoding: 'json/plain' }, + data: 'eyJ0aXRsZSI6ImhlbGxvIn0=', + }, + ]; + const singlePayload = getSinglePayload(JSON.stringify(payload)); + expect(singlePayload).toEqual(JSON.stringify(payload[0])); + }); + it('should return single payload from multiple payloads', () => { + const payload = [ + { + metadata: { encoding: 'json/plain' }, + data: 'eyJ0aXRsZSI6ImhlbGxvIn0=', + }, + { metadata: { encoding: 'json/plain' }, data: 'cccccasdf' }, + ]; + const singlePayload = getSinglePayload(JSON.stringify(payload)); + expect(singlePayload).toEqual(JSON.stringify(payload[0])); + }); + it('should return empty string from no payload', () => { + const singlePayload = getSinglePayload(''); + expect(singlePayload).toEqual(''); + }); + it('should return empty string from bad payload', () => { + const payload = { + metadata: { encoding: 'json/plain' }, + data: 'eyJ0aXRsZSI6ImhlbGxvIn0=', + }; + const singlePayload = getSinglePayload(JSON.stringify(payload)); + expect(singlePayload).toEqual(''); + }); +}); diff --git a/src/lib/utilities/encode-payload.ts b/src/lib/utilities/encode-payload.ts new file mode 100644 index 000000000..b5364150e --- /dev/null +++ b/src/lib/utilities/encode-payload.ts @@ -0,0 +1,48 @@ +import { get } from 'svelte/store'; + +import { encodePayloadsWithCodec } from '$lib/services/data-encoder'; +import { dataEncoder } from '$lib/stores/data-encoder'; +import { btoa } from '$lib/utilities/btoa'; +import { + parseWithBigInt, + stringifyWithBigInt, +} from '$lib/utilities/parse-with-big-int'; + +export const getSinglePayload = (decodedValue: string): string => { + if (decodedValue) { + const parsedValue = parseWithBigInt(decodedValue); + const firstPayload = parsedValue?.[0]; + if (firstPayload) { + return stringifyWithBigInt(firstPayload); + } + } + return ''; +}; + +const setBase64Payload = (payload: unknown) => { + return { + metadata: { + encoding: btoa('json/plain'), + }, + data: btoa(JSON.stringify(payload)), + }; +}; + +export const encodePayloads = async (input: string) => { + let payloads = null; + + if (input) { + const parsedInput = JSON.parse(input); + payloads = [setBase64Payload(parsedInput)]; + + const endpoint = get(dataEncoder).endpoint; + if (endpoint) { + const awaitData = await encodePayloadsWithCodec({ + payloads: { payloads }, + }); + payloads = awaitData?.payloads ?? null; + } + } + + return payloads; +}; diff --git a/temporal/codec-server.ts b/temporal/codec-server.ts index 896bea791..f443b51d7 100644 --- a/temporal/codec-server.ts +++ b/temporal/codec-server.ts @@ -43,6 +43,16 @@ export async function createCodecServer( app.use(cors({ allowedHeaders: ['x-namespace', 'content-type'] })); app.use(express.json()); + app.post('/encode', async (req, res) => { + try { + const { payloads: raw } = req.body as Body; + res.json({ payloads: raw.map(() => MOCK_DECODED_PAYLOAD) }).end(); + } catch (err) { + console.error('Error in /encode', err); + res.status(500).end('Internal server error'); + } + }); + app.post('/decode', async (req, res) => { try { const { payloads: raw } = req.body as Body; diff --git a/tests/e2e/schedules.spec.ts b/tests/e2e/schedules.spec.ts new file mode 100644 index 000000000..202ac62ea --- /dev/null +++ b/tests/e2e/schedules.spec.ts @@ -0,0 +1,35 @@ +import { expect, test } from '@playwright/test'; + +test.beforeEach(async ({ page, baseURL }) => { + await page.goto(baseURL); +}); + +test.describe('Schedules Page', () => { + test('should render empty list of schedules and navigate to Create Schedule page with form', async ({ + page, + }) => { + test.slow(); + const scheduleButton = page.getByTestId('schedules-button'); + await scheduleButton.click(); + await expect(page).toHaveURL(/schedules/); + const createScheduleButton = page.getByTestId('create-schedule'); + await expect(createScheduleButton).toBeVisible(); + await createScheduleButton.click(); + await expect(page).toHaveURL(/create/); + + await page.getByLabel('Name*').type('e2e-schedule-1'); + await page.getByLabel('Workflow Type*').type('test-type-e2e'); + await page.getByLabel('Workflow Id*').type('e2e-1234'); + await page.getByLabel('Task Queue*').type('default'); + await page.locator('#schedule-input').getByRole('textbox').type('abc'); + await page.getByRole('textbox', { name: 'hrs.' }).type('1'); + const createSchedule = page.getByRole('button', { + name: 'Create Schedule', + }); + await expect(createSchedule).toBeDisabled(); + + await page.locator('#schedule-input').getByRole('textbox').clear(); + await page.locator('#schedule-input').getByRole('textbox').type('123'); + await expect(createSchedule).toBeEnabled(); + }); +});