diff --git a/src/lib/components/event/event-card.svelte b/src/lib/components/event/event-card.svelte index e33cc5954b..57108304d4 100644 --- a/src/lib/components/event/event-card.svelte +++ b/src/lib/components/event/event-card.svelte @@ -182,9 +182,27 @@ {format(key)}

{#if value?.payloads} - + {:else} - + {/if} {#if stackTrace} diff --git a/src/lib/components/payload/payload-code-block.svelte b/src/lib/components/payload/payload-code-block.svelte index 65b8a17ca4..cb5f0133ae 100644 --- a/src/lib/components/payload/payload-code-block.svelte +++ b/src/lib/components/payload/payload-code-block.svelte @@ -1,34 +1,142 @@ - {#snippet children(decodedValue)} + {#snippet children(results)}
- {#each decodedValue as data (data)} - + {#each results as result (result.decodedValue)} + {#if isExternallyStoredRawPayload(result.decodedValue)} + + {#snippet headerActions()} + + + + {/snippet} + + {#if downloadError} +
+ +

{downloadError}

+
+ {/if} +

+ Payload downloads require a codec server with a /download + endpoint. How to set up a codec server + + +

+ {:else} + + {/if} {/each}
{/snippet} diff --git a/src/lib/components/payload/payload-decoder.svelte b/src/lib/components/payload/payload-decoder.svelte index 7a4382249a..68af83c37a 100644 --- a/src/lib/components/payload/payload-decoder.svelte +++ b/src/lib/components/payload/payload-decoder.svelte @@ -1,52 +1,102 @@ + + -{#await decodePayloadValue(value)} +{#await decodeValue(value)} {@render loading?.()} -{:then decodedValue} - {@render children(decodedValue)} +{:then decodeResult} + {@render children(decodeResult)} {/await} diff --git a/src/lib/components/payload/payload-inline.svelte b/src/lib/components/payload/payload-inline.svelte index 19a220f6b0..7b769b6def 100644 --- a/src/lib/components/payload/payload-inline.svelte +++ b/src/lib/components/payload/payload-inline.svelte @@ -1,5 +1,6 @@ - {#snippet children(decodedValue)} + {#snippet children(result)} + {@const stringifiedData = stringifyWithBigInt(result[0].decodedValue.data)}
-
{(decodedValue[0] ?? '').slice(
-            0,
-            truncateAt,
-          )}
+
{stringifiedData.slice(0, truncateAt)}
{/snippet} diff --git a/src/lib/components/schedule/schedule-form/schedule-input-payload.svelte b/src/lib/components/schedule/schedule-form/schedule-input-payload.svelte index cbda4c872c..a44c4b1caa 100644 --- a/src/lib/components/schedule/schedule-form/schedule-input-payload.svelte +++ b/src/lib/components/schedule/schedule-form/schedule-input-payload.svelte @@ -1,7 +1,9 @@ - -{#snippet defaultTitleSnippet()} -

- {title} -

-{/snippet} - -
- {@render titleSnippet()} - {#if content} - - {:else} - - {/if} -
diff --git a/src/lib/components/workflow/input-and-results.svelte b/src/lib/components/workflow/input-and-results.svelte index 7391c4f345..59f74054d3 100644 --- a/src/lib/components/workflow/input-and-results.svelte +++ b/src/lib/components/workflow/input-and-results.svelte @@ -1,26 +1,62 @@
- - +
+

+ {translate('workflows.input')} +

+ {#if workflowEvents.input} + + {:else} + + {/if} +
+
+

+ {translate('workflows.results')} +

+ {#if workflowEvents.results} + + {:else} + + {/if} +
diff --git a/src/lib/holocene/button.svelte b/src/lib/holocene/button.svelte index dcbce6f925..efa7c8148c 100644 --- a/src/lib/holocene/button.svelte +++ b/src/lib/holocene/button.svelte @@ -194,14 +194,14 @@ )} {...$$restProps} > - {#if leadingIcon} - - + {#if (leadingIcon || loading) && !trailingIcon} + + {/if} - {#if trailingIcon || loading} + {#if (trailingIcon || loading) && !leadingIcon} diff --git a/src/lib/holocene/code-block.svelte b/src/lib/holocene/code-block.svelte index 1cce013ce3..a31b8a287a 100644 --- a/src/lib/holocene/code-block.svelte +++ b/src/lib/holocene/code-block.svelte @@ -295,7 +295,9 @@ > {#snippet actions()} - {#if copyable && !hasHeader} + {#if headerActions} + {@render headerActions()} + {:else if copyable && !hasHeader} { const settings = page.data.settings; @@ -31,7 +35,7 @@ export async function codeServerRequest({ if (!endpoint) { if (type === 'decode') return payloads; - throw new Error('No codec endpoint configured'); + throw NO_CODEC_SERVER_CONFIGURED_ERROR; } const passAccessToken = getCodecPassAccessToken(settings); @@ -71,10 +75,10 @@ export async function codeServerRequest({ body: stringifyWithBigInt(payloads), }; - const decoderResponse: Promise = fetch( - endpoint + `/${type}`, - requestOptions, - ) + const url = new URL(type, endpoint); + url.searchParams.set('preserveStorageRefs', 'true'); + + const decoderResponse: Promise = fetch(url, requestOptions) .then((response) => { if (response.ok === false) { throw { @@ -93,7 +97,9 @@ export async function codeServerRequest({ return response; }) .catch((err: unknown) => { - setLastDataEncoderFailure(err); + if (type !== 'download') { + setLastDataEncoderFailure(err); + } if (type === 'decode') { return payloads; } else { @@ -119,3 +125,12 @@ export async function encodePayloadsWithCodec({ }): Promise { return codeServerRequest({ type: 'encode', payloads }); } + +export async function downloadExternalPayloadWithCodec( + payload: Payload, +): Promise { + return codeServerRequest({ + type: 'download', + payloads: { payloads: [payload] }, + }); +} diff --git a/src/lib/utilities/decode-payload.ts b/src/lib/utilities/decode-payload.ts index a6f0547b00..610574bc92 100644 --- a/src/lib/utilities/decode-payload.ts +++ b/src/lib/utilities/decode-payload.ts @@ -33,6 +33,10 @@ export type ParsedPayload = { metadata: ParsedMetadata; }; +export type ParsedExternalPayload = ParsedPayload & { + externalPayloads: { sizeBytes: number }[]; +}; + /** * Decoding TL;DR * Decoding includes either 1 or 2 phases - "parse" and "decode" @@ -113,6 +117,7 @@ export function parseRawPayloadToJSON( return { metadata, data, + externalPayloads: payload.externalPayloads ?? [], }; } catch (_e) { console.warn('Could not parse payload: ', _e); @@ -237,8 +242,23 @@ const keyIs = (key: string, ...validKeys: string[]) => { export const isRawPayload = (payload: unknown): payload is Payload => { if (!isObject(payload)) return false; const keys = Object.keys(payload); + return keys.length >= 2 && keys.includes('metadata') && keys.includes('data'); +}; + +export const isParsedPayload = (payload: unknown): payload is ParsedPayload => { + if (!isObject(payload)) return false; + const keys = Object.keys(payload); + return keys.length >= 2 && keys.includes('metadata') && keys.includes('data'); +}; + +export const isExternallyStoredRawPayload = ( + payload: unknown, +): payload is ParsedExternalPayload => { return ( - keys.length === 2 && keys.includes('metadata') && keys.includes('data') + isParsedPayload(payload) && + has(payload.metadata, 'messageType') && + payload.metadata.messageType === + 'temporal.api.sdk.v1.ExternalStorageReference' ); }; @@ -275,17 +295,27 @@ export async function decodePayloadAndParseDataToJSON( return parseRawPayloadToJSON(decoded[0], returnDataOnly); } -export const decodePayloadsAndParseDataToJSON = async ( +export async function decodePayloadsAndParseDataToJSON( + payload: Payloads, +): Promise; +export async function decodePayloadsAndParseDataToJSON( + payload: Payloads, + returnDataOnly: false, +): Promise; +export async function decodePayloadsAndParseDataToJSON( payloads: Payloads | null | undefined, -): Promise => { + returnDataOnly: boolean = true, +): Promise { const decoded = await decodePayloadsWithRemoteCodec(payloads?.payloads); if (!decoded || !decoded[0]) { return [null]; } - return decoded.map((payload) => parseRawPayloadToJSON(payload)); -}; + return decoded.map((payload) => + parseRawPayloadToJSON(payload, returnDataOnly), + ); +} /** * Phase 2 internal implementation shared by {@link decodeEventAttributes} and diff --git a/temporal/codec-server.ts b/temporal/codec-server.ts index 9800f6fd4d..02537dcebc 100644 --- a/temporal/codec-server.ts +++ b/temporal/codec-server.ts @@ -94,6 +94,10 @@ export async function createCodecServer( } }); + app.post('/download', async (req, res) => { + res.status(404).end('Not found'); + }); + const start = () => new Promise((resolve, reject) => { server = app.listen(port, () => {