diff --git a/package.json b/package.json index 50280eb6c0..d66dd98bdf 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "@storybook/addon-essentials": "^8.6.11", "@storybook/addon-interactions": "^8.6.11", "@storybook/addon-links": "^8.6.11", - "@storybook/addon-svelte-csf": "^5.0.0-next.23", + "@storybook/addon-svelte-csf": "^5.0.10", "@storybook/addon-themes": "^8.6.11", "@storybook/blocks": "^8.6.11", "@storybook/icons": "^1.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f99281f51d..4387e58d30 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -170,7 +170,7 @@ importers: specifier: ^8.6.11 version: 8.6.15(react@18.3.1)(storybook@8.6.15(prettier@3.5.3)) '@storybook/addon-svelte-csf': - specifier: ^5.0.0-next.23 + specifier: ^5.0.10 version: 5.0.10(@storybook/svelte@8.6.15(storybook@8.6.15(prettier@3.5.3))(svelte@5.46.4))(@sveltejs/vite-plugin-svelte@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)))(storybook@8.6.15(prettier@3.5.3))(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)) '@storybook/addon-themes': specifier: ^8.6.11 diff --git a/server/go.mod b/server/go.mod index e5bf2ed824..e74848f013 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.57.0 + go.temporal.io/api v1.62.0 golang.org/x/net v0.47.0 golang.org/x/oauth2 v0.30.0 google.golang.org/grpc v1.66.1 diff --git a/server/go.sum b/server/go.sum index 2aaac2dde0..087cfff401 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.57.0 h1:vJGbU6RqMqCAXP03Jq4KEL61sCxAdJx4Yj7PxtbsrF0= -go.temporal.io/api v1.57.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/api v1.62.0 h1:rh7LqqV+pxaLNwPLsFRZgYoDJ/NvCNDv0EnWe6oS7A4= +go.temporal.io/api v1.62.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= diff --git a/src/lib/components/detail-list/detail-list-label.svelte b/src/lib/components/detail-list/detail-list-label.svelte index 7e5f937d7a..a0c40d9a8b 100644 --- a/src/lib/components/detail-list/detail-list-label.svelte +++ b/src/lib/components/detail-list/detail-list-label.svelte @@ -1,17 +1,19 @@ -
+
{#if href} {@render children()} {:else} diff --git a/src/lib/components/detail-list/detail-list-text-value.svelte b/src/lib/components/detail-list/detail-list-text-value.svelte index c243efd8d0..00088b1b8f 100644 --- a/src/lib/components/detail-list/detail-list-text-value.svelte +++ b/src/lib/components/detail-list/detail-list-text-value.svelte @@ -1,4 +1,5 @@ {#snippet content()} -
- {#if iconName} - - {/if} - {text} -
+ {#if isBadge} + + {text} + + {:else} +
+ {#if iconName} + + {/if} + {text} +
+ {/if} {/snippet} diff --git a/src/lib/components/detail-list/detail-list-timestamp-value.svelte b/src/lib/components/detail-list/detail-list-timestamp-value.svelte new file mode 100644 index 0000000000..98d7d53235 --- /dev/null +++ b/src/lib/components/detail-list/detail-list-timestamp-value.svelte @@ -0,0 +1,44 @@ + + +{#snippet content()} +
+ {$relativeTime ? relativeTimestamp : formattedTimestamp} +
+{/snippet} + + + + {@render content()} + {@render children?.()} + + diff --git a/src/lib/components/detail-list/index.ts b/src/lib/components/detail-list/index.ts index 2593d6aa4c..305ee02182 100644 --- a/src/lib/components/detail-list/index.ts +++ b/src/lib/components/detail-list/index.ts @@ -4,3 +4,4 @@ export { default as DetailListValue } from './detail-list-value.svelte'; export { default as DetailListTextValue } from './detail-list-text-value.svelte'; export { default as DetailListLinkValue } from './detail-list-link-value.svelte'; export { default as DetailListColumn } from './detail-list-column.svelte'; +export { default as DetailListTimestampValue } from './detail-list-timestamp-value.svelte'; diff --git a/src/lib/components/payload-input-with-encoding.svelte b/src/lib/components/payload-input-with-encoding.svelte index eb01dff1c7..26eddb5599 100644 --- a/src/lib/components/payload-input-with-encoding.svelte +++ b/src/lib/components/payload-input-with-encoding.svelte @@ -1,19 +1,12 @@ - - + +
+ + + + +
diff --git a/src/lib/components/schedule/schedule-form-view.svelte b/src/lib/components/schedule/schedule-form-view.svelte index e8b981f1aa..88ab6f1f08 100644 --- a/src/lib/components/schedule/schedule-form-view.svelte +++ b/src/lib/components/schedule/schedule-form-view.svelte @@ -14,6 +14,7 @@ import Link from '$lib/holocene/link.svelte'; import Loading from '$lib/holocene/loading.svelte'; import { translate } from '$lib/i18n/translate'; + import type { PayloadInputEncoding } from '$lib/models/payload-encoding'; import { error, loading } from '$lib/stores/schedules'; import { customSearchAttributes, @@ -31,8 +32,6 @@ } from '$lib/utilities/route-for'; import { writeActionsAreAllowed } from '$lib/utilities/write-actions-are-allowed'; - import type { PayloadInputEncoding } from '../payload-input-with-encoding.svelte'; - import ScheduleInputPayload from './schedule-input-payload.svelte'; import SchedulesSearchAttributesInputs from './schedules-search-attributes-inputs.svelte'; diff --git a/src/lib/components/schedule/schedule-input-payload.svelte b/src/lib/components/schedule/schedule-input-payload.svelte index 3189d48636..81d994c335 100644 --- a/src/lib/components/schedule/schedule-input-payload.svelte +++ b/src/lib/components/schedule/schedule-input-payload.svelte @@ -3,15 +3,16 @@ import Button from '$lib/holocene/button.svelte'; import { translate } from '$lib/i18n/translate'; + import { + isPayloadInputEncodingType, + type PayloadInputEncoding, + } from '$lib/models/payload-encoding'; import type { Payloads } from '$lib/types'; import { atob } from '$lib/utilities/atob'; import { getSinglePayload } from '$lib/utilities/encode-payload'; import PayloadDecoder from '../event/payload-decoder.svelte'; - import PayloadInputWithEncoding, { - isPayloadInputEncodingType, - type PayloadInputEncoding, - } from '../payload-input-with-encoding.svelte'; + import PayloadInputWithEncoding from '../payload-input-with-encoding.svelte'; export let input: string; export let editInput: boolean; diff --git a/src/lib/components/standalone-activities/activities-summary-configurable-table.svelte b/src/lib/components/standalone-activities/activities-summary-configurable-table.svelte new file mode 100644 index 0000000000..bc928978d7 --- /dev/null +++ b/src/lib/components/standalone-activities/activities-summary-configurable-table.svelte @@ -0,0 +1,77 @@ + + +{#key [namespace, query, $activityRefresh]} + + + {translate('standalone-activities.standalone-activities')} + + + + {#each columns as column} + + {/each} + + {#each visibleItems as activity} + + {#each columns as column} + + {/each} + + {/each} + + + + + + + + + +{/key} diff --git a/src/lib/components/standalone-activities/activities-summary-configurable-table/filterable-table-cell.svelte b/src/lib/components/standalone-activities/activities-summary-configurable-table/filterable-table-cell.svelte new file mode 100644 index 0000000000..2222273bf8 --- /dev/null +++ b/src/lib/components/standalone-activities/activities-summary-configurable-table/filterable-table-cell.svelte @@ -0,0 +1,68 @@ + + +{#if href} + {value} +{:else} + {value} +{/if} + filter.attribute === attribute && filter.value === value, + )} +/> diff --git a/src/lib/components/standalone-activities/activities-summary-configurable-table/table-body-cell.svelte b/src/lib/components/standalone-activities/activities-summary-configurable-table/table-body-cell.svelte new file mode 100644 index 0000000000..6e7c1e32a6 --- /dev/null +++ b/src/lib/components/standalone-activities/activities-summary-configurable-table/table-body-cell.svelte @@ -0,0 +1,101 @@ + + +{#if filterableLabels.includes(label)} + + {#if label === 'Activity ID'} + + {:else if label === 'Activity Type'} + + {:else if label === 'Task Queue'} + + {/if} + +{:else} + + {#if label === 'Status'} + + {:else if label === 'Run ID'} + {activity.runId ?? ''} + {:else if label === 'Start Time'} + + {:else if label === 'Execution Time'} + + {:else if label === 'Close Time'} + + {:else if label === 'Execution Duration'} + {#if activity.executionDuration} + {formatDistance({ + start: activity.lastStartedTime || activity.scheduleTime, + end: activity.closeTime, + includeMillisecondsForUnderSecond: true, + })} + {/if} + {:else if label === 'State Transitions'} + {activity.stateTransitionCount ?? ''} + {/if} + +{/if} diff --git a/src/lib/components/standalone-activities/activities-summary-configurable-table/table-empty-state.svelte b/src/lib/components/standalone-activities/activities-summary-configurable-table/table-empty-state.svelte new file mode 100644 index 0000000000..c2bd5f4b79 --- /dev/null +++ b/src/lib/components/standalone-activities/activities-summary-configurable-table/table-empty-state.svelte @@ -0,0 +1,69 @@ + + +{#if query} +
+
+

+ {#if $activityError} + {translate('standalone-activities.activity-query-error-state')} + {:else} + {translate('standalone-activities.empty-state-title')} + {/if} +

+

+ {#if $activityError} + {$activityError} + {:else} + {translate('standalone-activities.empty-state-description')} + {/if} +

+ +
+
+{:else} +
+
+

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

+ {#if $activityError} + + {$activityError} + + {:else} +

+ {translate('standalone-activities.empty-state-description')} +

+ {/if} +
+
+
+ +
+
+
+
+{/if} diff --git a/src/lib/components/standalone-activities/activities-summary-configurable-table/table-header-cell.svelte b/src/lib/components/standalone-activities/activities-summary-configurable-table/table-header-cell.svelte new file mode 100644 index 0000000000..310821952e --- /dev/null +++ b/src/lib/components/standalone-activities/activities-summary-configurable-table/table-header-cell.svelte @@ -0,0 +1,15 @@ + + + + {label} + diff --git a/src/lib/components/standalone-activities/activities-summary-configurable-table/table-header-row.svelte b/src/lib/components/standalone-activities/activities-summary-configurable-table/table-header-row.svelte new file mode 100644 index 0000000000..315563430c --- /dev/null +++ b/src/lib/components/standalone-activities/activities-summary-configurable-table/table-header-row.svelte @@ -0,0 +1,12 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/standalone-activities/activities-summary-configurable-table/table-row.svelte b/src/lib/components/standalone-activities/activities-summary-configurable-table/table-row.svelte new file mode 100644 index 0000000000..801352d1af --- /dev/null +++ b/src/lib/components/standalone-activities/activities-summary-configurable-table/table-row.svelte @@ -0,0 +1,40 @@ + + + + {#if !empty && activity} + + + + {:else} + + {/if} + {@render children?.()} + diff --git a/src/lib/components/standalone-activities/activities-summary-filter-bar/dropdown-filter-list.svelte b/src/lib/components/standalone-activities/activities-summary-filter-bar/dropdown-filter-list.svelte new file mode 100644 index 0000000000..6db1c9e76c --- /dev/null +++ b/src/lib/components/standalone-activities/activities-summary-filter-bar/dropdown-filter-list.svelte @@ -0,0 +1,117 @@ + + +{#if visibleFilters.length > 0} +
+ {#each visibleFilters as activityFilter, i (activityFilter.attribute + '-' + i)} + {#if isStatusFilter(activityFilter) && i === firstExecutionStatusIndex} + updateStatusFilters(i, statusFilters)} + /> + {:else if !isStatusFilter(activityFilter) && activityFilter.attribute} + updateFilter(i, updatedFilter)} + onRemove={() => removeFilter(i)} + /> + {/if} + {/each} + + {#if hasMoreFilters} + + {/if} +
+{/if} diff --git a/src/lib/components/standalone-activities/activities-summary-filter-bar/edit-view-modal.svelte b/src/lib/components/standalone-activities/activities-summary-filter-bar/edit-view-modal.svelte new file mode 100644 index 0000000000..95f69eacd2 --- /dev/null +++ b/src/lib/components/standalone-activities/activities-summary-filter-bar/edit-view-modal.svelte @@ -0,0 +1,29 @@ + + + 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 new file mode 100644 index 0000000000..d46b7a3e58 --- /dev/null +++ b/src/lib/components/standalone-activities/activities-summary-filter-bar/filter-bar.svelte @@ -0,0 +1,40 @@ + + +{#snippet actionToggleButtons()} +
+ +
+{/snippet} + +
+
+
+ + +
+ {@render actionToggleButtons()} +
+ {#if viewManualQuery} + + {/if} +
diff --git a/src/lib/components/standalone-activities/activities-summary-filter-bar/filter.svelte b/src/lib/components/standalone-activities/activities-summary-filter-bar/filter.svelte new file mode 100644 index 0000000000..2168f765b6 --- /dev/null +++ b/src/lib/components/standalone-activities/activities-summary-filter-bar/filter.svelte @@ -0,0 +1,70 @@ + + + + +
+ + +
diff --git a/src/lib/components/standalone-activities/activities-summary-filter-bar/manual-query.svelte b/src/lib/components/standalone-activities/activities-summary-filter-bar/manual-query.svelte new file mode 100644 index 0000000000..1221aa6f23 --- /dev/null +++ b/src/lib/components/standalone-activities/activities-summary-filter-bar/manual-query.svelte @@ -0,0 +1,91 @@ + + +
+ +
diff --git a/src/lib/components/standalone-activities/activities-summary-filter-bar/save-view-modal.svelte b/src/lib/components/standalone-activities/activities-summary-filter-bar/save-view-modal.svelte new file mode 100644 index 0000000000..a441c00acb --- /dev/null +++ b/src/lib/components/standalone-activities/activities-summary-filter-bar/save-view-modal.svelte @@ -0,0 +1,13 @@ + + + diff --git a/src/lib/components/standalone-activities/activities-summary-filter-bar/search-attribute-menu.svelte b/src/lib/components/standalone-activities/activities-summary-filter-bar/search-attribute-menu.svelte new file mode 100644 index 0000000000..10e151f78c --- /dev/null +++ b/src/lib/components/standalone-activities/activities-summary-filter-bar/search-attribute-menu.svelte @@ -0,0 +1,160 @@ + + + + = MAX_QUERY_LENGTH} + onclick={() => (searchAttributeValue = '')} + class="text-nowrap" + size="xs" + > + Add Filter + + + { + document.getElementById('activity-filter-search')?.focus(); + }} + > + + +
+ + {#each filteredOptions as { value, label, type }} + { + handleNewQuery(value, type); + }} + disabled={value === 'ExecutionStatus' && + !!$activityFilters.find((f) => isStatusFilter(f))} + > +
+

{label}

+ {type} +
+
+ {:else} + {translate('common.no-results')} + {/each} +
+
+{#if $activityFilters.length > 0} + +{/if} diff --git a/src/lib/components/standalone-activities/activity-actions.svelte b/src/lib/components/standalone-activities/activity-actions.svelte new file mode 100644 index 0000000000..7d184792b4 --- /dev/null +++ b/src/lib/components/standalone-activities/activity-actions.svelte @@ -0,0 +1,98 @@ + + +
+ + + + {translate('workflows.more-actions')} + + + (terminateConfirmationModalOpen = true)} + destructive + disabled={!isRunning} + data-testid="terminate-button" + > + {translate('standalone-activities.terminate')} + + + + goto( + routeForStartStandaloneActivity({ + namespace, + activityId: activityExecutionInfo.activityId, + activityType: activityExecutionInfo.activityType.name, + taskQueue: activityExecutionInfo.taskQueue, + startToCloseTimeout: activityExecutionInfo.startToCloseTimeout, + scheduleToCloseTimeout: + activityExecutionInfo.scheduleToCloseTimeout, + }), + )} + data-testid="start-activity-button" + > + {translate('standalone-activities.start-activity-like-this-one')} + + + +
+ + + diff --git a/src/lib/components/standalone-activities/activity-count-refresh.svelte b/src/lib/components/standalone-activities/activity-count-refresh.svelte new file mode 100644 index 0000000000..5db0d08437 --- /dev/null +++ b/src/lib/components/standalone-activities/activity-count-refresh.svelte @@ -0,0 +1,23 @@ + + + diff --git a/src/lib/components/standalone-activities/activity-counts.svelte b/src/lib/components/standalone-activities/activity-counts.svelte new file mode 100644 index 0000000000..d0f95304c4 --- /dev/null +++ b/src/lib/components/standalone-activities/activity-counts.svelte @@ -0,0 +1,162 @@ + + +
+ {#each statusGroups as { count, status } (status)} + {#if !loading} + + {:else} + + {/if} + {/each} +
diff --git a/src/lib/components/standalone-activities/activity-header.svelte b/src/lib/components/standalone-activities/activity-header.svelte new file mode 100644 index 0000000000..7bfdb40bb1 --- /dev/null +++ b/src/lib/components/standalone-activities/activity-header.svelte @@ -0,0 +1,100 @@ + + +
+
+
+ +
+ +
+
+ +
+ + + Scheduled Time + + Last Started Time + + Close Time + + Duration + + + + Run ID + + Activity Type + + Task Queue + + + +
diff --git a/src/lib/components/standalone-activities/activity-input-and-outcome.svelte b/src/lib/components/standalone-activities/activity-input-and-outcome.svelte new file mode 100644 index 0000000000..0b94a952d1 --- /dev/null +++ b/src/lib/components/standalone-activities/activity-input-and-outcome.svelte @@ -0,0 +1,40 @@ + + +
+
+
Input
+ + {#snippet children(decodedValue)} + + {/snippet} + +
+
+
Outcome
+ {#if has(outcome, 'failure')} + + {:else if has(outcome, 'result')} + + {#snippet children(decodedValue)} + + {/snippet} + + {:else} + + {/if} +
+
diff --git a/src/lib/components/standalone-activities/activity-status.svelte b/src/lib/components/standalone-activities/activity-status.svelte new file mode 100644 index 0000000000..56203f0390 --- /dev/null +++ b/src/lib/components/standalone-activities/activity-status.svelte @@ -0,0 +1,53 @@ + + + + +
+ {activityStatusToLabel[status]} + {#if status === 'ACTIVITY_EXECUTION_STATUS_RUNNING'} + + {/if} +
diff --git a/src/lib/components/standalone-activities/cancel-confirmation-modal.svelte b/src/lib/components/standalone-activities/cancel-confirmation-modal.svelte new file mode 100644 index 0000000000..2abb22d7ad --- /dev/null +++ b/src/lib/components/standalone-activities/cancel-confirmation-modal.svelte @@ -0,0 +1,72 @@ + + + +

{translate('standalone-activities.cancel-modal-title')}

+ +

+ {translate('standalone-activities.cancel-modal-confirmation')} +

+
+
diff --git a/src/lib/components/standalone-activities/saved-views.svelte b/src/lib/components/standalone-activities/saved-views.svelte new file mode 100644 index 0000000000..473fd8b2e8 --- /dev/null +++ b/src/lib/components/standalone-activities/saved-views.svelte @@ -0,0 +1,515 @@ + + +
+
+
+ {#if $savedQueryNavOpen} + + {/if} +

Saved Views

+ +
+
+ +
+
+
+ {#each systemActivityViews as view} + {@render queryButton({ + ...view, + active: query === view.query, + })} + {/each} +
+
+ + {#if $savedQueryNavOpen} + + {/if} + +
+ + {#if unsavedQuery} + {@render queryButton(unsaveView)} + {/if} + + {#if namespaceSavedQueries.length > 0} +
+
+ {#each namespaceSavedQueries as savedQuery} + {@render queryButton({ + ...savedQuery, + active: savedQuery.id === activeQueryView?.id, + badge: + savedQuery.id === activeQueryView?.id && + savedQuery.query !== query + ? 'Unsaved' + : undefined, + })} + {/each} +
+
+ {/if} + + {#if namespaceSavedQueries.length === 0 && !unsavedQuery} +

+ No Views +

+ {/if} +
+
+ + + +{#snippet queryButton(view: SavedQuery)} + +{/snippet} + +{#snippet queryBadge({ + className, + content, + iconClass, + icon, +}: { + className?: ClassNameValue; + content: string | number; + iconClass?: ClassNameValue; + icon?: IconName; +})} + +{/snippet} + +{#if showTooltip && tooltipText} + +{/if} diff --git a/src/lib/components/standalone-activities/standalone-activities-disabled.svelte b/src/lib/components/standalone-activities/standalone-activities-disabled.svelte new file mode 100644 index 0000000000..5e109980ba --- /dev/null +++ b/src/lib/components/standalone-activities/standalone-activities-disabled.svelte @@ -0,0 +1,39 @@ + + +

Standalone Activities

+ +
+

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

+ +

+ {translate( + 'standalone-activities.standalone-activities-enablement-per-namespace', + )} +

+ +
diff --git a/src/lib/components/standalone-activities/standalone-activities-guard.svelte b/src/lib/components/standalone-activities/standalone-activities-guard.svelte new file mode 100644 index 0000000000..e4b771a01c --- /dev/null +++ b/src/lib/components/standalone-activities/standalone-activities-guard.svelte @@ -0,0 +1,19 @@ + + +{#if namespace.namespaceInfo?.capabilities?.standaloneActivities} + {@render children()} +{:else if fallback} + {@render fallback()} +{/if} diff --git a/src/lib/components/standalone-activities/start-activity-button.svelte b/src/lib/components/standalone-activities/start-activity-button.svelte new file mode 100644 index 0000000000..d4ca2fbe5f --- /dev/null +++ b/src/lib/components/standalone-activities/start-activity-button.svelte @@ -0,0 +1,50 @@ + + + + + diff --git a/src/lib/components/standalone-activities/start-standalone-activity-form/form.svelte b/src/lib/components/standalone-activities/start-standalone-activity-form/form.svelte new file mode 100644 index 0000000000..68538db375 --- /dev/null +++ b/src/lib/components/standalone-activities/start-standalone-activity-form/form.svelte @@ -0,0 +1,414 @@ + + +
+ + + + + + + checkTaskQueue($form.taskQueue)} + /> + {#if taskQueueActive !== null} + +
+ + View Task Queue + +
+
+ {/if} + + + + + +
{translate('standalone-activities.form-timeouts-heading')}
+ + + + + + + + {#if $errors.startToCloseTimeout} +

+ {$errors.startToCloseTimeout} +

+ {/if} +
+ + {#if advancedOptionsVisible} + +
+
+ {translate('standalone-activities.form-search-attributes-heading')} +
+

+ {translate( + 'standalone-activities.form-search-attributes-description', + )} +

+
+ +
+ + +
+
{translate('standalone-activities.form-user-metadata-heading')}
+

+ {translate('standalone-activities.form-user-metadata-description')} +

+
+
+
+
+
+
+ + +
{translate('standalone-activities.form-retry-policy-heading')}
+ +
+ + + + + + +
{translate('standalone-activities.form-id-policies-heading')}
+ + + + +
+ {/if} + +
+ + + +
+ diff --git a/src/lib/components/standalone-activities/start-standalone-activity-form/types.ts b/src/lib/components/standalone-activities/start-standalone-activity-form/types.ts new file mode 100644 index 0000000000..4f6887d3a5 --- /dev/null +++ b/src/lib/components/standalone-activities/start-standalone-activity-form/types.ts @@ -0,0 +1,36 @@ +import type { PayloadInputEncoding } from '$lib/models/payload-encoding'; +import type { SearchAttributeInput } from '$lib/stores/search-attributes'; + +export interface StandaloneActivityFormData { + identity: string; + namespace: string; + activityId: string; + taskQueue: string; + activityType: string; + startToCloseTimeout: string; + scheduleToCloseTimeout: string; + scheduleToStartTimeout: string; + input: string; + encoding: PayloadInputEncoding; + messageType: string; + searchAttributes: SearchAttributeInput[]; + summary: string; + details: string; + // retry policy + initialInterval: string; + backoffCoefficient: string; + maximumInterval: string; + maximumAttempts: string; +} + +export type StandaloneActivityFormDefaults = Pick< + StandaloneActivityFormData, + | 'identity' + | 'namespace' + | 'encoding' + | 'activityId' + | 'activityType' + | 'taskQueue' + | 'startToCloseTimeout' + | 'scheduleToCloseTimeout' +>; diff --git a/src/lib/components/standalone-activities/terminate-confirmation-modal.svelte b/src/lib/components/standalone-activities/terminate-confirmation-modal.svelte new file mode 100644 index 0000000000..cedee979eb --- /dev/null +++ b/src/lib/components/standalone-activities/terminate-confirmation-modal.svelte @@ -0,0 +1,85 @@ + + + +

+ {translate('standalone-activities.terminate-modal-title')} +

+
+

+ {translate('standalone-activities.terminate-modal-confirmation')} +

+ + +
+
diff --git a/src/lib/components/workflow/metadata/workflow-summary-and-details.svelte b/src/lib/components/user-metadata.svelte similarity index 71% rename from src/lib/components/workflow/metadata/workflow-summary-and-details.svelte rename to src/lib/components/user-metadata.svelte index 3e857215f2..00c7271696 100644 --- a/src/lib/components/workflow/metadata/workflow-summary-and-details.svelte +++ b/src/lib/components/user-metadata.svelte @@ -1,12 +1,13 @@
@@ -42,12 +43,4 @@
{/if} -
-
-

Events with Metadata

-
-
- -
-
diff --git a/src/lib/components/worker-table.svelte b/src/lib/components/worker-table.svelte index c66592c3e2..52737720ca 100644 --- a/src/lib/components/worker-table.svelte +++ b/src/lib/components/worker-table.svelte @@ -12,7 +12,6 @@ import { translate } from '$lib/i18n/translate'; import { getWorkflowPollersWithVersions } from '$lib/runes/workflow-versions.svelte'; import { type PollerWithTaskQueueTypes } from '$lib/services/pollers-service'; - import { workflowRun } from '$lib/stores/workflow-run'; import type { TaskQueueResponse } from '$lib/types'; import type { DeploymentStatus as Status } from '$lib/types/deployments'; import { routeForWorkerDeployment } from '$lib/utilities/route-for'; @@ -23,12 +22,12 @@ type Props = { workers: TaskQueueResponse; + searchAttributes?: Record; children?: Snippet; }; - let { workers, children }: Props = $props(); + let { workers, searchAttributes = {}, children }: Props = $props(); const { namespace } = $derived(page.params); - const { workflow } = $derived($workflowRun); const { pollers, pinned, @@ -37,7 +36,7 @@ currentDeployment, rampingBuildId, rampingDeployment, - } = $derived(getWorkflowPollersWithVersions(workflow, workers)); + } = $derived(getWorkflowPollersWithVersions(searchAttributes, workers)); const getPollerDeploymentName = (poller: PollerWithTaskQueueTypes) => { const deployment = diff --git a/src/lib/components/workflow/add-search-attributes.svelte b/src/lib/components/workflow/add-search-attributes.svelte index 6a966e7541..f64db6b8e4 100644 --- a/src/lib/components/workflow/add-search-attributes.svelte +++ b/src/lib/components/workflow/add-search-attributes.svelte @@ -34,8 +34,8 @@
- {#each attributes as attribute} - + {#each attributes as attribute, id} + {/each} + {/snippet} + diff --git a/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/+layout.svelte b/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/+layout.svelte new file mode 100644 index 0000000000..3dfc0d23e2 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/+layout.svelte @@ -0,0 +1,54 @@ + + +{#if error} + +{:else} + + {@render children()} + +{/if} diff --git a/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/+page.ts b/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/+page.ts new file mode 100644 index 0000000000..2484777e99 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/+page.ts @@ -0,0 +1,7 @@ +import { redirect } from '@sveltejs/kit'; + +import type { PageLoad } from './$types'; + +export const load: PageLoad = async function ({ url }) { + redirect(302, `${url.pathname}/details`); +}; diff --git a/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/details/+page.svelte b/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/details/+page.svelte new file mode 100644 index 0000000000..e807d38b01 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/details/+page.svelte @@ -0,0 +1,16 @@ + + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/metadata/+page.svelte b/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/metadata/+page.svelte new file mode 100644 index 0000000000..25e4b9a7d5 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/metadata/+page.svelte @@ -0,0 +1,15 @@ + + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/search-attributes/+page.svelte b/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/search-attributes/+page.svelte new file mode 100644 index 0000000000..83ee89bca1 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/search-attributes/+page.svelte @@ -0,0 +1,14 @@ + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/workers/+page.svelte b/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/workers/+page.svelte new file mode 100644 index 0000000000..4735655aae --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/activities/[activityId]/workers/+page.svelte @@ -0,0 +1,12 @@ + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/activities/start/+page.svelte b/src/routes/(app)/namespaces/[namespace]/activities/start/+page.svelte new file mode 100644 index 0000000000..fb3c54d8d9 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/activities/start/+page.svelte @@ -0,0 +1,18 @@ + + + + + diff --git a/tests/integration/start-standalone-activity.spec.ts b/tests/integration/start-standalone-activity.spec.ts new file mode 100644 index 0000000000..1376f7cbef --- /dev/null +++ b/tests/integration/start-standalone-activity.spec.ts @@ -0,0 +1,83 @@ +import { expect, test } from '@playwright/test'; + +import { StartStandaloneActivityPage } from '~/pages/start-standalone-activity'; +import { + mockGlobalApis, + mockNamespaceApi, + mockSearchAttributesApi, + mockSettingsApi, +} from '~/test-utilities/mock-apis'; + +test.describe('Start a Standalone Activity', () => { + test.beforeEach(async ({ page }) => { + await mockGlobalApis(page); + await mockNamespaceApi(page); + await mockSettingsApi(page); + await mockSearchAttributesApi(page); + }); + + test.skip('Allows select form fields to be pre-filled via URL Search Parameters', async ({ + page, + }) => { + const startStandaloneActivityPage = new StartStandaloneActivityPage(page); + + await startStandaloneActivityPage.goto({ + activityId: 'abc-123', + activityType: 'greet', + taskQueue: 'default', + startToCloseTimeout: '1', + scheduleToCloseTimeout: '10', + }); + + await expect(startStandaloneActivityPage.activityIdInput).toHaveValue( + 'abc-123', + ); + await expect(startStandaloneActivityPage.activityTypeInput).toHaveValue( + 'greet', + ); + await expect(startStandaloneActivityPage.taskQueueInput).toHaveValue( + 'default', + ); + await expect( + startStandaloneActivityPage.startToCloseTimeoutInput, + ).toHaveValue('1'); + await expect( + startStandaloneActivityPage.scheduleToCloseTimeoutInput, + ).toHaveValue('10'); + }); + + test.skip('Displays errors when select form fields are incomplete', async ({ + page, + }) => { + const startStandaloneActivityPage = new StartStandaloneActivityPage(page); + await startStandaloneActivityPage.goto(); + + await startStandaloneActivityPage.submitButton.click(); + + await expect( + startStandaloneActivityPage.activityIdInputError, + ).toBeVisible(); + await expect( + startStandaloneActivityPage.activityTypeInputError, + ).toBeVisible(); + await expect(startStandaloneActivityPage.taskQueueInputError).toBeVisible(); + await expect(startStandaloneActivityPage.timeoutError).toBeVisible(); + }); + + test.skip('Allows expanding more options', async ({ page }) => { + const startStandaloneActivityPage = new StartStandaloneActivityPage(page); + await startStandaloneActivityPage.goto(); + + await expect(startStandaloneActivityPage.moreOptionsButton).toBeVisible(); + await expect( + startStandaloneActivityPage.addSearchAttributesCard, + ).toBeHidden(); + await expect(startStandaloneActivityPage.addMetadataCard).toBeHidden(); + + await startStandaloneActivityPage.moreOptionsButton.click(); + await expect( + startStandaloneActivityPage.addSearchAttributesCard, + ).toBeVisible(); + await expect(startStandaloneActivityPage.addMetadataCard).toBeVisible(); + }); +}); diff --git a/tests/pages/start-standalone-activity.ts b/tests/pages/start-standalone-activity.ts new file mode 100644 index 0000000000..d1ba9a6967 --- /dev/null +++ b/tests/pages/start-standalone-activity.ts @@ -0,0 +1,61 @@ +import type { Locator, Page } from '@playwright/test'; + +export class StartStandaloneActivityPage { + readonly page: Page; + readonly activityIdInput: Locator; + readonly activityTypeInput: Locator; + readonly taskQueueInput: Locator; + readonly startToCloseTimeoutInput: Locator; + readonly scheduleToCloseTimeoutInput: Locator; + readonly submitButton: Locator; + readonly activityIdInputError: Locator; + readonly activityTypeInputError: Locator; + readonly taskQueueInputError: Locator; + readonly timeoutError: Locator; + readonly moreOptionsButton: Locator; + readonly addSearchAttributesCard: Locator; + readonly addMetadataCard: Locator; + + constructor(page: Page) { + this.page = page; + this.activityIdInput = page.locator('#activityId'); + this.activityTypeInput = page.locator('#activityType'); + this.taskQueueInput = page.locator('#taskQueue'); + this.startToCloseTimeoutInput = page.locator('#startToCloseTimeout'); + this.scheduleToCloseTimeoutInput = page.locator('#scheduleToCloseTimeout'); + this.submitButton = page.getByTestId( + 'start-standalone-activity-submit-button', + ); + this.activityIdInputError = page.getByText('Activity ID is required.'); + this.activityTypeInputError = page.getByText('Activity Type is required.'); + this.taskQueueInputError = page.getByText('Task Queue is required.'); + this.timeoutError = page.getByText( + 'Either "Start to Close Timeout" or "Schedule to Close Timeout" is required.', + ); + + this.moreOptionsButton = page.getByTestId( + 'start-standalone-activity-more-options', + ); + this.addSearchAttributesCard = page.getByTestId( + 'start-standalone-activity-add-search-attributes', + ); + this.addMetadataCard = page.getByTestId( + 'start-standalone-activity-add-metadata', + ); + } + + public goto = async (params: Record = {}) => { + const paramsString = Object.entries(params).reduce( + (queryString, [key, value]) => { + return `${queryString}&${key}=${value}`; + }, + '', + ); + + const path = paramsString + ? `/namespaces/default/activities/start?${paramsString}` + : '/namespaces/default/activities/start'; + + await this.page.goto(path); + }; +} diff --git a/tests/test-utilities/mocks/namespace.ts b/tests/test-utilities/mocks/namespace.ts index eee296800e..a1e6e61a99 100644 --- a/tests/test-utilities/mocks/namespace.ts +++ b/tests/test-utilities/mocks/namespace.ts @@ -43,6 +43,9 @@ const MOCK_DEFAULT_NAMESPACE = { data: {}, id: 'bbe0d4ea-c682-4c5a-bc4b-fadb8e8c8bfe', supportsSchedules: true, + capabilities: { + standaloneActivities: true, + }, }, config: { workflowExecutionRetentionTtl: '86400s',