mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-11 16:09:39 +00:00
feat(frontend): improve and simplify scheduled poll flows (#4560)
This commit is contained in:
@@ -1112,6 +1112,7 @@ async fn test_deno_flow(db: Pool<Postgres>) {
|
||||
custom_concurrency_key: None,
|
||||
concurrent_limit: None,
|
||||
concurrency_time_window_s: None,
|
||||
is_trigger: None,
|
||||
}
|
||||
.into(),
|
||||
stop_after_if: Default::default(),
|
||||
@@ -1153,6 +1154,7 @@ async fn test_deno_flow(db: Pool<Postgres>) {
|
||||
custom_concurrency_key: None,
|
||||
concurrent_limit: None,
|
||||
concurrency_time_window_s: None,
|
||||
is_trigger: None,
|
||||
}
|
||||
.into(),
|
||||
stop_after_if: Default::default(),
|
||||
@@ -1276,6 +1278,8 @@ async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
|
||||
custom_concurrency_key: None,
|
||||
concurrent_limit: None,
|
||||
concurrency_time_window_s: None,
|
||||
is_trigger: None,
|
||||
|
||||
}.into(),
|
||||
stop_after_if: Default::default(),
|
||||
stop_after_all_iters_if: Default::default(),
|
||||
@@ -1327,6 +1331,7 @@ async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
|
||||
custom_concurrency_key: None,
|
||||
concurrent_limit: None,
|
||||
concurrency_time_window_s: None,
|
||||
is_trigger: None,
|
||||
}.into(),
|
||||
stop_after_if: Default::default(),
|
||||
stop_after_all_iters_if: Default::default(),
|
||||
@@ -1364,6 +1369,8 @@ async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
|
||||
custom_concurrency_key: None,
|
||||
concurrent_limit: None,
|
||||
concurrency_time_window_s: None,
|
||||
is_trigger: None,
|
||||
|
||||
}.into(),
|
||||
stop_after_if: Default::default(),
|
||||
stop_after_all_iters_if: Default::default(),
|
||||
@@ -1424,6 +1431,7 @@ async fn test_deno_flow_same_worker(db: Pool<Postgres>) {
|
||||
custom_concurrency_key: None,
|
||||
concurrent_limit: None,
|
||||
concurrency_time_window_s: None,
|
||||
is_trigger: None,
|
||||
}.into(),
|
||||
stop_after_if: Default::default(),
|
||||
stop_after_all_iters_if: Default::default(),
|
||||
|
||||
@@ -1209,6 +1209,7 @@ mod tests {
|
||||
.into(),
|
||||
hash: None,
|
||||
tag_override: None,
|
||||
is_trigger: None,
|
||||
}),
|
||||
stop_after_if: None,
|
||||
stop_after_all_iters_if: None,
|
||||
@@ -1236,6 +1237,7 @@ mod tests {
|
||||
custom_concurrency_key: None,
|
||||
concurrent_limit: None,
|
||||
concurrency_time_window_s: None,
|
||||
is_trigger: None,
|
||||
}),
|
||||
stop_after_if: Some(StopAfterIf {
|
||||
expr: "foo = 'bar'".to_string(),
|
||||
@@ -1290,6 +1292,7 @@ mod tests {
|
||||
input_transforms: HashMap::new(),
|
||||
hash: None,
|
||||
tag_override: None,
|
||||
is_trigger: None,
|
||||
}
|
||||
.into(),
|
||||
stop_after_if: Some(StopAfterIf {
|
||||
|
||||
@@ -426,6 +426,8 @@ pub enum FlowModuleValue {
|
||||
hash: Option<ScriptHash>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
tag_override: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
is_trigger: Option<bool>,
|
||||
},
|
||||
Flow {
|
||||
#[serde(default)]
|
||||
@@ -474,6 +476,8 @@ pub enum FlowModuleValue {
|
||||
concurrent_limit: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
concurrency_time_window_s: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
is_trigger: Option<bool>,
|
||||
},
|
||||
Identity,
|
||||
}
|
||||
@@ -505,6 +509,7 @@ struct UntaggedFlowModuleValue {
|
||||
custom_concurrency_key: Option<String>,
|
||||
concurrent_limit: Option<i32>,
|
||||
concurrency_time_window_s: Option<i32>,
|
||||
is_trigger: Option<bool>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for FlowModuleValue {
|
||||
@@ -522,6 +527,7 @@ impl<'de> Deserialize<'de> for FlowModuleValue {
|
||||
.ok_or_else(|| serde::de::Error::missing_field("path"))?,
|
||||
hash: untagged.hash,
|
||||
tag_override: untagged.tag_override,
|
||||
is_trigger: untagged.is_trigger,
|
||||
}),
|
||||
"flow" => Ok(FlowModuleValue::Flow {
|
||||
input_transforms: untagged.input_transforms.unwrap_or_default(),
|
||||
@@ -574,6 +580,7 @@ impl<'de> Deserialize<'de> for FlowModuleValue {
|
||||
custom_concurrency_key: untagged.custom_concurrency_key,
|
||||
concurrent_limit: untagged.concurrent_limit,
|
||||
concurrency_time_window_s: untagged.concurrency_time_window_s,
|
||||
is_trigger: untagged.is_trigger,
|
||||
}),
|
||||
"identity" => Ok(FlowModuleValue::Identity),
|
||||
other => Err(serde::de::Error::unknown_variant(
|
||||
|
||||
@@ -3522,6 +3522,7 @@ pub async fn push<'c, 'd, R: rsmq_async::RsmqConnection + Send + 'c>(
|
||||
path: path.clone(),
|
||||
hash: Some(hash),
|
||||
tag_override: tag_override,
|
||||
is_trigger: None,
|
||||
},
|
||||
),
|
||||
stop_after_if: None,
|
||||
|
||||
@@ -735,6 +735,7 @@ async fn lock_modules<'c>(
|
||||
custom_concurrency_key,
|
||||
concurrent_limit,
|
||||
concurrency_time_window_s,
|
||||
is_trigger,
|
||||
} = e.get_value()?
|
||||
else {
|
||||
match e.get_value()? {
|
||||
@@ -970,6 +971,7 @@ async fn lock_modules<'c>(
|
||||
custom_concurrency_key,
|
||||
concurrent_limit,
|
||||
concurrency_time_window_s,
|
||||
is_trigger,
|
||||
});
|
||||
new_flow_modules.push(e);
|
||||
continue;
|
||||
@@ -992,6 +994,7 @@ async fn lock_modules<'c>(
|
||||
custom_concurrency_key,
|
||||
concurrent_limit,
|
||||
concurrency_time_window_s,
|
||||
is_trigger,
|
||||
});
|
||||
new_flow_modules.push(e);
|
||||
continue;
|
||||
|
||||
@@ -182,6 +182,11 @@
|
||||
"svelte": "./package/components/icons/WindmillIcon2.svelte",
|
||||
"default": "./package/components/icons/WindmillIcon2.svelte"
|
||||
},
|
||||
"./components/icons/SchedulePollIcon.svelte": {
|
||||
"types": "./package/components/icons/SchedulePollIcon.d.ts",
|
||||
"svelte": "./package/components/icons/SchedulePollIcon.svelte",
|
||||
"default": "./package/components/icons/SchedulePollIcon.svelte"
|
||||
},
|
||||
"./components/IconedResourceType.svelte": {
|
||||
"types": "./package/components/IconedResourceType.svelte.d.ts",
|
||||
"svelte": "./package/components/IconedResourceType.svelte",
|
||||
|
||||
@@ -476,7 +476,7 @@
|
||||
const testStepStore = writable<Record<string, any>>({})
|
||||
const selectedIdStore = writable('settings-metadata')
|
||||
const selectedTriggerStore = writable<
|
||||
'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets'
|
||||
'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' | 'scheduledPoll'
|
||||
>('webhooks')
|
||||
|
||||
const primaryScheduleStore = writable<ScheduleTrigger | undefined | false>(undefined)
|
||||
@@ -484,7 +484,8 @@
|
||||
setContext<TriggerContext>('TriggerContext', {
|
||||
primarySchedule: primaryScheduleStore,
|
||||
selectedTrigger: selectedTriggerStore,
|
||||
triggersCount: triggersCount
|
||||
triggersCount: triggersCount,
|
||||
simplifiedPoll: writable(false)
|
||||
})
|
||||
setContext<FlowEditorContext>('FlowEditorContext', {
|
||||
selectedId: selectedIdStore,
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
? { schedule_count: 1, primary_schedule: { schedule: savedPrimarySchedule.cron } }
|
||||
: undefined
|
||||
)
|
||||
|
||||
const simplifiedPoll = writable(false)
|
||||
export function setPrimarySchedule(schedule: ScheduleTrigger | undefined | false) {
|
||||
primaryScheduleStore.set(schedule)
|
||||
loadTriggers()
|
||||
@@ -468,7 +468,7 @@
|
||||
|
||||
const selectedIdStore = writable<string>(selectedId ?? 'settings-metadata')
|
||||
const selectedTriggerStore = writable<
|
||||
'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets'
|
||||
'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' | 'scheduledPoll'
|
||||
>('webhooks')
|
||||
|
||||
export function getSelectedId() {
|
||||
@@ -490,7 +490,14 @@
|
||||
}
|
||||
|
||||
function selectTrigger(
|
||||
selectedTrigger: 'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets'
|
||||
selectedTrigger:
|
||||
| 'webhooks'
|
||||
| 'emails'
|
||||
| 'schedules'
|
||||
| 'cli'
|
||||
| 'routes'
|
||||
| 'websockets'
|
||||
| 'scheduledPoll'
|
||||
) {
|
||||
selectedTriggerStore.set(selectedTrigger)
|
||||
}
|
||||
@@ -517,7 +524,8 @@
|
||||
setContext<TriggerContext>('TriggerContext', {
|
||||
selectedTrigger: selectedTriggerStore,
|
||||
primarySchedule: primaryScheduleStore,
|
||||
triggersCount
|
||||
triggersCount,
|
||||
simplifiedPoll
|
||||
})
|
||||
|
||||
async function loadTriggers() {
|
||||
@@ -876,7 +884,6 @@
|
||||
await tick()
|
||||
select(module.id)
|
||||
await tick()
|
||||
await tick()
|
||||
focusCopilot()
|
||||
|
||||
let isFirstInLoop = false
|
||||
|
||||
@@ -1008,6 +1008,7 @@
|
||||
modules={job.raw_flow?.modules ?? []}
|
||||
failureModule={job.raw_flow?.failure_module}
|
||||
preprocessorModule={job.raw_flow?.preprocessor_module}
|
||||
allowSimplifiedPoll={false}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import { emptyString, sendUserToast } from '$lib/utils'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import { loadSchedules, saveSchedule } from './flows/scheduleUtils'
|
||||
import { type Writable, writable } from 'svelte/store'
|
||||
|
||||
export let schema: any
|
||||
export let isFlow: boolean
|
||||
@@ -22,103 +24,33 @@
|
||||
const { primarySchedule, triggersCount } = getContext<TriggerContext>('TriggerContext')
|
||||
|
||||
let scheduleEditor: ScheduleEditor
|
||||
let schedules: Writable<Schedule[] | undefined> = writable(undefined)
|
||||
let initialPrimarySchedule: Writable<ScheduleTrigger | false | undefined> = writable(undefined)
|
||||
|
||||
$: loadSchedules(false) || path
|
||||
|
||||
let schedules: Schedule[] | undefined = undefined
|
||||
let initialPrimarySchedule: ScheduleTrigger | false | undefined = undefined
|
||||
async function loadSchedules(forceRefresh: boolean) {
|
||||
if (!path || path == '') {
|
||||
schedules = []
|
||||
if ($primarySchedule == undefined) {
|
||||
$primarySchedule = false
|
||||
}
|
||||
initialPrimarySchedule = structuredClone($primarySchedule)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const allSchedules = await ScheduleService.listSchedules({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: path,
|
||||
isFlow
|
||||
})
|
||||
const primary = allSchedules.find((s) => s.path == path)
|
||||
let remotePrimarySchedule = primary
|
||||
? {
|
||||
summary: primary.summary,
|
||||
args: primary.args ?? {},
|
||||
cron: primary.schedule,
|
||||
timezone: primary.timezone,
|
||||
enabled: primary.enabled
|
||||
}
|
||||
: false
|
||||
if ($primarySchedule == undefined || forceRefresh) {
|
||||
$primarySchedule = remotePrimarySchedule
|
||||
}
|
||||
initialPrimarySchedule = structuredClone(remotePrimarySchedule)
|
||||
|
||||
$triggersCount = {
|
||||
...($triggersCount ?? {}),
|
||||
schedule_count: allSchedules.length,
|
||||
primary_schedule: $primarySchedule ? { schedule: $primarySchedule.cron } : undefined
|
||||
}
|
||||
schedules = allSchedules.filter((s) => s.path != path)
|
||||
} catch (e) {
|
||||
console.error('impossible to load schedules', e)
|
||||
}
|
||||
async function updateSchedules(forceRefresh: boolean) {
|
||||
loadSchedules(
|
||||
forceRefresh,
|
||||
path,
|
||||
isFlow,
|
||||
schedules,
|
||||
primarySchedule,
|
||||
initialPrimarySchedule,
|
||||
$workspaceStore ?? '',
|
||||
triggersCount
|
||||
)
|
||||
}
|
||||
|
||||
$: updateSchedules(false) || path
|
||||
|
||||
async function save() {
|
||||
const scheduleExists =
|
||||
path != '' &&
|
||||
!newItem &&
|
||||
(await ScheduleService.existsSchedule({
|
||||
workspace: $workspaceStore!,
|
||||
path
|
||||
}))
|
||||
if (scheduleExists) {
|
||||
console.log('primary schedule exists')
|
||||
if ($primarySchedule) {
|
||||
await ScheduleService.updateSchedule({
|
||||
workspace: $workspaceStore!,
|
||||
path,
|
||||
requestBody: {
|
||||
summary: $primarySchedule.summary,
|
||||
args: $primarySchedule.args,
|
||||
schedule: $primarySchedule.cron,
|
||||
timezone: $primarySchedule.timezone
|
||||
}
|
||||
})
|
||||
sendUserToast(`Primary schedule updated`)
|
||||
} else {
|
||||
await ScheduleService.deleteSchedule({ workspace: $workspaceStore!, path })
|
||||
sendUserToast(`Primary schedule deleted`)
|
||||
}
|
||||
} else {
|
||||
if ($primarySchedule) {
|
||||
await ScheduleService.createSchedule({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
path,
|
||||
script_path: path,
|
||||
is_flow: isFlow,
|
||||
summary: $primarySchedule.summary,
|
||||
args: $primarySchedule.args,
|
||||
schedule: $primarySchedule.cron,
|
||||
timezone: $primarySchedule.timezone,
|
||||
enabled: $primarySchedule.enabled
|
||||
}
|
||||
})
|
||||
sendUserToast(`Primary schedule created`)
|
||||
}
|
||||
}
|
||||
loadSchedules(true)
|
||||
await saveSchedule(path, newItem, $workspaceStore ?? '', primarySchedule, isFlow)
|
||||
updateSchedules(true)
|
||||
}
|
||||
</script>
|
||||
|
||||
<ScheduleEditor
|
||||
on:update={() => {
|
||||
loadSchedules(true)
|
||||
updateSchedules(true)
|
||||
}}
|
||||
bind:this={scheduleEditor}
|
||||
/>
|
||||
@@ -204,7 +136,7 @@
|
||||
<p class="text-xs text-tertiary mt-10">Define a schedule frequency first</p>
|
||||
{/if}
|
||||
|
||||
{#if initialPrimarySchedule != false}
|
||||
{#if $initialPrimarySchedule != false}
|
||||
<div class="flex">
|
||||
<Button size="xs" color="light" on:click={() => scheduleEditor?.openEdit(path, isFlow)}
|
||||
>Advanced</Button
|
||||
@@ -213,29 +145,31 @@
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex flex-row gap-4 mt-2">
|
||||
<Button
|
||||
on:click={() => {
|
||||
$primarySchedule = {
|
||||
summary: '',
|
||||
args: {},
|
||||
cron: '0 0 */1 * * *',
|
||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
enabled: true
|
||||
}
|
||||
$triggersCount = {
|
||||
...($triggersCount ?? {}),
|
||||
schedule_count: ($triggersCount?.schedule_count ?? 0) + 1,
|
||||
primary_schedule: { schedule: $primarySchedule.cron }
|
||||
}
|
||||
}}
|
||||
variant="contained"
|
||||
color="dark"
|
||||
size="md"
|
||||
startIcon={{ icon: Calendar }}
|
||||
>
|
||||
Set Primary Schedule
|
||||
</Button>
|
||||
{#if initialPrimarySchedule != undefined && initialPrimarySchedule != false && !newItem}
|
||||
<div class="flex items-center">
|
||||
<Button
|
||||
on:click={() => {
|
||||
$primarySchedule = {
|
||||
summary: '',
|
||||
args: {},
|
||||
cron: '0 0 */1 * * *',
|
||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
enabled: true
|
||||
}
|
||||
$triggersCount = {
|
||||
...($triggersCount ?? {}),
|
||||
schedule_count: ($triggersCount?.schedule_count ?? 0) + 1,
|
||||
primary_schedule: { schedule: $primarySchedule.cron }
|
||||
}
|
||||
}}
|
||||
variant="contained"
|
||||
color="dark"
|
||||
size="md"
|
||||
startIcon={{ icon: Calendar }}
|
||||
>
|
||||
Set Primary Schedule
|
||||
</Button>
|
||||
</div>
|
||||
{#if $initialPrimarySchedule != undefined && $initialPrimarySchedule != false && !newItem}
|
||||
<Button on:click={save} color="dark" size="md" startIcon={{ icon: Save }}>
|
||||
Apply changes now
|
||||
</Button>
|
||||
@@ -274,12 +208,12 @@
|
||||
{/if}
|
||||
|
||||
<Label label="Other schedules">
|
||||
{#if schedules}
|
||||
{#if schedules?.length == 0 || schedules == undefined}
|
||||
{#if $schedules}
|
||||
{#if $schedules.length == 0 || $schedules == undefined}
|
||||
<div class="text-xs text-tertiary"> No other schedules </div>
|
||||
{:else}
|
||||
<div class="flex flex-col divide-y">
|
||||
{#each schedules as schedule (schedule.path)}
|
||||
{#each $schedules as schedule (schedule.path)}
|
||||
<div class="grid grid-cols-6 text-xs items-center py-2">
|
||||
<div class="col-span-3 truncate">{schedule.path}</div>
|
||||
<div class="col-span-2 flex flex-row gap-4 flex-nowrap">
|
||||
|
||||
@@ -106,8 +106,9 @@
|
||||
? { schedule_count: 1, primary_schedule: { schedule: savedPrimarySchedule.cron } }
|
||||
: undefined
|
||||
)
|
||||
const simplifiedPoll = writable(false)
|
||||
const selectedTriggerStore = writable<
|
||||
'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets'
|
||||
'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' | 'scheduledPoll'
|
||||
>('webhooks')
|
||||
|
||||
export function setPrimarySchedule(schedule: ScheduleTrigger | undefined | false) {
|
||||
@@ -138,7 +139,8 @@
|
||||
setContext<TriggerContext>('TriggerContext', {
|
||||
selectedTrigger: selectedTriggerStore,
|
||||
primarySchedule: primaryScheduleStore,
|
||||
triggersCount
|
||||
triggersCount,
|
||||
simplifiedPoll
|
||||
})
|
||||
|
||||
const enterpriseLangs = ['bigquery', 'snowflake', 'mssql']
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
export let lightMode: boolean = false
|
||||
export let eeOnly: boolean = false
|
||||
|
||||
export let size: 'sm' | 'xs' = 'sm'
|
||||
export let size: 'sm' | 'xs' | '2xs' = 'sm'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const bothOptions = Boolean(options.left) && Boolean(options.right)
|
||||
@@ -39,7 +39,7 @@
|
||||
class={twMerge(
|
||||
'mr-2 font-medium duration-50 select-none',
|
||||
bothOptions || textDisabled ? (checked ? 'text-disabled' : 'text-primary') : 'text-primary',
|
||||
size === 'xs' ? 'text-xs' : 'text-sm',
|
||||
size === 'xs' ? 'text-xs' : size === '2xs' ? 'text-[0.5rem]' : 'text-sm',
|
||||
textClass
|
||||
)}
|
||||
style={textStyle}
|
||||
@@ -76,6 +76,8 @@
|
||||
: 'peer-checked:bg-nord-950 dark:peer-checked:bg-nord-400',
|
||||
size === 'sm'
|
||||
? 'w-11 h-6 after:top-0.5 after:left-[2px] after:h-5 after:w-5'
|
||||
: size === '2xs'
|
||||
? 'w-5 h-3 after:top-0.5 after:left-[2px] after:h-2 after:w-2'
|
||||
: 'w-7 h-4 after:top-0.5 after:left-[2px] after:h-3 after:w-3'
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -265,7 +265,7 @@ Generate a description for the flow below:
|
||||
</div>
|
||||
{#if elementType === 'textarea'}
|
||||
<div>
|
||||
<div class="flex flex-row-reverse text-2xs text-tertiary -mt-4">GH Markdown</div>
|
||||
<div class="flex flex-row-reverse !text-3xs text-tertiary -mt-4">GH Markdown</div>
|
||||
<textarea
|
||||
bind:this={el}
|
||||
bind:value={content}
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
| 'schedules'
|
||||
| 'cli'
|
||||
| 'routes'
|
||||
| 'websockets' = 'webhooks'
|
||||
| 'websockets'
|
||||
| 'scheduledPoll' = 'webhooks'
|
||||
export let flow_json: any | undefined = undefined
|
||||
export let simplfiedPoll: boolean = false
|
||||
|
||||
export let isOperator: boolean = false
|
||||
|
||||
@@ -45,7 +47,7 @@
|
||||
<slot name="script" />
|
||||
</TabContent>
|
||||
<TabContent value="triggers" class="h-full pt-2">
|
||||
<DetailPageTriggerPanel bind:triggerSelected>
|
||||
<DetailPageTriggerPanel {simplfiedPoll} bind:triggerSelected>
|
||||
<slot slot="webhooks" name="webhooks" />
|
||||
<slot slot="routes" name="routes" />
|
||||
<slot slot="websockets" name="websockets" />
|
||||
|
||||
@@ -20,13 +20,15 @@
|
||||
|
||||
const primaryScheduleStore = writable<ScheduleTrigger | undefined | false>(undefined)
|
||||
const selectedTriggerStore = writable<
|
||||
'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets'
|
||||
'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' | 'scheduledPoll'
|
||||
>('webhooks')
|
||||
|
||||
const simplifiedPoll = writable(false)
|
||||
setContext<TriggerContext>('TriggerContext', {
|
||||
selectedTrigger: selectedTriggerStore,
|
||||
primarySchedule: primaryScheduleStore,
|
||||
triggersCount
|
||||
triggersCount,
|
||||
simplifiedPoll
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -41,6 +43,7 @@
|
||||
</Pane>
|
||||
<Pane size={35} minSize={15}>
|
||||
<DetailPageDetailPanel
|
||||
simplfiedPoll={$simplifiedPoll}
|
||||
bind:triggerSelected={$selectedTriggerStore}
|
||||
bind:selected
|
||||
{isOperator}
|
||||
@@ -83,7 +86,10 @@
|
||||
<slot name="save_inputs" />
|
||||
</TabContent>
|
||||
<TabContent value="triggers" class="flex flex-col flex-1 h-full">
|
||||
<DetailPageTriggerPanel bind:triggerSelected={$selectedTriggerStore}>
|
||||
<DetailPageTriggerPanel
|
||||
simplfiedPoll={$simplifiedPoll}
|
||||
bind:triggerSelected={$selectedTriggerStore}
|
||||
>
|
||||
<slot slot="webhooks" name="webhooks" />
|
||||
<slot slot="routes" name="routes" />
|
||||
<slot slot="script" name="script" />
|
||||
|
||||
@@ -10,64 +10,70 @@
|
||||
| 'schedules'
|
||||
| 'cli'
|
||||
| 'routes'
|
||||
| 'websockets' = 'webhooks'
|
||||
| 'websockets'
|
||||
| 'scheduledPoll' = 'webhooks'
|
||||
export let simplfiedPoll: boolean = false
|
||||
</script>
|
||||
|
||||
<HighlightTheme />
|
||||
|
||||
<Tabs bind:selected={triggerSelected}>
|
||||
<Tab value="webhooks">
|
||||
<span class="flex flex-row gap-2 items-center text-xs">
|
||||
<Webhook size={12} />
|
||||
Webhooks
|
||||
</span>
|
||||
</Tab>
|
||||
<Tab value="schedules">
|
||||
<span class="flex flex-row gap-2 items-center text-xs">
|
||||
<CalendarCheck2 size={12} />
|
||||
Schedules
|
||||
</span>
|
||||
</Tab>
|
||||
<Tab value="routes">
|
||||
<span class="flex flex-row gap-2 items-center text-xs">
|
||||
<Route size={12} />
|
||||
HTTP
|
||||
</span>
|
||||
</Tab>
|
||||
<Tab value="websockets">
|
||||
<span class="flex flex-row gap-2 items-center text-xs">
|
||||
<Unplug size={12} />
|
||||
Websockets
|
||||
</span>
|
||||
</Tab>
|
||||
<Tab value="emails">
|
||||
<span class="flex flex-row gap-2 items-center text-xs">
|
||||
<MailIcon size={12} />
|
||||
Email
|
||||
</span>
|
||||
</Tab>
|
||||
<Tab value="cli">
|
||||
<span class="flex flex-row gap-2 items-center text-xs">
|
||||
<Terminal size={12} />
|
||||
CLI
|
||||
</span>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
{#if !simplfiedPoll}
|
||||
<Tabs bind:selected={triggerSelected}>
|
||||
<Tab value="webhooks">
|
||||
<span class="flex flex-row gap-2 items-center text-xs">
|
||||
<Webhook size={12} />
|
||||
Webhooks
|
||||
</span>
|
||||
</Tab>
|
||||
<Tab value="schedules">
|
||||
<span class="flex flex-row gap-2 items-center text-xs">
|
||||
<CalendarCheck2 size={12} />
|
||||
Schedules
|
||||
</span>
|
||||
</Tab>
|
||||
<Tab value="routes">
|
||||
<span class="flex flex-row gap-2 items-center text-xs">
|
||||
<Route size={12} />
|
||||
HTTP
|
||||
</span>
|
||||
</Tab>
|
||||
<Tab value="websockets">
|
||||
<span class="flex flex-row gap-2 items-center text-xs">
|
||||
<Unplug size={12} />
|
||||
Websockets
|
||||
</span>
|
||||
</Tab>
|
||||
<Tab value="emails">
|
||||
<span class="flex flex-row gap-2 items-center text-xs">
|
||||
<MailIcon size={12} />
|
||||
Email
|
||||
</span>
|
||||
</Tab>
|
||||
<Tab value="cli">
|
||||
<span class="flex flex-row gap-2 items-center text-xs">
|
||||
<Terminal size={12} />
|
||||
CLI
|
||||
</span>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<div class="h-[calc(100%-32px)]">
|
||||
<div class="h-full overflow-auto">
|
||||
{#if triggerSelected === 'webhooks'}
|
||||
<slot name="webhooks" />
|
||||
{:else if triggerSelected === 'routes'}
|
||||
<slot name="routes" />
|
||||
{:else if triggerSelected === 'emails'}
|
||||
<slot name="emails" />
|
||||
{:else if triggerSelected === 'schedules'}
|
||||
<slot name="schedules" />
|
||||
{:else if triggerSelected === 'websockets'}
|
||||
<slot name="websockets" />
|
||||
{:else if triggerSelected === 'cli'}
|
||||
<slot name="cli" />
|
||||
{/if}
|
||||
<div class="h-[calc(100%-32px)]">
|
||||
<div class="h-full overflow-auto">
|
||||
{#if triggerSelected === 'webhooks'}
|
||||
<slot name="webhooks" />
|
||||
{:else if triggerSelected === 'routes'}
|
||||
<slot name="routes" />
|
||||
{:else if triggerSelected === 'emails'}
|
||||
<slot name="emails" />
|
||||
{:else if triggerSelected === 'schedules'}
|
||||
<slot name="schedules" />
|
||||
{:else if triggerSelected === 'websockets'}
|
||||
<slot name="websockets" />
|
||||
{:else if triggerSelected === 'cli'}
|
||||
<slot name="cli" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<slot name="schedules" />
|
||||
{/if}
|
||||
|
||||
@@ -12,15 +12,16 @@
|
||||
|
||||
let flow: Flow | undefined = undefined
|
||||
|
||||
const selectedTriggerStore = writable<'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes'>(
|
||||
'webhooks'
|
||||
)
|
||||
const selectedTriggerStore = writable<
|
||||
'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'scheduledPoll'
|
||||
>('webhooks')
|
||||
const primaryScheduleStore = writable<ScheduleTrigger | undefined | false>(undefined)
|
||||
const triggersCount = writable<TriggersCount | undefined>(undefined)
|
||||
setContext<TriggerContext>('TriggerContext', {
|
||||
primarySchedule: primaryScheduleStore,
|
||||
selectedTrigger: selectedTriggerStore,
|
||||
triggersCount: triggersCount
|
||||
triggersCount: triggersCount,
|
||||
simplifiedPoll: writable(false)
|
||||
})
|
||||
|
||||
async function loadFlow(path: string) {
|
||||
|
||||
@@ -45,11 +45,12 @@ export async function pickScript(
|
||||
path: string,
|
||||
summary: string,
|
||||
id: string,
|
||||
hash?: string
|
||||
hash?: string,
|
||||
kind?: string
|
||||
): Promise<[FlowModule & { value: PathScript }, FlowModuleState]> {
|
||||
const flowModule: FlowModule & { value: PathScript } = {
|
||||
id,
|
||||
value: { type: 'script', path, hash, input_transforms: {} },
|
||||
value: { type: 'script', path, hash, input_transforms: {}, is_trigger: kind === 'trigger' },
|
||||
summary
|
||||
}
|
||||
|
||||
@@ -82,7 +83,13 @@ export async function createInlineScriptModule(
|
||||
const flowModule: FlowModule = {
|
||||
id,
|
||||
summary,
|
||||
value: { type: 'rawscript', content: code, language, input_transforms: {} }
|
||||
value: {
|
||||
type: 'rawscript',
|
||||
content: code,
|
||||
language,
|
||||
input_transforms: {},
|
||||
is_trigger: kind === 'trigger'
|
||||
}
|
||||
}
|
||||
|
||||
return [flowModule, await loadFlowModuleState(flowModule)]
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
export let concurrency: boolean = false
|
||||
export let retries: number | undefined = undefined
|
||||
export let warningMessage: string | undefined = undefined
|
||||
export let isTrigger: boolean = false
|
||||
|
||||
const { flowInputsStore } = getContext<{ flowInputsStore: Writable<FlowInput | undefined> }>(
|
||||
'FlowGraphContext'
|
||||
@@ -131,7 +132,7 @@
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class={classNames(
|
||||
'w-full module flex rounded-sm cursor-pointer',
|
||||
'w-full module flex rounded-sm cursor-pointer max-w-full',
|
||||
selected ? 'outline outline-offset-0 outline-2 outline-slate-500 dark:outline-gray-400' : '',
|
||||
'flex relative',
|
||||
$copilotCurrentStepStore === id ? 'z-[901]' : ''
|
||||
@@ -139,7 +140,7 @@
|
||||
style="width: 275px; height: 34px; background-color: {bgColor};"
|
||||
on:mouseenter={() => (hover = true)}
|
||||
on:mouseleave={() => (hover = false)}
|
||||
on:click
|
||||
on:click|preventDefault|stopPropagation
|
||||
>
|
||||
<div class="absolute text-sm right-12 -bottom-3 flex flex-row gap-1 z-10">
|
||||
{#if retry}
|
||||
@@ -185,7 +186,11 @@
|
||||
>
|
||||
<Square size={12} />
|
||||
</div>
|
||||
<svelte:fragment slot="text">Early stop/break</svelte:fragment>
|
||||
<svelte:fragment slot="text"
|
||||
>{isTrigger
|
||||
? 'Stop early if there are no new events'
|
||||
: 'Early stop/break'}</svelte:fragment
|
||||
>
|
||||
</Popover>
|
||||
{/if}
|
||||
{#if skip}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
import { tutorialInProgress } from '$lib/tutorialUtils'
|
||||
import FlowGraphV2 from '$lib/components/graph/FlowGraphV2.svelte'
|
||||
import { replaceId } from '../flowStore'
|
||||
import { setScheduledPollSchedule, type TriggerContext } from '$lib/components/triggers'
|
||||
|
||||
export let modules: FlowModule[] | undefined
|
||||
export let sidebarSize: number | undefined = undefined
|
||||
@@ -47,7 +48,7 @@
|
||||
|
||||
const { selectedId, moving, history, flowStateStore, flowStore, flowInputsStore, pathStore } =
|
||||
getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
const { primarySchedule, triggersCount } = getContext<TriggerContext>('TriggerContext')
|
||||
async function insertNewModuleAtIndex(
|
||||
modules: FlowModule[],
|
||||
index: number,
|
||||
@@ -74,10 +75,17 @@
|
||||
push(history, $flowStore)
|
||||
var module = emptyModule($flowStateStore, $flowStore, kind == 'flow')
|
||||
var state = emptyFlowModuleState()
|
||||
$flowStateStore[module.id] = state
|
||||
if (wsFlow) {
|
||||
;[module, state] = await pickFlow(wsFlow.path, wsFlow.summary, module.id)
|
||||
} else if (wsScript) {
|
||||
;[module, state] = await pickScript(wsScript.path, wsScript.summary, module.id, wsScript.hash)
|
||||
;[module, state] = await pickScript(
|
||||
wsScript.path,
|
||||
wsScript.summary,
|
||||
module.id,
|
||||
wsScript.hash,
|
||||
kind
|
||||
)
|
||||
} else if (kind == 'forloop') {
|
||||
;[module, state] = await createLoop(
|
||||
module.id,
|
||||
@@ -89,10 +97,7 @@
|
||||
;[module, state] = await createBranches(module.id)
|
||||
} else if (kind == 'branchall') {
|
||||
;[module, state] = await createBranchAll(module.id)
|
||||
}
|
||||
$flowStateStore[module.id] = state
|
||||
|
||||
if (inlineScript) {
|
||||
} else if (inlineScript) {
|
||||
const { language, kind, subkind } = inlineScript
|
||||
;[module, state] = await createInlineScriptModule(
|
||||
language,
|
||||
@@ -108,6 +113,7 @@
|
||||
module.summary = 'Approval'
|
||||
}
|
||||
}
|
||||
$flowStateStore[module.id] = state
|
||||
|
||||
if (kind == 'approval') {
|
||||
module.suspend = { required_events: 1, timeout: 1800 }
|
||||
@@ -116,6 +122,9 @@
|
||||
expr: '!result || (Array.isArray(result) && result.length == 0)',
|
||||
skip_if_stopped: true
|
||||
}
|
||||
} else if (kind == 'end') {
|
||||
module.summary = 'Terminate flow'
|
||||
module.stop_after_if = { skip_if_stopped: false, expr: 'true' }
|
||||
}
|
||||
|
||||
if (!modules) return [module]
|
||||
@@ -403,6 +412,7 @@
|
||||
undefined
|
||||
)
|
||||
setExpr(detail.modules[index + 1], `results.${id}`)
|
||||
setScheduledPollSchedule(primarySchedule, triggersCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import { Cross, Zap } from 'lucide-svelte'
|
||||
import { Cross } from 'lucide-svelte'
|
||||
import StepGenQuick from '$lib/components/copilot/StepGenQuick.svelte'
|
||||
import FlowInputsQuick from '../content/FlowInputsQuick.svelte'
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
@@ -11,6 +11,7 @@
|
||||
import TopLevelNode from '../pickers/TopLevelNode.svelte'
|
||||
import PopupV2 from '$lib/components/common/popup/PopupV2.svelte'
|
||||
import { flip, offset } from 'svelte-floating-ui/dom'
|
||||
import { SchedulePollIcon } from '$lib/components/icons'
|
||||
|
||||
// import type { Writable } from 'svelte/store'
|
||||
|
||||
@@ -22,6 +23,7 @@
|
||||
export let disableAi = false
|
||||
export let kind: 'script' | 'trigger' | 'preprocessor' | 'failure' = 'script'
|
||||
export let allowTrigger = true
|
||||
export let iconSize = 12
|
||||
|
||||
type Alignment = 'start' | 'end' | 'center'
|
||||
type Side = 'top' | 'bottom'
|
||||
@@ -70,22 +72,22 @@ shouldUsePortal={true} -->
|
||||
id={`flow-editor-add-step-${index}`}
|
||||
type="button"
|
||||
class={twMerge(
|
||||
'w-5 h-5 flex items-center justify-center',
|
||||
'outline-[1px] outline dark:outline-gray-500 outline-gray-300',
|
||||
'text-secondary',
|
||||
'bg-surface focus:outline-none hover:bg-surface-hover rounded'
|
||||
'w-[17.5px] h-[17.5px] flex items-center justify-center outline-[1px] outline dark:outline-gray-500 outline-gray-300 text-secondary bg-surface focus:outline-none hover:bg-surface-hover rounded',
|
||||
$$props.class
|
||||
)}
|
||||
on:pointerdown|preventDefault|stopPropagation={pointerdown}
|
||||
on:pointerdown|preventDefault|stopPropagation={() => {
|
||||
dispatch('open')
|
||||
pointerdown()
|
||||
}}
|
||||
on:pointerup={pointerup}
|
||||
>
|
||||
{#if kind === 'trigger'}
|
||||
<Zap size={12} />
|
||||
<SchedulePollIcon size={14} />
|
||||
{:else}
|
||||
<Cross size={12} />
|
||||
<Cross size={iconSize} />
|
||||
{/if}
|
||||
</button>
|
||||
</svelte:fragment>
|
||||
<!-- FOO -->
|
||||
<div
|
||||
id="flow-editor-insert-module"
|
||||
class="flex flex-col h-[400px] {small ? 'w-[450px]' : 'w-[650px]'} pt-1 pr-1 pl-1 gap-1.5"
|
||||
@@ -109,7 +111,7 @@ shouldUsePortal={true} -->
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row grow min-h-0">
|
||||
{#if kind === 'script' || kind == 'trigger'}
|
||||
{#if kind === 'script'}
|
||||
<div class="flex-none flex flex-col text-xs text-primary">
|
||||
<TopLevelNode
|
||||
label="Action"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import { msToSec } from '$lib/utils'
|
||||
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
|
||||
import FlowJobsMenu from './FlowJobsMenu.svelte'
|
||||
import { isTriggerStep } from '$lib/components/graph/graphBuilder'
|
||||
|
||||
export let mod: FlowModule
|
||||
export let insertable: boolean
|
||||
@@ -19,6 +20,7 @@
|
||||
export let modules: FlowModule[]
|
||||
export let moving: string | undefined = undefined
|
||||
export let duration_ms: number | undefined = undefined
|
||||
export let isTrigger: boolean = false
|
||||
|
||||
export let retries: number | undefined = undefined
|
||||
export let flowJobs:
|
||||
@@ -173,6 +175,7 @@
|
||||
(mod.value.type === 'rawscript'
|
||||
? `Inline ${prettyLanguage(mod.value.language)}`
|
||||
: 'To be defined')}
|
||||
isTrigger={isTriggerStep(mod)}
|
||||
>
|
||||
<div slot="icon">
|
||||
{#if mod.value.type === 'rawscript'}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { Badge } from '$lib/components/common'
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
|
||||
import { getContext } from 'svelte'
|
||||
import type { FlowCopilotContext } from '$lib/components/copilot/flow'
|
||||
import VirtualItemWrapper from './VirtualItemWrapper.svelte'
|
||||
|
||||
export let label: string | undefined = undefined
|
||||
export let bgColor: string = ''
|
||||
@@ -15,41 +15,18 @@
|
||||
export let hideId: boolean = false
|
||||
export let preLabel: string | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
insert: {
|
||||
script?: { path: string; summary: string; hash: string | undefined }
|
||||
detail: 'script' | 'forloop' | 'branchone' | 'branchall' | 'trigger' | 'move'
|
||||
modules: FlowModule[]
|
||||
index: number
|
||||
}
|
||||
select: string
|
||||
}>()
|
||||
|
||||
const { currentStepStore: copilotCurrentStepStore } =
|
||||
getContext<FlowCopilotContext | undefined>('FlowCopilotContext') || {}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class={classNames(
|
||||
'w-full flex relative overflow-hidden rounded-sm',
|
||||
selectable ? 'cursor-pointer' : '',
|
||||
selected ? 'outline outline-offset-1 outline-2 outline-gray-600' : '',
|
||||
label === 'Input' && $copilotCurrentStepStore === 'Input' ? 'z-[901]' : ''
|
||||
)}
|
||||
style="width: 275px; max-height: 34px; background-color: {bgColor} !important;"
|
||||
on:click={() => {
|
||||
if (selectable) {
|
||||
if (id) {
|
||||
dispatch('select', id)
|
||||
} else {
|
||||
dispatch('select', label || label || '')
|
||||
}
|
||||
}
|
||||
}}
|
||||
title={(label ? label + ' ' : '') + (label ?? '')}
|
||||
id={`flow-editor-virtual-${encodeURIComponent(label || label || '')}`}
|
||||
<VirtualItemWrapper
|
||||
{label}
|
||||
{bgColor}
|
||||
{selected}
|
||||
{selectable}
|
||||
{id}
|
||||
onTop={label === 'Input' && $copilotCurrentStepStore === 'Input'}
|
||||
on:select
|
||||
>
|
||||
<div
|
||||
style={borderColor ? `border-color: ${borderColor};` : 'border: 0'}
|
||||
@@ -76,4 +53,4 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</VirtualItemWrapper>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<script lang="ts">
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let label: string | undefined
|
||||
export let selectable: boolean
|
||||
export let selected: boolean
|
||||
export let id: string | undefined
|
||||
export let onTop: boolean = false
|
||||
export let bgColor: string
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
insert: {
|
||||
script?: { path: string; summary: string; hash: string | undefined }
|
||||
detail: 'script' | 'forloop' | 'branchone' | 'branchall' | 'trigger' | 'move'
|
||||
modules: FlowModule[]
|
||||
index: number
|
||||
}
|
||||
select: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class={classNames(
|
||||
'w-full flex relative overflow-hidden rounded-sm',
|
||||
selectable ? 'cursor-pointer' : '',
|
||||
selected ? 'outline outline-offset-1 outline-2 outline-gray-600 dark:outline-gray-400' : '',
|
||||
onTop ? 'z-[901]' : ''
|
||||
)}
|
||||
style="width: 275px; max-height: 34px; background-color: {bgColor} !important;"
|
||||
on:click={() => {
|
||||
if (selectable) {
|
||||
if (id) {
|
||||
dispatch('select', id)
|
||||
} else {
|
||||
dispatch('select', label || label || '')
|
||||
}
|
||||
}
|
||||
}}
|
||||
title={label ? label + ' ' : ''}
|
||||
id={`flow-editor-virtual-${encodeURIComponent(label || label || '')}`}><slot /></div
|
||||
>
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ScheduleService } from '$lib/gen'
|
||||
import { ScheduleService, type Schedule, type TriggersCount } from '$lib/gen'
|
||||
import type { ScheduleTrigger } from '../triggers'
|
||||
import type { Writable } from 'svelte/store'
|
||||
import { get } from 'svelte/store'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
|
||||
// Load the schedule of a flow given its path and the workspace
|
||||
export async function loadFlowSchedule(path: string, workspace: string): Promise<ScheduleTrigger> {
|
||||
@@ -25,3 +28,108 @@ export async function loadFlowSchedule(path: string, workspace: string): Promise
|
||||
args: schedule.args ?? {}
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadSchedules(
|
||||
forceRefresh: boolean,
|
||||
path: string,
|
||||
isFlow: boolean,
|
||||
schedules: Writable<Schedule[] | undefined>,
|
||||
primarySchedule: Writable<ScheduleTrigger | false | undefined>,
|
||||
initialPrimarySchedule: Writable<ScheduleTrigger | false | undefined>,
|
||||
workspace: string,
|
||||
triggersCount: Writable<TriggersCount | undefined>
|
||||
) {
|
||||
console.log('loading schedules for path', path)
|
||||
if (!path || path == '') {
|
||||
schedules.set([])
|
||||
primarySchedule.update((ps) => (ps === undefined ? false : ps))
|
||||
initialPrimarySchedule.set(structuredClone(get(primarySchedule)))
|
||||
return
|
||||
}
|
||||
console.log('loading schedules for path', path)
|
||||
try {
|
||||
const allSchedules = await ScheduleService.listSchedules({
|
||||
workspace,
|
||||
path: path,
|
||||
isFlow
|
||||
})
|
||||
const primary = allSchedules.find((s) => s.path == path)
|
||||
let remotePrimarySchedule: ScheduleTrigger | false | undefined = primary
|
||||
? {
|
||||
summary: primary.summary,
|
||||
args: primary.args ?? {},
|
||||
cron: primary.schedule,
|
||||
timezone: primary.timezone,
|
||||
enabled: primary.enabled
|
||||
}
|
||||
: false
|
||||
primarySchedule.update((ps) => (ps === undefined || forceRefresh ? remotePrimarySchedule : ps))
|
||||
initialPrimarySchedule.set(structuredClone(remotePrimarySchedule))
|
||||
|
||||
triggersCount.update((tc) => {
|
||||
const primary = get(primarySchedule)
|
||||
return {
|
||||
...(tc ?? {}),
|
||||
schedule_count: allSchedules.length,
|
||||
primary_schedule: primary ? { schedule: primary.cron } : undefined
|
||||
}
|
||||
})
|
||||
schedules.set(allSchedules.filter((s) => s.path != path))
|
||||
} catch (e) {
|
||||
console.error('impossible to load schedules', e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveSchedule(
|
||||
path: string,
|
||||
newItem: boolean,
|
||||
workspace: string,
|
||||
primarySchedule: Writable<ScheduleTrigger | false | undefined>,
|
||||
isFlow: boolean
|
||||
) {
|
||||
const scheduleExists =
|
||||
path != '' &&
|
||||
!newItem &&
|
||||
(await ScheduleService.existsSchedule({
|
||||
workspace,
|
||||
path
|
||||
}))
|
||||
if (scheduleExists) {
|
||||
console.log('primary schedule exists')
|
||||
const primary = get(primarySchedule)
|
||||
if (primary) {
|
||||
await ScheduleService.updateSchedule({
|
||||
workspace,
|
||||
path,
|
||||
requestBody: {
|
||||
summary: primary.summary,
|
||||
args: primary.args,
|
||||
schedule: primary.cron,
|
||||
timezone: primary.timezone
|
||||
}
|
||||
})
|
||||
sendUserToast(`Primary schedule updated`)
|
||||
} else {
|
||||
await ScheduleService.deleteSchedule({ workspace, path })
|
||||
sendUserToast(`Primary schedule deleted`)
|
||||
}
|
||||
} else {
|
||||
const primary = get(primarySchedule)
|
||||
if (primary) {
|
||||
await ScheduleService.createSchedule({
|
||||
workspace,
|
||||
requestBody: {
|
||||
path,
|
||||
script_path: path,
|
||||
is_flow: isFlow,
|
||||
summary: primary.summary,
|
||||
args: primary.args,
|
||||
schedule: primary.cron,
|
||||
timezone: primary.timezone,
|
||||
enabled: primary.enabled
|
||||
}
|
||||
})
|
||||
sendUserToast(`Primary schedule created`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { type FlowModule } from '../../gen'
|
||||
import { NODE, type GraphModuleState } from '.'
|
||||
import { createEventDispatcher, onMount, setContext } from 'svelte'
|
||||
import { createEventDispatcher, getContext, onDestroy, onMount, setContext } from 'svelte'
|
||||
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
import { get, writable, type Writable } from 'svelte/store'
|
||||
import '@xyflow/svelte/dist/style.css'
|
||||
import type { FlowInput } from '../flows/types'
|
||||
import {
|
||||
@@ -15,7 +15,7 @@
|
||||
ControlButton,
|
||||
type Viewport
|
||||
} from '@xyflow/svelte'
|
||||
import graphBuilder from './graphBuilder'
|
||||
import { graphBuilder, isTriggerStep, type SimplifiableFlow } from './graphBuilder'
|
||||
import ModuleNode from './renderers/nodes/ModuleNode.svelte'
|
||||
import InputNode from './renderers/nodes/InputNode.svelte'
|
||||
import BranchAllStart from './renderers/nodes/BranchAllStart.svelte'
|
||||
@@ -38,6 +38,8 @@
|
||||
import Button from '../common/button/Button.svelte'
|
||||
import FlowYamlEditor from '../flows/header/FlowYamlEditor.svelte'
|
||||
import BranchOneEndNode from './renderers/nodes/branchOneEndNode.svelte'
|
||||
import type { TriggerContext } from '../triggers'
|
||||
|
||||
export let success: boolean | undefined = undefined
|
||||
export let modules: FlowModule[] | undefined = []
|
||||
export let failureModule: FlowModule | undefined = undefined
|
||||
@@ -72,11 +74,46 @@
|
||||
useDataflow: Writable<boolean | undefined>
|
||||
}>('FlowGraphContext', { selectedId, flowInputsStore, useDataflow })
|
||||
|
||||
const triggerContext = getContext<TriggerContext>('TriggerContext')
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let fullWidth = 0
|
||||
let width = 0
|
||||
|
||||
export let allowSimplifiedPoll: boolean = true
|
||||
|
||||
let simplifiableFlow: SimplifiableFlow | undefined = undefined
|
||||
|
||||
if (triggerContext && allowSimplifiedPoll) {
|
||||
if (isSimplifiable(modules)) {
|
||||
triggerContext?.simplifiedPoll?.set(true)
|
||||
}
|
||||
triggerContext?.simplifiedPoll.subscribe((value) => {
|
||||
computeSimplifiableFlow(modules ?? [], value ?? false)
|
||||
})
|
||||
}
|
||||
|
||||
function computeSimplifiableFlow(modules: FlowModule[], simplifiedFlow: boolean) {
|
||||
const isSimplif = isSimplifiable(modules)
|
||||
simplifiableFlow = isSimplif ? { simplifiedFlow } : undefined
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
if (isSimplifiable(modules)) {
|
||||
triggerContext?.simplifiedPoll?.set(undefined)
|
||||
}
|
||||
})
|
||||
|
||||
function onModulesChange(modules: FlowModule[]) {
|
||||
computeSimplifiableFlow(
|
||||
modules,
|
||||
triggerContext?.simplifiedPoll ? get(triggerContext.simplifiedPoll) ?? false : false
|
||||
)
|
||||
}
|
||||
|
||||
$: allowSimplifiedPoll && onModulesChange(modules ?? [])
|
||||
|
||||
function layoutNodes(nodes: Node[]): Node[] {
|
||||
let seenId: string[] = []
|
||||
for (const n of nodes) {
|
||||
@@ -129,6 +166,44 @@
|
||||
return newNodes
|
||||
}
|
||||
|
||||
let eventHandler = {
|
||||
deleteBranch: (detail, label) => {
|
||||
$selectedId = label
|
||||
dispatch('deleteBranch', detail)
|
||||
},
|
||||
insert: (detail) => {
|
||||
dispatch('insert', detail)
|
||||
},
|
||||
select: (modId) => {
|
||||
if (!notSelectable) {
|
||||
if ($selectedId != modId) {
|
||||
$selectedId = modId
|
||||
}
|
||||
dispatch('select', modId)
|
||||
}
|
||||
},
|
||||
changeId: (detail) => {
|
||||
dispatch('changeId', detail)
|
||||
},
|
||||
delete: (detail, label) => {
|
||||
$selectedId = label
|
||||
|
||||
dispatch('delete', detail)
|
||||
},
|
||||
newBranch: (module) => {
|
||||
dispatch('newBranch', { module })
|
||||
},
|
||||
move: (module, modules) => {
|
||||
dispatch('move', { module, modules })
|
||||
},
|
||||
selectedIteration: (detail, moduleId) => {
|
||||
dispatch('selectedIteration', { ...detail, moduleId: moduleId })
|
||||
},
|
||||
simplifyFlow: (detail) => {
|
||||
triggerContext?.simplifiedPoll.set(detail)
|
||||
}
|
||||
}
|
||||
|
||||
$: graph = graphBuilder(
|
||||
modules,
|
||||
{
|
||||
@@ -141,50 +216,13 @@
|
||||
},
|
||||
failureModule,
|
||||
preprocessorModule,
|
||||
{
|
||||
deleteBranch: (detail, label) => {
|
||||
$selectedId = label
|
||||
|
||||
dispatch('deleteBranch', detail)
|
||||
},
|
||||
insert: (detail) => {
|
||||
dispatch('insert', detail)
|
||||
},
|
||||
select: (modId) => {
|
||||
if (!notSelectable) {
|
||||
if ($selectedId != modId) {
|
||||
$selectedId = modId
|
||||
}
|
||||
dispatch('select', modId)
|
||||
}
|
||||
},
|
||||
changeId: (detail) => {
|
||||
dispatch('changeId', detail)
|
||||
},
|
||||
delete: (detail, label) => {
|
||||
$selectedId = label
|
||||
|
||||
dispatch('delete', detail)
|
||||
},
|
||||
newBranch: (module) => {
|
||||
dispatch('newBranch', { module })
|
||||
},
|
||||
move: (module, modules) => {
|
||||
dispatch('move', { module, modules })
|
||||
},
|
||||
selectedIteration: (detail, moduleId) => {
|
||||
dispatch('selectedIteration', { ...detail, moduleId: moduleId })
|
||||
}
|
||||
},
|
||||
eventHandler,
|
||||
success,
|
||||
$useDataflow,
|
||||
$selectedId,
|
||||
moving,
|
||||
triggerNode
|
||||
? {
|
||||
path
|
||||
}
|
||||
: undefined
|
||||
simplifiableFlow,
|
||||
triggerNode ? path : undefined
|
||||
)
|
||||
|
||||
const nodes = writable<Node[]>([])
|
||||
@@ -192,18 +230,93 @@
|
||||
|
||||
let height = 0
|
||||
|
||||
// function removeInputNode(nodes, edges, id) {
|
||||
// const inputNode = nodes.find((node) => node.id === id)
|
||||
// if (!inputNode) return { nodes, edges }
|
||||
|
||||
// // Find edges connected to the input node
|
||||
// const connectedEdges = edges.filter((edge) => edge.source === id || edge.target === id)
|
||||
|
||||
// // Remove the input node
|
||||
// let updatedNodes = nodes.filter((node) => node.id !== id)
|
||||
|
||||
// // Remove edges connected to the input node
|
||||
// let updatedEdges = edges.filter((edge) => edge.source !== id && edge.target !== id)
|
||||
|
||||
// // Create new edges from the input node's parent to its children
|
||||
// const inputEdges = connectedEdges.filter((edge) => edge.target === id)
|
||||
// const outputEdges = connectedEdges.filter((edge) => edge.source === id)
|
||||
|
||||
// inputEdges.forEach((inputEdge) => {
|
||||
// outputEdges.forEach((outputEdge) => {
|
||||
// const newEdge = {
|
||||
// id: `edge:${inputEdge.source}->${outputEdge.target}`,
|
||||
// source: inputEdge.source,
|
||||
// target: outputEdge.target,
|
||||
// type: 'empty',
|
||||
// data: {
|
||||
// ...outputEdge.data,
|
||||
// sourceId: inputEdge.source,
|
||||
// targetId: outputEdge.target
|
||||
// }
|
||||
// }
|
||||
// updatedEdges.push(newEdge)
|
||||
// })
|
||||
// })
|
||||
|
||||
// // Update parent ids of the nodes
|
||||
// updatedNodes = updatedNodes.map((node) => {
|
||||
// if (node.data && node.data.parentIds && node.data.parentIds.includes(id)) {
|
||||
// const updatedParentIds = node.data.parentIds.filter((parentId) => parentId !== id)
|
||||
// if (inputNode.data && inputNode.data.parentIds) {
|
||||
// updatedParentIds.push(...inputNode.data.parentIds)
|
||||
// }
|
||||
// return {
|
||||
// ...node,
|
||||
// data: {
|
||||
// ...node.data,
|
||||
// parentIds: [...new Set(updatedParentIds)] // Remove duplicates
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return node
|
||||
// })
|
||||
|
||||
// return { nodes: updatedNodes, edges: updatedEdges }
|
||||
// }
|
||||
|
||||
// function processGraph(graph, simplifiable) {
|
||||
// let newGraph = { nodes: graph.nodes, edges: graph.edges }
|
||||
// newGraph = removeInputNode(newGraph.nodes, newGraph.edges, 'Input')
|
||||
// newGraph = removeInputNode(newGraph.nodes, newGraph.edges, simplifiable.forLoopNode.id)
|
||||
// newGraph = removeInputNode(newGraph.nodes, newGraph.edges, simplifiable.triggerNode.id)
|
||||
// return newGraph
|
||||
// }
|
||||
|
||||
function isSimplifiable(modules: FlowModule[] | undefined): boolean {
|
||||
if (!modules || modules?.length !== 2) {
|
||||
return false
|
||||
}
|
||||
if (isTriggerStep(modules?.[0])) {
|
||||
let secondValue = modules?.[1].value
|
||||
return secondValue.type == 'forloopflow'
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function updateStores() {
|
||||
if (graph.error) {
|
||||
return
|
||||
}
|
||||
let newGraph = graph
|
||||
|
||||
$nodes = layoutNodes(graph?.nodes)
|
||||
$edges = graph.edges
|
||||
|
||||
$nodes = layoutNodes(newGraph.nodes)
|
||||
$edges = newGraph.edges
|
||||
height = Math.max(...$nodes.map((n) => n.position.y + NODE.height + 40), minHeight)
|
||||
}
|
||||
|
||||
$: graph && updateStores()
|
||||
$: (graph || allowSimplifiedPoll) && updateStores()
|
||||
|
||||
const nodeTypes = {
|
||||
input2: InputNode,
|
||||
|
||||
@@ -14,9 +14,19 @@ export type GraphEventHandlers = {
|
||||
move: (module: FlowModule, modules: FlowModule[]) => void
|
||||
selectedIteration: (detail, moduleId: string) => void
|
||||
changeId: (newId: string) => void
|
||||
simplifyFlow: (detail: boolean) => void
|
||||
}
|
||||
|
||||
export default function graphBuilder(
|
||||
export type SimplifiableFlow = { simplifiedFlow: boolean }
|
||||
|
||||
export function isTriggerStep(module: FlowModule | undefined): boolean {
|
||||
return (
|
||||
module?.value != undefined &&
|
||||
(module.value.type === 'script' || module.value.type === 'rawscript') &&
|
||||
module.value.is_trigger === true
|
||||
)
|
||||
}
|
||||
export function graphBuilder(
|
||||
modules: FlowModule[] | undefined,
|
||||
extra: Record<string, any>,
|
||||
failureModule: FlowModule | undefined,
|
||||
@@ -26,9 +36,12 @@ export default function graphBuilder(
|
||||
useDataflow: boolean | undefined,
|
||||
selectedId: string | undefined,
|
||||
moving: string | undefined,
|
||||
triggerProps?: {
|
||||
path?: string
|
||||
}
|
||||
simplifiableFlow: SimplifiableFlow | undefined,
|
||||
flowPathForTriggerNode: string | undefined
|
||||
// triggerProps?: {
|
||||
// path?: string
|
||||
// flowIsSimplifiable?: boolean
|
||||
// }
|
||||
): {
|
||||
nodes: Node[]
|
||||
edges: Edge[]
|
||||
@@ -102,6 +115,7 @@ export default function graphBuilder(
|
||||
sourceId: string,
|
||||
targetId: string,
|
||||
options?: {
|
||||
disableInsert?: boolean
|
||||
customId?: string
|
||||
type?: string
|
||||
subModules?: FlowModule[]
|
||||
@@ -129,17 +143,18 @@ export default function graphBuilder(
|
||||
target: targetId,
|
||||
type: options?.type ?? 'edge',
|
||||
data: {
|
||||
insertable: extra.insertable,
|
||||
modules: options?.subModules ?? modules,
|
||||
sourceId,
|
||||
targetId,
|
||||
moving,
|
||||
eventHandlers,
|
||||
simplifiedTriggerView: simplifiableFlow?.simplifiedFlow,
|
||||
disableMoveIds: options?.disableMoveIds,
|
||||
enableTrigger: sourceId === 'Input',
|
||||
// If the index is -1, it means that the target module is not in the modules array, so we set it to the length of the array
|
||||
index: index >= 0 ? index : mods?.length ?? 0,
|
||||
...extra
|
||||
...extra,
|
||||
insertable: extra.insertable && !options?.disableInsert
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -156,15 +171,19 @@ export default function graphBuilder(
|
||||
}
|
||||
}
|
||||
|
||||
if (extra.path && triggerProps != undefined) {
|
||||
const triggerNode: Node = {
|
||||
let triggerNode: Node | undefined = undefined
|
||||
if (flowPathForTriggerNode) {
|
||||
triggerNode = {
|
||||
id: 'Trigger',
|
||||
position: { x: -1, y: -1 },
|
||||
type: 'trigger',
|
||||
data: {
|
||||
path: triggerProps?.path,
|
||||
simplifiableFlow: simplifiableFlow,
|
||||
path: flowPathForTriggerNode,
|
||||
newFlow: extra.newFlow,
|
||||
eventHandlers: eventHandlers
|
||||
eventHandlers: eventHandlers,
|
||||
modules: modules,
|
||||
isEditor: extra.insertable
|
||||
}
|
||||
}
|
||||
nodes.push(triggerNode)
|
||||
@@ -191,13 +210,16 @@ export default function graphBuilder(
|
||||
type: 'result'
|
||||
}
|
||||
|
||||
nodes.push(inputNode)
|
||||
nodes.push(resultNode)
|
||||
if (simplifiableFlow?.simplifiedFlow !== true) {
|
||||
nodes.push(inputNode)
|
||||
nodes.push(resultNode)
|
||||
}
|
||||
|
||||
function processModules(
|
||||
modules: FlowModule[],
|
||||
beforeNode: Node,
|
||||
nextNode: Node,
|
||||
nextNode: Node | undefined,
|
||||
simplifiedTriggerView: boolean,
|
||||
currentOffset = 0,
|
||||
disableMoveIds: string[] = [],
|
||||
parentIndex?: string,
|
||||
@@ -206,10 +228,12 @@ export default function graphBuilder(
|
||||
let previousId: string | undefined = undefined
|
||||
|
||||
if (modules.length === 0) {
|
||||
addEdge(beforeNode.id, nextNode.id, {
|
||||
subModules: modules,
|
||||
disableMoveIds
|
||||
})
|
||||
if (nextNode) {
|
||||
addEdge(beforeNode.id, nextNode.id, {
|
||||
subModules: modules,
|
||||
disableMoveIds
|
||||
})
|
||||
}
|
||||
} else {
|
||||
modules.forEach((module, index) => {
|
||||
const localDisableMoveIds = [...disableMoveIds, module.id]
|
||||
@@ -295,6 +319,7 @@ export default function graphBuilder(
|
||||
branch.modules,
|
||||
startNode,
|
||||
endNode,
|
||||
false,
|
||||
currentOffset,
|
||||
localDisableMoveIds,
|
||||
parentIndex ? `${parentIndex}-${index}-${branchIndex}` : `${index}-${branchIndex}`
|
||||
@@ -304,7 +329,9 @@ export default function graphBuilder(
|
||||
|
||||
previousId = endNode.id
|
||||
} else if (module.value.type === 'forloopflow') {
|
||||
addNode(module, currentOffset, 'module', modules)
|
||||
if (!simplifiedTriggerView) {
|
||||
addNode(module, currentOffset, 'module', modules)
|
||||
}
|
||||
|
||||
const startNode = {
|
||||
id: `${module.id}-start`,
|
||||
@@ -313,6 +340,7 @@ export default function graphBuilder(
|
||||
id: module.id,
|
||||
module: module,
|
||||
modules: modules,
|
||||
simplifiedTriggerView,
|
||||
eventHandlers: eventHandlers,
|
||||
...extra
|
||||
},
|
||||
@@ -320,9 +348,15 @@ export default function graphBuilder(
|
||||
type: 'forLoopStart'
|
||||
}
|
||||
|
||||
addEdge(module.id, startNode.id, {
|
||||
type: 'empty'
|
||||
})
|
||||
if (!simplifiedTriggerView) {
|
||||
addEdge(module.id, startNode.id, {
|
||||
type: 'empty'
|
||||
})
|
||||
} else if (previousId) {
|
||||
addEdge(previousId, startNode.id, {
|
||||
type: 'empty'
|
||||
})
|
||||
}
|
||||
|
||||
const endNode = {
|
||||
id: `${module.id}-end`,
|
||||
@@ -332,6 +366,7 @@ export default function graphBuilder(
|
||||
module: module,
|
||||
modules: modules,
|
||||
eventHandlers: eventHandlers,
|
||||
simplifiedTriggerView,
|
||||
...extra
|
||||
},
|
||||
position: { x: -1, y: -1 },
|
||||
@@ -347,6 +382,7 @@ export default function graphBuilder(
|
||||
module.value.modules,
|
||||
startNode,
|
||||
endNode,
|
||||
false,
|
||||
currentOffset + 25,
|
||||
localDisableMoveIds,
|
||||
parentIndex
|
||||
@@ -390,6 +426,7 @@ export default function graphBuilder(
|
||||
module.value.modules,
|
||||
startNode,
|
||||
endNode,
|
||||
false,
|
||||
currentOffset + 25,
|
||||
localDisableMoveIds,
|
||||
parentIndex
|
||||
@@ -435,6 +472,7 @@ export default function graphBuilder(
|
||||
module.value.default,
|
||||
defaultBranch,
|
||||
endNode,
|
||||
false,
|
||||
currentOffset,
|
||||
localDisableMoveIds,
|
||||
parentIndex ? `${parentIndex}-${index}` : index.toString(),
|
||||
@@ -468,6 +506,7 @@ export default function graphBuilder(
|
||||
branch.modules,
|
||||
startNode,
|
||||
endNode,
|
||||
false,
|
||||
currentOffset,
|
||||
localDisableMoveIds,
|
||||
parentIndex ? `${parentIndex}-${index}` : index.toString(),
|
||||
@@ -480,30 +519,31 @@ export default function graphBuilder(
|
||||
addNode(module, currentOffset, 'module', modules)
|
||||
|
||||
previousId = module.id
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (index === 0) {
|
||||
addEdge(beforeNode.id, module.id, {
|
||||
subModules: modules,
|
||||
disableMoveIds
|
||||
disableMoveIds,
|
||||
disableInsert: simplifiedTriggerView
|
||||
})
|
||||
}
|
||||
|
||||
if (index === modules.length - 1 && previousId) {
|
||||
if (index === modules.length - 1 && previousId && nextNode) {
|
||||
addEdge(previousId, nextNode.id, {
|
||||
subModules: modules,
|
||||
disableMoveIds
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
processModules(modules, inputNode, resultNode)
|
||||
if (simplifiableFlow?.simplifiedFlow === true && triggerNode) {
|
||||
processModules(modules, triggerNode, undefined, true)
|
||||
} else {
|
||||
processModules(modules, inputNode, resultNode, false)
|
||||
}
|
||||
|
||||
if (failureModule) {
|
||||
let toAdd: Record<string, string> = {}
|
||||
|
||||
@@ -86,45 +86,6 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if data.enableTrigger}
|
||||
<div
|
||||
class="edgeButtonContainer nodrag nopan"
|
||||
style:transform="translate(100%, 50%) translate({sourceX}px,{sourceY + 2}px)"
|
||||
>
|
||||
<InsertModuleButton
|
||||
disableAi={data.disableAi}
|
||||
on:new={(e) => {
|
||||
// console.log('new', e)
|
||||
data?.eventHandlers.insert({
|
||||
modules: data.modules,
|
||||
index: data.index,
|
||||
kind: e.detail.kind,
|
||||
inlineScript: e.detail.inlineScript
|
||||
})
|
||||
}}
|
||||
on:pickScript={(e) => {
|
||||
// console.log('pickScript', e)
|
||||
data?.eventHandlers.insert({
|
||||
modules: data.modules,
|
||||
index: data.index,
|
||||
script: e.detail,
|
||||
kind: e.detail.kind
|
||||
})
|
||||
}}
|
||||
on:pickFlow={(e) => {
|
||||
// console.log('pickFlow', e)
|
||||
data?.eventHandlers.insert({
|
||||
modules: data.modules,
|
||||
index: data.index,
|
||||
flow: e.detail
|
||||
})
|
||||
}}
|
||||
kind="trigger"
|
||||
index={data?.index ?? 0}
|
||||
modules={data?.modules ?? []}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if data?.moving}
|
||||
|
||||
@@ -11,19 +11,35 @@
|
||||
insertable: boolean
|
||||
flowModuleStates: Record<string, GraphModuleState> | undefined
|
||||
eventHandlers: GraphEventHandlers
|
||||
simplifiedTriggerView: boolean
|
||||
}
|
||||
</script>
|
||||
|
||||
<NodeWrapper offset={data.offset} let:darkMode>
|
||||
<VirtualItem
|
||||
label={'Collect result of each iteration'}
|
||||
selectable={true}
|
||||
selected={false}
|
||||
id={data.id}
|
||||
bgColor={getStateColor(undefined, darkMode)}
|
||||
borderColor={getStateColor(data.flowModuleStates?.[data.id]?.type, darkMode)}
|
||||
on:select={(e) => {
|
||||
data?.eventHandlers?.select(e.detail)
|
||||
}}
|
||||
/>
|
||||
{#if data.simplifiedTriggerView}
|
||||
<VirtualItem
|
||||
label={'Each event is processed'}
|
||||
selectable={false}
|
||||
selected={false}
|
||||
id={data.id}
|
||||
hideId
|
||||
bgColor={getStateColor(undefined, darkMode)}
|
||||
borderColor={getStateColor(data.flowModuleStates?.[data.id]?.type, darkMode)}
|
||||
on:select={(e) => {
|
||||
data?.eventHandlers?.select(e.detail)
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<VirtualItem
|
||||
label={'Collect result of each iteration'}
|
||||
selectable={true}
|
||||
selected={false}
|
||||
id={data.id}
|
||||
bgColor={getStateColor(undefined, darkMode)}
|
||||
borderColor={getStateColor(data.flowModuleStates?.[data.id]?.type, darkMode)}
|
||||
on:select={(e) => {
|
||||
data?.eventHandlers?.select(e.detail)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</NodeWrapper>
|
||||
|
||||
@@ -12,12 +12,13 @@
|
||||
modules: FlowModule[]
|
||||
flowModuleStates: Record<string, GraphModuleState> | undefined
|
||||
eventHandlers: GraphEventHandlers
|
||||
simplifiedTriggerView: boolean
|
||||
}
|
||||
</script>
|
||||
|
||||
<NodeWrapper let:darkMode offset={data.offset}>
|
||||
<VirtualItem
|
||||
label={'Do one iteration'}
|
||||
label={data.simplifiedTriggerView ? 'For each new event' : 'Do one iteration'}
|
||||
selectable={false}
|
||||
selected={false}
|
||||
id={data.id}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
moving: string | undefined
|
||||
eventHandlers: GraphEventHandlers
|
||||
index: number
|
||||
enableTrigger: boolean
|
||||
disableAi: boolean
|
||||
disableMoveIds: string[]
|
||||
}
|
||||
@@ -50,6 +49,8 @@
|
||||
detail: 'preprocessor'
|
||||
})
|
||||
}}
|
||||
class="w-[14px] h-[14px]"
|
||||
iconSize={10}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
<script lang="ts">
|
||||
import NodeWrapper from './NodeWrapper.svelte'
|
||||
import TriggersWrapper from '../triggers/TriggersWrapper.svelte'
|
||||
import { type GraphEventHandlers } from '../../graphBuilder'
|
||||
import { type GraphEventHandlers, type SimplifiableFlow } from '../../graphBuilder'
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import { getContext } from 'svelte'
|
||||
import type { Writable } from 'svelte/store'
|
||||
import { Maximize2, Minimize2, Calendar } from 'lucide-svelte'
|
||||
import { getStateColor } from '../../util'
|
||||
import { setScheduledPollSchedule, type TriggerContext } from '$lib/components/triggers'
|
||||
import VirtualItemWrapper from '$lib/components/flows/map/VirtualItemWrapper.svelte'
|
||||
|
||||
export let data: {
|
||||
path: string
|
||||
@@ -11,21 +16,107 @@
|
||||
newFlow: boolean
|
||||
extra_perms: Record<string, any>
|
||||
eventHandlers: GraphEventHandlers
|
||||
modules: FlowModule[]
|
||||
index: number
|
||||
disableAi: boolean
|
||||
simplifiableFlow: SimplifiableFlow
|
||||
}
|
||||
|
||||
const { selectedId } = getContext<{
|
||||
selectedId: Writable<string | undefined>
|
||||
}>('FlowGraphContext')
|
||||
|
||||
const { primarySchedule, triggersCount, selectedTrigger } =
|
||||
getContext<TriggerContext>('TriggerContext')
|
||||
</script>
|
||||
|
||||
<NodeWrapper wrapperClass="shadow-none">
|
||||
<TriggersWrapper
|
||||
path={data.path}
|
||||
on:select={() => {
|
||||
data?.eventHandlers?.select('triggers')
|
||||
}}
|
||||
isFlow={true}
|
||||
selected={$selectedId == 'triggers'}
|
||||
newItem={data.newFlow}
|
||||
/>
|
||||
<NodeWrapper wrapperClass="shadow-md" let:darkMode>
|
||||
{#if data.simplifiableFlow?.simplifiedFlow != true}
|
||||
<TriggersWrapper
|
||||
disableAi={data.disableAi}
|
||||
isEditor={data.isEditor}
|
||||
path={data.path}
|
||||
bgColor={getStateColor(undefined, darkMode)}
|
||||
on:new={(e) => {
|
||||
data?.eventHandlers.insert({
|
||||
modules: data.modules,
|
||||
index: 0,
|
||||
kind: 'trigger',
|
||||
inlineScript: e.detail.inlineScript
|
||||
})
|
||||
data?.eventHandlers?.simplifyFlow(true)
|
||||
}}
|
||||
on:pickScript={(e) => {
|
||||
data?.eventHandlers.insert({
|
||||
modules: data.modules,
|
||||
index: 0,
|
||||
kind: 'trigger',
|
||||
script: e.detail
|
||||
})
|
||||
data?.eventHandlers?.simplifyFlow(true)
|
||||
}}
|
||||
on:openScheduledPoll={(e) => {
|
||||
$selectedTrigger = 'scheduledPoll'
|
||||
}}
|
||||
on:select={(e) => {
|
||||
data?.eventHandlers?.select('triggers')
|
||||
}}
|
||||
on:delete={(e) => {
|
||||
data.eventHandlers.delete(e, '')
|
||||
}}
|
||||
selected={$selectedId == 'triggers'}
|
||||
newItem={data.newFlow}
|
||||
modules={data.modules}
|
||||
/>
|
||||
{:else}
|
||||
<VirtualItemWrapper
|
||||
label="Check for new events"
|
||||
selectable={true}
|
||||
selected={$selectedId == 'triggers'}
|
||||
id={'triggers'}
|
||||
bgColor={getStateColor(undefined, darkMode)}
|
||||
on:select={(e) => {
|
||||
data?.eventHandlers?.select(e.detail)
|
||||
}}
|
||||
>
|
||||
{#if $primarySchedule || ($primarySchedule == undefined && $triggersCount?.primary_schedule?.schedule)}
|
||||
<div class="text-2xs text-primary p-2 flex gap-2 items-center">
|
||||
<Calendar size={12} />
|
||||
<div>
|
||||
Schedule every {$primarySchedule?.cron ?? $triggersCount?.primary_schedule?.schedule}
|
||||
{$primarySchedule?.enabled ||
|
||||
($primarySchedule == undefined && $triggersCount?.primary_schedule?.schedule)
|
||||
? ''
|
||||
: ' (disabled)'}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
class="px-2 py-1 hover:bg-surface-inverse w-full hover:text-primary-inverse"
|
||||
on:click={() => {
|
||||
setScheduledPollSchedule(primarySchedule, triggersCount)
|
||||
}}
|
||||
>
|
||||
Set primary schedule
|
||||
</button>
|
||||
{/if}
|
||||
</VirtualItemWrapper>
|
||||
{/if}
|
||||
{#if data.simplifiableFlow != undefined}
|
||||
<button
|
||||
class="absolute -top-[10px] -right-[10px] rounded-full h-[20px] w-[20px] trash center-center text-secondary
|
||||
outline-[1px] outline dark:outline-gray-500 outline-gray-300 bg-surface duration-150 hover:bg-nord-950 hover:text-white"
|
||||
on:click|preventDefault|stopPropagation={() =>
|
||||
data?.eventHandlers?.simplifyFlow(!data.simplifiableFlow?.simplifiedFlow)}
|
||||
title={data.simplifiableFlow?.simplifiedFlow
|
||||
? 'Expand to full flow view'
|
||||
: 'Simplify flow view for scheduled poll'}
|
||||
>
|
||||
{#if data.simplifiableFlow?.simplifiedFlow}
|
||||
<Maximize2 size={12} strokeWidth={2} />
|
||||
{:else}
|
||||
<Minimize2 size={12} strokeWidth={2} />
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
</NodeWrapper>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<button
|
||||
on:click
|
||||
class={twMerge(
|
||||
'hover:bg-surface-hover rounded-md border text-xs w-6 h-6 relative center-center cursor-pointer bg-surface',
|
||||
'hover:bg-surface-hover rounded-md border text-xs w-[23px] h-[23px] relative center-center cursor-pointer bg-surface',
|
||||
selected ? 'outline-1 outline-tertiary outline' : 'outline-0'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
export let isFlow: boolean
|
||||
export let selected: boolean
|
||||
export let showOnlyWithCount: boolean
|
||||
export let triggersToDisplay: ('webhooks' | 'schedules' | 'routes' | 'websockets' | 'emails')[] =
|
||||
['webhooks', 'schedules', 'routes', 'websockets', 'emails']
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
onMount(() => {
|
||||
@@ -39,84 +41,31 @@
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const triggerTypeConfig = {
|
||||
webhooks: { icon: Webhook, countKey: 'webhook_count' },
|
||||
schedules: { icon: Calendar, countKey: 'schedule_count' },
|
||||
routes: { icon: Route, countKey: 'http_routes_count' },
|
||||
websockets: { icon: Unplug, countKey: 'websocket_count' },
|
||||
emails: { icon: Mail, countKey: 'email_count' }
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !showOnlyWithCount || ($triggersCount?.webhook_count ?? 0) > 0}
|
||||
<Popover>
|
||||
<svelte:fragment slot="text">Webhooks</svelte:fragment>
|
||||
<TriggerButton
|
||||
on:click={() => {
|
||||
$selectedTrigger = 'webhooks'
|
||||
dispatch('select')
|
||||
}}
|
||||
selected={selected && $selectedTrigger === 'webhooks'}
|
||||
>
|
||||
<TriggerCount count={$triggersCount?.webhook_count} />
|
||||
<Webhook size={12} />
|
||||
</TriggerButton>
|
||||
</Popover>
|
||||
{/if}
|
||||
|
||||
{#if !showOnlyWithCount || ($triggersCount?.schedule_count ?? 0) > 0}
|
||||
<Popover>
|
||||
<svelte:fragment slot="text">Schedules</svelte:fragment>
|
||||
<TriggerButton
|
||||
on:click={() => {
|
||||
$selectedTrigger = 'schedules'
|
||||
dispatch('select')
|
||||
}}
|
||||
selected={selected && $selectedTrigger === 'schedules'}
|
||||
>
|
||||
<TriggerCount count={$triggersCount?.schedule_count} />
|
||||
<Calendar size={12} />
|
||||
</TriggerButton>
|
||||
</Popover>
|
||||
{/if}
|
||||
|
||||
{#if !showOnlyWithCount || ($triggersCount?.http_routes_count ?? 0) > 0}
|
||||
<Popover>
|
||||
<svelte:fragment slot="text">HTTP Routes</svelte:fragment>
|
||||
<TriggerButton
|
||||
on:click={() => {
|
||||
$selectedTrigger = 'routes'
|
||||
dispatch('select')
|
||||
}}
|
||||
selected={selected && $selectedTrigger === 'routes'}
|
||||
>
|
||||
<TriggerCount count={$triggersCount?.http_routes_count} />
|
||||
<Route size={12} />
|
||||
</TriggerButton>
|
||||
</Popover>
|
||||
{/if}
|
||||
|
||||
{#if !showOnlyWithCount || ($triggersCount?.websocket_count ?? 0) > 0}
|
||||
<Popover>
|
||||
<svelte:fragment slot="text">Websockets</svelte:fragment>
|
||||
<TriggerButton
|
||||
on:click={() => {
|
||||
$selectedTrigger = 'websockets'
|
||||
dispatch('select')
|
||||
}}
|
||||
selected={selected && $selectedTrigger === 'websockets'}
|
||||
>
|
||||
<TriggerCount count={$triggersCount?.websocket_count} />
|
||||
<Unplug size={12} />
|
||||
</TriggerButton>
|
||||
</Popover>
|
||||
{/if}
|
||||
|
||||
{#if !showOnlyWithCount || ($triggersCount?.email_count ?? 0) > 0}
|
||||
<Popover>
|
||||
<svelte:fragment slot="text">Emails</svelte:fragment>
|
||||
<TriggerButton
|
||||
on:click={() => {
|
||||
$selectedTrigger = 'emails'
|
||||
dispatch('select')
|
||||
}}
|
||||
selected={selected && $selectedTrigger === 'emails'}
|
||||
>
|
||||
<TriggerCount count={$triggersCount?.email_count} />
|
||||
<Mail size={12} />
|
||||
</TriggerButton>
|
||||
</Popover>
|
||||
{/if}
|
||||
{#each triggersToDisplay as type}
|
||||
{@const { icon, countKey } = triggerTypeConfig[type]}
|
||||
{#if !showOnlyWithCount || ($triggersCount?.[countKey] ?? 0) > 0}
|
||||
<Popover>
|
||||
<svelte:fragment slot="text">{type.charAt(0).toUpperCase() + type.slice(1)}</svelte:fragment>
|
||||
<TriggerButton
|
||||
on:click={() => {
|
||||
$selectedTrigger = type
|
||||
dispatch('select')
|
||||
}}
|
||||
selected={selected && $selectedTrigger === type}
|
||||
>
|
||||
<TriggerCount count={$triggersCount?.[countKey]} />
|
||||
<svelte:component this={icon} size={12} />
|
||||
</TriggerButton>
|
||||
</Popover>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -4,29 +4,51 @@
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import TriggersBadge from './TriggersBadge.svelte'
|
||||
import InsertModuleButton from '$lib/components/flows/map/InsertModuleButton.svelte'
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export let path: string
|
||||
export let newItem: boolean
|
||||
export let isFlow: boolean
|
||||
export let selected: boolean
|
||||
export let isEditor: boolean = false
|
||||
export let disableAi: boolean = false
|
||||
export let modules: FlowModule[] = []
|
||||
export let bgColor: string
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<div style={`width: ${NODE.width}px;`}>
|
||||
<div class="flex flex-row mx-auto w-min">
|
||||
<button
|
||||
class="flex flex-row gap-2 px-2 border p-1 rounded-md bg-surface shadow-md items-center {selected
|
||||
? 'outline outline-offset-1 outline-1 outline-slate-900 dark:bg-white/5 dark:outline-slate-800/60 dark:border-gray-400'
|
||||
: ''}"
|
||||
on:click={() => {
|
||||
dispatch('select')
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-row items-center text-2xs font-normal"> Triggers </div>
|
||||
</div>
|
||||
<TriggersBadge showOnlyWithCount={false} {path} {newItem} {isFlow} {selected} on:select />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
style="background-color: {bgColor} !important;"
|
||||
class="flex w-full flex-row gap-2 px-2 p-1 items-center {selected
|
||||
? 'outline outline-2 outline-gray-600 rounded-sm dark:bg-white/5 dark:outline-gray-400'
|
||||
: ''}"
|
||||
on:click={() => {
|
||||
dispatch('select')
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col mr-1 ml-1">
|
||||
<div class="flex flex-row items-center text-2xs font-normal"> Triggers </div>
|
||||
</div>
|
||||
<TriggersBadge showOnlyWithCount={false} {path} {newItem} isFlow {selected} on:select />
|
||||
{#if isEditor}
|
||||
<InsertModuleButton
|
||||
{disableAi}
|
||||
on:new
|
||||
on:pickScript
|
||||
on:select
|
||||
on:open={() => {
|
||||
dispatch('openScheduledPoll')
|
||||
}}
|
||||
kind="trigger"
|
||||
index={0}
|
||||
{modules}
|
||||
class={twMerge(
|
||||
'hover:bg-surface-hover rounded-md border text-xs w-[23px] h-[23px] relative center-center cursor-pointer bg-surface outline-0'
|
||||
)}
|
||||
/>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<script>
|
||||
// You can pass props for flexibility (e.g., size, color)
|
||||
export let size = 24
|
||||
</script>
|
||||
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M4.96046 2.04913L6.96046 2.04913" />
|
||||
<path d="M5.96046 8.94553L4.96046 7.94553" />
|
||||
<path d="M13.1611 13.8974L17.2173 13.8974" />
|
||||
<path
|
||||
d="M20.4622 12.2749V9.84124C20.4622 9.62608 20.3767 9.41975 20.2246 9.26761C20.0725 9.11547 19.8661 9.03001 19.651 9.03001H18.0285C17.8134 9.03001 17.607 9.11547 17.4549 9.26761C17.3028 9.41975 17.2173 9.62608 17.2173 9.84124V12.2749"
|
||||
/>
|
||||
<path
|
||||
d="M20.8678 22.0097C21.1906 22.0097 21.5001 21.8632 21.7283 21.6024C21.9565 21.3415 22.0847 20.9878 22.0847 20.619V17.9412C22.0847 16.9747 20.8678 15.8816 20.8678 14.5834V12.9703C20.8678 12.7858 20.8037 12.609 20.6896 12.4786C20.5755 12.3482 20.4208 12.2749 20.2594 12.2749H17.8257C17.6644 12.2749 17.5096 12.3482 17.3955 12.4786C17.2814 12.609 17.2173 12.7858 17.2173 12.9703V20.619C17.2173 20.9878 17.3455 21.3415 17.5737 21.6024C17.8019 21.8632 18.1114 22.0097 18.4341 22.0097H20.8678Z"
|
||||
/>
|
||||
<path d="M22.0847 18.7648H8.29377" />
|
||||
<path
|
||||
d="M9.51061 22.0097C9.18789 22.0097 8.87838 21.8632 8.65018 21.6024C8.42197 21.3415 8.29377 20.9878 8.29377 20.619V17.9412C8.29377 16.9747 9.51061 15.8816 9.51061 14.5834V12.9703C9.51061 12.7858 9.57472 12.609 9.68882 12.4786C9.80292 12.3482 9.95767 12.2749 10.119 12.2749H12.5527C12.7141 12.2749 12.8688 12.3482 12.9829 12.4786C13.097 12.609 13.1611 12.7858 13.1611 12.9703V20.619C13.1611 20.9878 13.0329 21.3415 12.8047 21.6024C12.5765 21.8632 12.267 22.0097 11.9443 22.0097H9.51061Z"
|
||||
/>
|
||||
<path
|
||||
d="M13.1611 12.2749V9.84124C13.1611 9.62608 13.0757 9.41975 12.9235 9.26761C12.7714 9.11547 12.5651 9.03001 12.3499 9.03001H10.7275C10.5123 9.03001 10.306 9.11547 10.1538 9.26761C10.0017 9.41975 9.91622 9.62608 9.91622 9.84124V12.2749"
|
||||
/>
|
||||
<path
|
||||
d="M8.78889 6.11711C8.30848 5.63669 7.71362 5.28656 7.0604 5.09974C6.40719 4.91291 5.71715 4.89555 5.05536 5.04928C4.39358 5.20301 3.78186 5.52278 3.27789 5.97842C2.77392 6.43406 2.39431 7.01056 2.17486 7.65355C1.95542 8.29654 1.90337 8.98483 2.02362 9.65351C2.14387 10.3222 2.43246 10.9492 2.86217 11.4755C3.29188 12.0017 3.84856 12.4098 4.47969 12.6614C5.11083 12.9129 5.79563 12.9995 6.46951 12.913"
|
||||
/>
|
||||
</svg>
|
||||
@@ -19,6 +19,7 @@ import Slack from './Slack.svelte'
|
||||
import TogglIcon from './TogglIcon.svelte'
|
||||
import WindmillIcon from './WindmillIcon.svelte'
|
||||
import WindmillIcon2 from './WindmillIcon2.svelte'
|
||||
import SchedulePollIcon from './SchedulePollIcon.svelte'
|
||||
import MailchimpIcon from './MailchimpIcon.svelte'
|
||||
import SendgridIcon from './SendgridIcon.svelte'
|
||||
import SendflakeIcon from './SendflakeIcon.svelte'
|
||||
@@ -215,6 +216,7 @@ export {
|
||||
TogglIcon,
|
||||
WindmillIcon,
|
||||
WindmillIcon2,
|
||||
SchedulePollIcon,
|
||||
MailchimpIcon,
|
||||
SendgridIcon,
|
||||
LinkedinIcon,
|
||||
|
||||
@@ -10,7 +10,31 @@ export type ScheduleTrigger = {
|
||||
}
|
||||
|
||||
export type TriggerContext = {
|
||||
selectedTrigger: Writable<'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets'>
|
||||
selectedTrigger: Writable<
|
||||
'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' | 'scheduledPoll'
|
||||
>
|
||||
primarySchedule: Writable<ScheduleTrigger | undefined | false>
|
||||
triggersCount: Writable<TriggersCount | undefined>
|
||||
simplifiedPoll: Writable<boolean | undefined>
|
||||
}
|
||||
|
||||
export function setScheduledPollSchedule(
|
||||
primarySchedule: Writable<ScheduleTrigger | undefined | false>,
|
||||
triggersCount: Writable<TriggersCount | undefined>
|
||||
) {
|
||||
const cron = '0 */5 * * * *'
|
||||
primarySchedule.set({
|
||||
enabled: true,
|
||||
summary: 'Check for new events every 5 minutes',
|
||||
cron: cron,
|
||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
args: {}
|
||||
})
|
||||
triggersCount.update((triggersCount) => {
|
||||
return {
|
||||
...(triggersCount ?? {}),
|
||||
schedule_count: (triggersCount?.schedule_count ?? 0) + 1,
|
||||
primary_schedule: { schedule: cron }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
import { base } from '$lib/base'
|
||||
|
||||
$: console.log('dbg base', base)
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col w-full gap-4 text-sm">
|
||||
<Alert
|
||||
title="Scheduled Poll"
|
||||
size="xs"
|
||||
documentationLink="https://www.windmill.dev/docs/flows/flow_trigger"
|
||||
>
|
||||
Sheduled Poll is a trigger that polls an external API at a given interval. It is useful for
|
||||
triggering jobs based on the change of external states. Under the hood, it is a script triggered
|
||||
by a schedule, and returning a list of the changed states so we can iterate over them.
|
||||
</Alert>
|
||||
|
||||
<Label label="Setting a scheduled poll" primary>
|
||||
<ul class="pt-4 space-y-2 text-sm">
|
||||
<li>
|
||||
1. Chose a trigger script using the script picker that will poll for new events regularly <img
|
||||
class="shadow-lg rounded"
|
||||
alt="script picker"
|
||||
src="{base}/script-picker.png"
|
||||
width="400"
|
||||
/>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
2. Add logic to process each new event <img
|
||||
class="shadow-lg rounded"
|
||||
alt="create action"
|
||||
src="{base}/create_action.png"
|
||||
width="500"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</Label>
|
||||
</div>
|
||||
@@ -11,6 +11,7 @@
|
||||
import { getContext } from 'svelte'
|
||||
import type { TriggerContext } from '$lib/components/triggers'
|
||||
import WebsocketTriggersPanel from './WebsocketTriggersPanel.svelte'
|
||||
import ScheduledPollPanel from './ScheduledPollPanel.svelte'
|
||||
|
||||
export let noEditor: boolean
|
||||
export let newItem = false
|
||||
@@ -19,67 +20,88 @@
|
||||
export let schema: any
|
||||
export let isFlow: boolean
|
||||
|
||||
const { selectedTrigger } = getContext<TriggerContext>('TriggerContext')
|
||||
const { selectedTrigger, simplifiedPoll } = getContext<TriggerContext>('TriggerContext')
|
||||
</script>
|
||||
|
||||
<FlowCard {noEditor} title="Triggers">
|
||||
<div class="pt-4">
|
||||
<Tabs bind:selected={$selectedTrigger}>
|
||||
<Tab value="webhooks" selectedClass="text-primary font-semibold">Webhooks</Tab>
|
||||
<Tab value="schedules" selectedClass="text-primary text-sm font-semibold">Schedules</Tab>
|
||||
<Tab value="routes" selectedClass="text-primary text-sm font-semibold">Routes</Tab>
|
||||
<Tab value="websockets" selectedClass="text-primary text-sm font-semibold">Websockets</Tab>
|
||||
<Tab value="emails" selectedClass="text-primary text-sm font-semibold">Email</Tab>
|
||||
{#if !$simplifiedPoll}
|
||||
<Tabs bind:selected={$selectedTrigger}>
|
||||
<Tab value="webhooks" selectedClass="text-primary font-semibold">Webhooks</Tab>
|
||||
<Tab value="schedules" selectedClass="text-primary text-sm font-semibold">Schedules</Tab>
|
||||
<Tab value="routes" selectedClass="text-primary text-sm font-semibold">Routes</Tab>
|
||||
<Tab value="websockets" selectedClass="text-primary text-sm font-semibold">Websockets</Tab>
|
||||
<Tab value="emails" selectedClass="text-primary text-sm font-semibold">Email</Tab>
|
||||
<Tab value="scheduledPoll" selectedClass="text-primary text-sm font-semibold"
|
||||
>Scheduled Poll</Tab
|
||||
>
|
||||
|
||||
<svelte:fragment slot="content">
|
||||
{#if $selectedTrigger === 'webhooks'}
|
||||
<div class="p-4">
|
||||
<WebhooksPanel
|
||||
scopes={isFlow ? [`run:flow/${currentPath}`] : [`run:script/${currentPath}`]}
|
||||
path={currentPath}
|
||||
{isFlow}
|
||||
args={{}}
|
||||
token=""
|
||||
{newItem}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<svelte:fragment slot="content">
|
||||
{#if $selectedTrigger === 'webhooks'}
|
||||
<div class="p-4">
|
||||
<WebhooksPanel
|
||||
scopes={isFlow ? [`run:flow/${currentPath}`] : [`run:script/${currentPath}`]}
|
||||
path={currentPath}
|
||||
{isFlow}
|
||||
args={{}}
|
||||
token=""
|
||||
{newItem}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $selectedTrigger === 'emails'}
|
||||
<div class="p-4">
|
||||
<EmailTriggerPanel
|
||||
token=""
|
||||
scopes={isFlow ? [`run:flow/${currentPath}`] : [`run:script/${currentPath}`]}
|
||||
path={currentPath}
|
||||
{isFlow}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $selectedTrigger === 'emails'}
|
||||
<div class="p-4">
|
||||
<EmailTriggerPanel
|
||||
token=""
|
||||
scopes={isFlow ? [`run:flow/${currentPath}`] : [`run:script/${currentPath}`]}
|
||||
path={currentPath}
|
||||
{isFlow}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $selectedTrigger === 'routes'}
|
||||
<div class="p-4">
|
||||
<RoutesPanel {newItem} path={currentPath} {isFlow} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $selectedTrigger === 'routes'}
|
||||
<div class="p-4">
|
||||
<RoutesPanel {newItem} path={currentPath} {isFlow} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $selectedTrigger === 'websockets'}
|
||||
<div class="p-4">
|
||||
<WebsocketTriggersPanel {newItem} path={currentPath} {isFlow} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $selectedTrigger === 'websockets'}
|
||||
<div class="p-4">
|
||||
<WebsocketTriggersPanel {newItem} path={currentPath} {isFlow} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $selectedTrigger === 'schedules'}
|
||||
<div class="p-2">
|
||||
<RunPageSchedules
|
||||
{schema}
|
||||
{isFlow}
|
||||
path={initialPath}
|
||||
{newItem}
|
||||
can_write={canWrite(currentPath, {}, $userStore)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Tabs>
|
||||
{#if $selectedTrigger === 'schedules'}
|
||||
<div class="p-4">
|
||||
<RunPageSchedules
|
||||
{schema}
|
||||
{isFlow}
|
||||
path={initialPath}
|
||||
{newItem}
|
||||
can_write={canWrite(currentPath, {}, $userStore)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $selectedTrigger === 'scheduledPoll'}
|
||||
<div class="p-4">
|
||||
<ScheduledPollPanel />
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Tabs>
|
||||
{:else}
|
||||
<div class="px-4 pb-2">
|
||||
<RunPageSchedules
|
||||
{schema}
|
||||
{isFlow}
|
||||
path={initialPath}
|
||||
{newItem}
|
||||
can_write={canWrite(currentPath, {}, $userStore)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</FlowCard>
|
||||
|
||||
@@ -74,12 +74,13 @@
|
||||
const primaryScheduleStore = writable<ScheduleTrigger | undefined | false>(undefined)
|
||||
const triggersCount = writable<TriggersCount | undefined>(undefined)
|
||||
const selectedTriggerStore = writable<
|
||||
'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets'
|
||||
'webhooks' | 'emails' | 'schedules' | 'cli' | 'routes' | 'websockets' | 'scheduledPoll'
|
||||
>('webhooks')
|
||||
setContext<TriggerContext>('TriggerContext', {
|
||||
primarySchedule: primaryScheduleStore,
|
||||
selectedTrigger: selectedTriggerStore,
|
||||
triggersCount: triggersCount
|
||||
triggersCount: triggersCount,
|
||||
simplifiedPoll: writable(false)
|
||||
})
|
||||
|
||||
setContext<FlowEditorContext>('FlowEditorContext', {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
@@ -463,7 +463,8 @@ const config = {
|
||||
height: 'height'
|
||||
},
|
||||
fontSize: {
|
||||
'2xs': '0.7rem'
|
||||
'2xs': '0.7rem',
|
||||
'3xs': '0.65rem'
|
||||
},
|
||||
screens: {
|
||||
fhd: '1900px',
|
||||
|
||||
@@ -269,6 +269,8 @@ components:
|
||||
type: number
|
||||
custom_concurrency_key:
|
||||
type: string
|
||||
is_trigger:
|
||||
type: boolean
|
||||
required:
|
||||
- type
|
||||
- content
|
||||
@@ -292,6 +294,8 @@ components:
|
||||
- script
|
||||
tag_override:
|
||||
type: string
|
||||
is_trigger:
|
||||
type: boolean
|
||||
required:
|
||||
- type
|
||||
- path
|
||||
|
||||
Reference in New Issue
Block a user