primary schedule in flow UI wip

This commit is contained in:
Ruben Fiszel
2022-07-26 16:21:41 +02:00
parent 668ca4d9b1
commit 326a97a96e
6 changed files with 154 additions and 128 deletions
@@ -0,0 +1,16 @@
<script lang="ts">
import { faChevronUp, faChevronDown } from '@fortawesome/free-solid-svg-icons'
import Icon from 'svelte-awesome'
import { slide } from 'svelte/transition'
export let open = false
export let text: string
</script>
<button class="text-gray-600 underline" on:click={() => (open = !open)}
>{text}&nbsp;<Icon data={open ? faChevronUp : faChevronDown} scale={0.5} /></button
>
{#if open}
<div transition:slide><slot /></div>
{/if}
@@ -0,0 +1,80 @@
<script context="module">
export const OFFSET = new Date().getTimezoneOffset()
</script>
<script lang="ts">
import { ScheduleService } from '$lib/gen'
import { displayDate, formatCron, sendUserToast } from '$lib/utils'
import CollapseLink from './CollapseLink.svelte'
export let validCRON = true
let preview: string[] = []
let cronError = ''
export let schedule: string = '0 0 12 * *'
$: handleScheduleInput(schedule)
async function handleScheduleInput(input: string): Promise<void> {
try {
preview = await ScheduleService.previewSchedule({
requestBody: { schedule: formatCron(input), offset: OFFSET }
})
cronError = ''
validCRON = true
} catch (err) {
if (err.status == 400 && err.body.includes('cron')) {
cronError = `Invalid cron expression`
validCRON = false
} else {
sendUserToast(`Cannot preview: ${err}`, true)
validCRON = false
}
}
}
</script>
<div class="text-purple-500 text-2xs grow">{cronError}</div>
<div class="flex flex-row items-end max-w-5xl">
<label class="text-xs min-w-max mr-2 self-center" for="cron-schedule">CRON expression</label>
<input
class="inline-block"
type="text"
id="cron-schedule"
name="cron-schedule"
bind:value={schedule}
/>
</div>
<div class="flex flex-row text-xs text-blue-500 gap-3 pl-28">
<button
on:click={() => {
schedule = '0 */15 * * *'
cronError = ''
}}>every 15 min</button
>
<button
on:click={() => {
schedule = '0 0 * * * *'
cronError = ''
}}>every hour</button
>
<button
on:click={() => {
schedule = '0 0 8 * * *'
cronError = ''
}}>once a day at 8AM</button
>
</div>
<CollapseLink text="preview next runs" open={true}>
{#if preview && preview.length > 0}
<div class="text-sm text-gray-700 border p-2 rounded-md">
<div class="flex flex-row justify-between">The next 10 runs will be scheduled at:</div>
<ul class="list-disc mx-12">
{#each preview as p}
<li class="mx-2 text-gray-700 text-sm">{displayDate(p)}</li>
{/each}
</ul>
</div>
{/if}
</CollapseLink>
+23 -2
View File
@@ -17,9 +17,17 @@
import Modal from './Modal.svelte'
import FlowViewer from './FlowViewer.svelte'
import { flowToMode } from './flows/utils'
import CronInput from './CronInput.svelte'
import CollapseLink from './CollapseLink.svelte'
import Toggle from './Toggle.svelte'
export let pathError = ''
export let initialPath: string = ''
export let mode: FlowMode =
$flowStore?.value.modules[1]?.value.type == FlowModuleValue.type.FORLOOPFLOW ? 'pull' : 'push'
let allowSchedule = false
let cronSchedule: string | undefined
let jsonSetter: Modal
let jsonViewer: Modal
@@ -28,8 +36,7 @@
let open = 0
let args: Record<string, any> = {}
export let mode: FlowMode =
$flowStore?.value.modules[1]?.value.type == FlowModuleValue.type.FORLOOPFLOW ? 'pull' : 'push'
$: numberOfSteps = $flowStore?.value.modules.length - 1
</script>
@@ -163,6 +170,20 @@
]}
bind:value={mode}
/>
<div class="p-4 hidden">
<CollapseLink text="set primary schedule" open={mode == 'pull'}>
<Toggle
bind:value={allowSchedule}
options={{
left: { label: 'disabled', value: false },
right: { label: 'enabled', value: true }
}}
/>
<div class="p-2 mt-2 rounded" class:bg-gray-300={!allowSchedule}>
<CronInput schedule={cronSchedule} />
</div>
</CollapseLink>
</div>
</div>
</li>
<li class="flex flex-row flex-shrink max-w-full mx-auto mt-20">
+14 -12
View File
@@ -1,11 +1,13 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
export let value: any
export let options: {
left: { label?: string; value: any }
right: { label?: string; value: any }
}
let checked = options.right.value === value
export let value: any = undefined
export let options:
| {
left: { label?: string; value: any }
right: { label?: string; value: any }
}
| undefined = undefined
export let checked = options?.right.value === value
const id = Date.now().toString(36)
@@ -14,8 +16,8 @@
<span>
<label for={id} class="inline-flex items-center cursor-pointer mt-2">
{#if Boolean(options.left.label)}
<span class="mr-2 text-sm font-medium text-gray-900">{options.left.label}</span>
{#if Boolean(options?.left.label)}
<span class="mr-2 text-sm font-medium text-gray-900">{options?.left.label}</span>
{/if}
<div class="relative">
<input
@@ -25,16 +27,16 @@
class="sr-only peer"
bind:checked
on:change={() => {
value = checked ? options.right.value : options.left.value
dispatch('change', value)
value = options ? (checked ? options?.right.value : options?.left.value) : checked
dispatch('change', value ?? checked)
}}
/>
<div
class="w-11 h-6 bg-gray-200 rounded-full peer peer-focus:ring-4 peer-focus:ring-blue-300 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"
/>
</div>
{#if Boolean(options.right.label)}
<span class="ml-2 text-sm font-medium text-gray-900">{options.right.label}</span>
{#if Boolean(options?.right.label)}
<span class="ml-2 text-sm font-medium text-gray-900">{options?.right.label}</span>
{/if}
</label>
</span>
+11
View File
@@ -456,3 +456,14 @@ export async function loadHubFlows() {
const processed = flows.sort((a, b) => b.votes - a.votes)
return processed
}
export function formatCron(inp: string): string {
// Allow for cron expressions inputted by the user to omit month and year
let splitted = inp.split(' ')
splitted = splitted.filter(String) //remove empty string elements
if (6 - splitted.length > 0) {
return splitted.concat(Array(6 - splitted.length).fill('*')).join(' ')
} else {
return inp
}
}
+10 -114
View File
@@ -8,7 +8,7 @@
<script lang="ts">
import { page } from '$app/stores'
import { sendUserToast, displayDate } from '$lib/utils'
import { sendUserToast, displayDate, formatCron } from '$lib/utils'
import { ScriptService, type Script, ScheduleService, type Flow, FlowService } from '$lib/gen'
import PageHeader from '$lib/components/PageHeader.svelte'
@@ -21,11 +21,11 @@
import SchemaForm from '$lib/components/SchemaForm.svelte'
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
import Required from '$lib/components/Required.svelte'
import CronInput, { OFFSET } from '$lib/components/CronInput.svelte'
let initialPath = $page.url.searchParams.get('edit') || ''
let edit = initialPath === '' ? false : true
let scheduleInput: string = '0 0 12 * *'
let cronError = ''
let schedule: string = '0 0 12 * *'
let script_path = $page.url.searchParams.get('path') || ''
let is_flow = $page.url.searchParams.get('isFlow') == 'true'
@@ -36,20 +36,14 @@
let runnable: Script | Flow | undefined
let args: Record<string, any> = {}
let validCRON = true
let allowSchedule: boolean
let isValid = true
let preview: string[] = []
let path: string = ''
let pathError = ''
const offset = new Date().getTimezoneOffset()
let validCRON = true
$: allowSchedule = isValid && validCRON && script_path != ''
$: handleScheduleInput(scheduleInput)
$: script_path && loadScript(script_path)
async function loadScript(p: string | undefined): Promise<void> {
@@ -70,7 +64,7 @@
workspace: $workspaceStore!,
path: initialPath
})
scheduleInput = s.schedule
schedule = s.schedule
script_path = s.script_path ?? ''
args = s.args ?? {}
} catch (err) {
@@ -84,7 +78,7 @@
workspace: $workspaceStore!,
path: initialPath,
requestBody: {
schedule: formatInput(scheduleInput),
schedule: formatCron(schedule),
script_path: script_path,
is_flow: is_flow,
args
@@ -96,8 +90,8 @@
workspace: $workspaceStore!,
requestBody: {
path,
schedule: formatInput(scheduleInput),
offset,
schedule: formatCron(schedule),
offset: OFFSET,
script_path,
is_flow,
args
@@ -107,35 +101,6 @@
}
}
function formatInput(input: string): string {
// Allow for cron expressions inputted by the user to omit month and year
let splitted = input.split(' ')
splitted = splitted.filter(String) //remove empty string elements
if (6 - splitted.length > 0) {
return splitted.concat(Array(6 - splitted.length).fill('*')).join(' ')
} else {
return input
}
}
async function handleScheduleInput(input: string): Promise<void> {
try {
preview = await ScheduleService.previewSchedule({
requestBody: { schedule: formatInput(input), offset }
})
cronError = ''
validCRON = true
} catch (err) {
if (err.status == 400 && err.body.includes('cron')) {
cronError = `Invalid cron expression`
validCRON = false
} else {
sendUserToast(`Cannot preview: ${err}`, true)
validCRON = false
}
}
}
$: {
if ($workspaceStore) {
if (edit) {
@@ -191,39 +156,9 @@
{/if}
</div>
<h2 class="mt-2 md:mt-6">
Schedule<Tooltip>Schedules use CRON syntax. Milliseconds are mandatory.</Tooltip>
Schedule<Tooltip>Schedules use CRON syntax. Seconds are mandatory.</Tooltip>
</h2>
<div class="text-purple-500 text-2xs grow">{cronError}</div>
<div class="flex flex-row items-end max-w-5xl">
<label class="text-xs min-w-max mr-2 self-center" for="cron-schedule">CRON expression</label>
<input
class="inline-block"
type="text"
id="cron-schedule"
name="cron-schedule"
bind:value={scheduleInput}
/>
</div>
<div class="flex flex-row text-xs text-blue-500 gap-3 pl-28">
<button
on:click={() => {
scheduleInput = '0 */15 * * *'
cronError = ''
}}>every 15 min</button
>
<button
on:click={() => {
scheduleInput = '0 0 * * * *'
cronError = ''
}}>every hour</button
>
<button
on:click={() => {
scheduleInput = '0 0 8 * * *'
cronError = ''
}}>once a day at 8AM</button
>
</div>
<CronInput bind:schedule bind:validCRON />
<div class="flex flex-row-reverse mt-2 ">
<div>
<button
@@ -236,44 +171,5 @@
</button>
</div>
</div>
{#if preview && preview.length > 0}
<div class="text-sm text-gray-700 border mt-6 p-2 rounded-md">
<div class="flex flex-row justify-between">
The next 10 runs of this script will be:
<button
on:click={() => {
preview = []
}}
>
<svg
class="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
<ul class="list-disc mx-12">
{#each preview as p}
<li class="mx-2 text-gray-700 text-sm">{displayDate(p)}</li>
{/each}
</ul>
</div>
{/if}
</div>
</CenteredPage>
<style>
.selected:hover {
@apply border border-gray-400 rounded-md border-opacity-50;
}
</style>