+ Runs the handler now against a placeholder variable, as g/variable_expiration_handler.
+ Save the handler first: the test runs what is stored.
+
+ {#if testJob !== undefined}
+
+
+ {#if testJob.in_progress}
+
+ Sending message...
+ {:else if testJob.is_success}
+
+ Message sent via Windmill job
+ {:else}
+
+ Message not sent
+ {/if}
+
+ {testJob.uuid}
+
+
+
+
+ {/if}
+
+ {/if}
+
+ {#if handlerSelected !== 'custom' && workspaceConnectedToSlack === false && handlerSelected === 'slack'}
+
+ Connect this workspace to Slack to notify a channel without writing a script.
+
+ {/if}
+
+
diff --git a/frontend/src/lib/hub.ts b/frontend/src/lib/hub.ts
index 25b3f8b3fd..dfbc6b3723 100644
--- a/frontend/src/lib/hub.ts
+++ b/frontend/src/lib/hub.ts
@@ -59,6 +59,13 @@ type HubPaths = {
teamsErrorHandler: string
teamsRecoveryHandler: string
teamsSuccessHandler: string
+ // TODO(WIN-2332): the ids of these two are placeholders until the scripts are pushed to
+ // the hub, which assigns them. Nothing resolves them meanwhile, and the image build runs
+ // `windmill cache hubPaths.json` over every entry (Dockerfile), so it fails on an id the
+ // hub does not have. Replace both before merging; only the number matters, the slug is
+ // ours and is what the settings tab matches on.
+ slackVariableExpirationHandler: string
+ teamsVariableExpirationHandler: string
emailErrorHandler: string
cloneRepoToS3forGitRepoViewer: string
appReport: string
diff --git a/frontend/src/lib/hubPaths.json b/frontend/src/lib/hubPaths.json
index 2862fc7768..feb01e9246 100644
--- a/frontend/src/lib/hubPaths.json
+++ b/frontend/src/lib/hubPaths.json
@@ -8,6 +8,8 @@
"teamsErrorHandler": "hub/19742/workspace-or-schedule-error-handler-teams",
"teamsRecoveryHandler": "hub/11593/schedule-recovery-handler-teams",
"teamsSuccessHandler": "hub/11596/schedule-success-handler-teams",
+ "slackVariableExpirationHandler": "hub/99999901/variable-expiration-handler-slack",
+ "teamsVariableExpirationHandler": "hub/99999902/variable-expiration-handler-teams",
"slackReport": "hub/28792/slack",
"discordReport": "hub/9085/discord",
"smtpReport": "hub/28242/smtp",
diff --git a/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte b/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte
index 27696443ce..966767a3fa 100644
--- a/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte
+++ b/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte
@@ -10,6 +10,10 @@
import DeployToSetting from '$lib/components/DeployToSetting.svelte'
import DevWorkspaceSetting from '$lib/components/DevWorkspaceSetting.svelte'
import ErrorOrRecoveryHandler from '$lib/components/ErrorOrRecoveryHandler.svelte'
+ import VariableExpirationHandler, {
+ getVariableExpirationHandlerType,
+ type VariableExpirationHandlerType
+ } from '$lib/components/VariableExpirationHandler.svelte'
import PageHeader from '$lib/components/PageHeader.svelte'
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
@@ -188,10 +192,12 @@
let variableExpirationPath: string | undefined = $state(undefined)
let variableExpirationItemKind: 'flow' | 'script' = $state('script')
let variableExpirationMutedOnUserPath: boolean = $state(false)
- // Carried through saves rather than edited: `extra_args` is an API/CLI-only escape hatch
- // for passing fixed arguments to the handler, and omitting it on a save that keeps the
- // handler would erase it. Only a save that clears the handler drops it.
- let variableExpirationExtraArgs: Record | undefined = $state(undefined)
+ let variableExpirationSelected: VariableExpirationHandlerType = $state('custom')
+ // The built-in destinations write `channel` here, and the API and CLI can set arbitrary
+ // fixed arguments the tab never renders. Both have to survive a save that keeps the
+ // handler, since the endpoint rebuilds the setting from the request and an omitted
+ // `extra_args` erases it. Only a save that clears the handler drops it.
+ let variableExpirationExtraArgs: Record = $state({})
let criticalAlertUIMuted: boolean | undefined = $state(undefined)
let initialCriticalAlertUIMuted: boolean | undefined = $state(undefined)
let publicAppRateLimitPerMinute: number | undefined = $state(undefined)
@@ -249,6 +255,7 @@
let initialVariableExpirationPath: string | undefined = $state(undefined)
let initialVariableExpirationItemKind: 'flow' | 'script' = $state('script')
let initialVariableExpirationMutedOnUserPath: boolean = $state(false)
+ let initialVariableExpirationExtraArgs: Record = $state({})
let s3ResourceSettings: S3ResourceSettings = $state({
resourceType: 's3',
@@ -324,11 +331,13 @@
initialVariableExpirationItemKind,
initialVariableExpirationPath
),
- mutedOnUserPath: initialVariableExpirationMutedOnUserPath
+ mutedOnUserPath: initialVariableExpirationMutedOnUserPath,
+ extraArgs: normalizeHandlerExtraArgs(initialVariableExpirationExtraArgs)
},
{
handlerPath: variableExpirationFullPath(variableExpirationItemKind, variableExpirationPath),
- mutedOnUserPath: variableExpirationMutedOnUserPath
+ mutedOnUserPath: variableExpirationMutedOnUserPath,
+ extraArgs: normalizeHandlerExtraArgs(variableExpirationExtraArgs)
}
)
})
@@ -693,7 +702,8 @@
: 'script'
variableExpirationPath = variableExpirationHandlerPath.split('/').slice(1).join('/')
variableExpirationMutedOnUserPath = variableExpirationHandler?.muted_on_user_path ?? false
- variableExpirationExtraArgs = variableExpirationHandler?.extra_args
+ variableExpirationExtraArgs = variableExpirationHandler?.extra_args ?? {}
+ variableExpirationSelected = getVariableExpirationHandlerType(variableExpirationPath)
workspaceDefaultAppPath = settings.default_app
initialWorkspaceDefaultAppPath = settings.default_app
@@ -750,6 +760,7 @@
initialVariableExpirationPath = variableExpirationPath
initialVariableExpirationItemKind = variableExpirationItemKind
initialVariableExpirationMutedOnUserPath = variableExpirationMutedOnUserPath
+ initialVariableExpirationExtraArgs = clone(variableExpirationExtraArgs)
loadedSettings = true
}
@@ -925,11 +936,11 @@
async function editVariableExpirationHandler() {
const path = variableExpirationFullPath(variableExpirationItemKind, variableExpirationPath)
// Saving with no path clears the whole setting server-side, `extra_args` included, so
- // the loaded copy has to go too: a handler picked afterwards without reloading the page
- // would otherwise be persisted with the cleared one's arguments, which this tab does
- // not display.
+ // the in-memory copy has to go too: a handler picked afterwards without reloading the
+ // page would otherwise be persisted with the cleared one's arguments, including any the
+ // API or CLI set that this tab never renders.
if (!path) {
- variableExpirationExtraArgs = undefined
+ variableExpirationExtraArgs = {}
}
await WorkspaceService.editVariableExpirationHandler({
workspace: $workspaceStore!,
@@ -950,12 +961,15 @@
initialVariableExpirationPath = variableExpirationPath
initialVariableExpirationItemKind = variableExpirationItemKind
initialVariableExpirationMutedOnUserPath = variableExpirationMutedOnUserPath
+ initialVariableExpirationExtraArgs = clone(variableExpirationExtraArgs)
}
function discardVariableExpirationChanges() {
variableExpirationPath = initialVariableExpirationPath
variableExpirationItemKind = initialVariableExpirationItemKind
variableExpirationMutedOnUserPath = initialVariableExpirationMutedOnUserPath
+ variableExpirationExtraArgs = clone(initialVariableExpirationExtraArgs)
+ variableExpirationSelected = getVariableExpirationHandlerType(variableExpirationPath)
}
async function editSuccessHandler() {
@@ -1219,14 +1233,16 @@
initialVariableExpirationItemKind,
initialVariableExpirationPath
),
- mutedOnUserPath: initialVariableExpirationMutedOnUserPath
+ mutedOnUserPath: initialVariableExpirationMutedOnUserPath,
+ extraArgs: normalizeHandlerExtraArgs(initialVariableExpirationExtraArgs)
},
modifiedValue: {
handlerPath: variableExpirationFullPath(
variableExpirationItemKind,
variableExpirationPath
),
- mutedOnUserPath: variableExpirationMutedOnUserPath
+ mutedOnUserPath: variableExpirationMutedOnUserPath,
+ extraArgs: normalizeHandlerExtraArgs(variableExpirationExtraArgs)
}
}
case 'critical_alerts':
@@ -2120,26 +2136,22 @@ export async function main(