mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 00:02:19 +00:00
aefa43dcaf
* fix: Fix error handler token injection * Add frontend
285 lines
8.3 KiB
Svelte
285 lines
8.3 KiB
Svelte
<script lang="ts">
|
|
import { Alert, Button, Tab, Tabs } from '$lib/components/common'
|
|
import SchemaForm from '$lib/components/SchemaForm.svelte'
|
|
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
|
|
import Toggle from '$lib/components/Toggle.svelte'
|
|
import type { Schema, SupportedLanguage } from '$lib/common'
|
|
import { enterpriseLicense, workspaceStore } from '$lib/stores'
|
|
import { emptySchema, emptyString, sendUserToast, tryEvery } from '$lib/utils'
|
|
import { JobService, Script, ScriptService, WorkspaceService } from '$lib/gen'
|
|
import { inferArgs } from '$lib/infer'
|
|
import Icon from 'svelte-awesome'
|
|
import { check } from 'svelte-awesome/icons'
|
|
import { faRotate, faRotateRight, faTimes } from '@fortawesome/free-solid-svg-icons'
|
|
|
|
export let isEditable: boolean
|
|
export let slackToggleText: string = 'enable'
|
|
export let showScriptHelpText: boolean = false
|
|
export let handlerSelected: 'custom' | 'slack'
|
|
export let handlersOnlyForEe: string[]
|
|
|
|
export let handlerPath: string | undefined
|
|
export let handlerExtraArgs: Record<string, any>
|
|
|
|
export let customInitialScriptPath: string | undefined
|
|
export let customScriptTemplate: string
|
|
export let customHandlerKind: 'flow' | 'script' = 'script'
|
|
let customHandlerSchema: Schema | undefined
|
|
|
|
export let slackHandlerScriptPath: string
|
|
let slackHandlerSchema: Schema | undefined
|
|
let workspaceConnectedToSlack: boolean
|
|
let slackConnectionTestJob:
|
|
| { uuid: string; is_success: boolean; in_progress: boolean }
|
|
| undefined
|
|
|
|
async function loadSlackResources() {
|
|
const settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! })
|
|
if (!emptyString(settings.slack_name) && !emptyString(settings.slack_team_id)) {
|
|
workspaceConnectedToSlack = true
|
|
}
|
|
}
|
|
|
|
async function sendSlackMessage(channel: string): Promise<void> {
|
|
let submitted_job = await WorkspaceService.runSlackMessageTestJob({
|
|
workspace: $workspaceStore!,
|
|
requestBody: {
|
|
hub_script_path: handlerPath,
|
|
channel: channel,
|
|
test_msg: `This is a notification to test the connection between Slack and Windmill workspace '${$workspaceStore!}'`
|
|
}
|
|
})
|
|
slackConnectionTestJob = {
|
|
uuid: submitted_job.job_uuid!,
|
|
in_progress: true,
|
|
is_success: false
|
|
}
|
|
tryEvery({
|
|
tryCode: async () => {
|
|
const testResult = await JobService.getCompletedJob({
|
|
workspace: $workspaceStore!,
|
|
id: slackConnectionTestJob!.uuid
|
|
})
|
|
slackConnectionTestJob!.in_progress = false
|
|
slackConnectionTestJob!.is_success = testResult.success
|
|
},
|
|
timeoutCode: async () => {
|
|
try {
|
|
await JobService.cancelQueuedJob({
|
|
workspace: $workspaceStore!,
|
|
id: slackConnectionTestJob!.uuid,
|
|
requestBody: {
|
|
reason: 'Slack message not sent after after 5s'
|
|
}
|
|
})
|
|
} catch (err) {
|
|
console.error(err)
|
|
}
|
|
},
|
|
interval: 500,
|
|
timeout: 5000
|
|
})
|
|
}
|
|
|
|
async function loadHandlerScriptArgs(p: string, defaultArgs: string[] = []) {
|
|
try {
|
|
let schema: Schema | undefined = emptySchema()
|
|
if (p.startsWith('hub/')) {
|
|
const hubScript = await ScriptService.getHubScriptByPath({
|
|
path: p
|
|
})
|
|
|
|
if (hubScript.schema?.properties) {
|
|
schema = hubScript.schema
|
|
} else {
|
|
await inferArgs(hubScript.language as SupportedLanguage, hubScript.content ?? '', schema)
|
|
}
|
|
} else {
|
|
const script = await ScriptService.getScriptByPath({ workspace: $workspaceStore!, path: p })
|
|
schema = script.schema as Schema
|
|
}
|
|
if (schema && schema.properties) {
|
|
for (let key in schema.properties) {
|
|
if (defaultArgs.includes(key)) {
|
|
delete schema.properties[key]
|
|
}
|
|
}
|
|
return schema
|
|
}
|
|
} catch (err) {
|
|
sendUserToast(`Could not query handler schema: ${err}`, true)
|
|
}
|
|
}
|
|
|
|
$: {
|
|
if ($workspaceStore) {
|
|
loadSlackResources()
|
|
}
|
|
}
|
|
|
|
$: handlerPath &&
|
|
handlerPath !== slackHandlerScriptPath &&
|
|
loadHandlerScriptArgs(handlerPath, [
|
|
'path',
|
|
'workspace_id',
|
|
'job_id',
|
|
'is_flow',
|
|
'schedule_path',
|
|
'error',
|
|
'error_started_at',
|
|
'failed_times',
|
|
'started_at',
|
|
'success_times',
|
|
'success_result',
|
|
'success_started_at'
|
|
]).then((schema) => (customHandlerSchema = schema))
|
|
|
|
$: loadHandlerScriptArgs(slackHandlerScriptPath, [
|
|
'path',
|
|
'workspace_id',
|
|
'job_id',
|
|
'is_flow',
|
|
'schedule_path',
|
|
'error',
|
|
'error_started_at',
|
|
'failed_times',
|
|
'started_at',
|
|
'success_times',
|
|
'success_result',
|
|
'success_started_at',
|
|
'slack'
|
|
]).then((schema) => (slackHandlerSchema = schema))
|
|
</script>
|
|
|
|
<div>
|
|
<Tabs bind:selected={handlerSelected} class="mt-2 mb-4">
|
|
{#if $enterpriseLicense}
|
|
<Tab value="slack">Slack</Tab>
|
|
<Tab value="custom">
|
|
Custom
|
|
<slot name="custom-tab-tooltip" />
|
|
</Tab>
|
|
{:else}
|
|
<Tab value="custom">
|
|
Custom
|
|
<slot name="custom-tab-tooltip" />
|
|
</Tab>
|
|
<Tab value="slack">Slack {handlersOnlyForEe.includes('slack') ? '(ee only)' : ''}</Tab>
|
|
{/if}
|
|
</Tabs>
|
|
</div>
|
|
|
|
{#if handlerSelected === 'custom'}
|
|
<div class="flex flex-row mb-2">
|
|
<ScriptPicker
|
|
disabled={!isEditable}
|
|
initialPath={customInitialScriptPath}
|
|
kinds={[Script.kind.SCRIPT, Script.kind.FAILURE]}
|
|
allowFlow={true}
|
|
bind:scriptPath={handlerPath}
|
|
bind:itemKind={customHandlerKind}
|
|
allowRefresh
|
|
/>
|
|
|
|
{#if handlerPath === undefined}
|
|
<Button
|
|
btnClasses="ml-4 mt-2"
|
|
color="dark"
|
|
size="xs"
|
|
href={customScriptTemplate}
|
|
target="_blank">Create from template</Button
|
|
>
|
|
{/if}
|
|
</div>
|
|
{#if showScriptHelpText}
|
|
<div class="text-xs">
|
|
Example of error handler scripts can be found on <a
|
|
target="_blank"
|
|
href="https://hub.windmill.dev/failures"
|
|
>
|
|
Windmill Hub</a
|
|
>
|
|
</div>
|
|
{/if}
|
|
{#if handlerPath}
|
|
<p class="font-semibold text-sm mt-4 mb-2">Extra arguments</p>
|
|
<SchemaForm
|
|
disabled={!isEditable}
|
|
schema={customHandlerSchema}
|
|
bind:args={handlerExtraArgs}
|
|
shouldHideNoInputs
|
|
class="text-xs"
|
|
/>
|
|
{#if customHandlerSchema && customHandlerSchema.properties && Object.keys(customHandlerSchema.properties).length === 0}
|
|
<div class="text-xs texg-gray-700">This error handler takes no extra arguments</div>
|
|
{/if}
|
|
{/if}
|
|
{:else if handlerSelected === 'slack'}
|
|
<span class="w-full flex mb-3">
|
|
<Toggle
|
|
disabled={!$enterpriseLicense || !isEditable}
|
|
checked={handlerPath?.startsWith('hub/') &&
|
|
handlerPath?.endsWith('/workspace-or-schedule-error-handler-slack')}
|
|
options={{ right: slackToggleText }}
|
|
on:change={async (e) => {
|
|
handlerPath = e.detail ? slackHandlerScriptPath : undefined
|
|
}}
|
|
/>
|
|
</span>
|
|
{#if workspaceConnectedToSlack}
|
|
<SchemaForm
|
|
disabled={!$enterpriseLicense || handlerPath !== slackHandlerScriptPath}
|
|
schema={slackHandlerSchema}
|
|
schemaSkippedValues={['slack']}
|
|
schemaFieldTooltip={{
|
|
channel: 'Slack channel name without the "#" - example: "windmill-alerts"'
|
|
}}
|
|
bind:args={handlerExtraArgs}
|
|
shouldHideNoInputs
|
|
class="text-xs"
|
|
/>
|
|
{/if}
|
|
{#if enterpriseLicense && handlerPath?.startsWith('hub/') && handlerPath?.endsWith('/workspace-or-schedule-error-handler-slack')}
|
|
{#if !workspaceConnectedToSlack}
|
|
<Alert type="error" title="Workspace not connected to Slack">
|
|
<div class="flex flex-row gap-x-1 w-full items-center">
|
|
<p class="text-clip grow min-w-0">
|
|
The workspace needs to be connected to Slack to use this feature. You can <a
|
|
target="_blank"
|
|
href="/workspace_settings?tab=slack">configure it here</a
|
|
>.
|
|
</p>
|
|
<Button variant="border" color="light" on:click={loadSlackResources}>
|
|
<Icon scale={0.8} data={faRotateRight} />
|
|
</Button>
|
|
</div>
|
|
</Alert>
|
|
{:else}
|
|
<Button
|
|
disabled={emptyString(handlerExtraArgs['channel'])}
|
|
btnClasses="w-32 text-center"
|
|
color="dark"
|
|
on:click={() => sendSlackMessage(handlerExtraArgs['channel'])}
|
|
size="xs">Send test message</Button
|
|
>
|
|
{#if slackConnectionTestJob !== undefined}
|
|
<p class="text-normal text-2xs mt-1 gap-2">
|
|
{#if slackConnectionTestJob.in_progress}
|
|
<Icon scale={0.8} data={faRotate} class="mr-1" />
|
|
{:else if slackConnectionTestJob.is_success}
|
|
<Icon scale={0.8} data={check} class="mr-1 text-green-600" />
|
|
{:else}
|
|
<Icon scale={0.8} data={faTimes} class="mr-1 text-red-700" />
|
|
{/if}
|
|
Message sent via Windmill job
|
|
<a
|
|
target="_blank"
|
|
href={`/run/${slackConnectionTestJob.uuid}?workspace=${$workspaceStore}`}
|
|
>{slackConnectionTestJob.uuid}</a
|
|
>
|
|
</p>
|
|
{/if}
|
|
{/if}
|
|
{/if}
|
|
{/if}
|