mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-09 00:04:10 +00:00
fix(frontend): filter script by is_trigger and jobs by is_skipped + path fix
This commit is contained in:
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE script DROP COLUMN trigger_reco_interval;
|
||||
ALTER TABLE script ADD COLUMN is_trigger BOOLEAN NOT NULL DEFAULT false;
|
||||
|
||||
+24
-6
@@ -1609,6 +1609,15 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: is_trigger
|
||||
description: |
|
||||
(default regardless)
|
||||
if true show only the trigger scripts
|
||||
if false show only the non trigger scripts
|
||||
if not defined, show all regardless of if the script is a trigger script
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
"200":
|
||||
description: All available scripts
|
||||
@@ -1656,8 +1665,8 @@ paths:
|
||||
language:
|
||||
type: string
|
||||
enum: [python3, deno]
|
||||
trigger_reco_interval:
|
||||
type: integer
|
||||
is_trigger:
|
||||
type: boolean
|
||||
required:
|
||||
- path
|
||||
- summary
|
||||
@@ -2266,7 +2275,11 @@ paths:
|
||||
- $ref: "#/components/parameters/CreatedAfter"
|
||||
- $ref: "#/components/parameters/Success"
|
||||
- $ref: "#/components/parameters/JobKinds"
|
||||
|
||||
- name: is_skipped
|
||||
description: is the job skipped
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
"200":
|
||||
description: All available completed jobs
|
||||
@@ -2293,7 +2306,11 @@ paths:
|
||||
- $ref: "#/components/parameters/CreatedBefore"
|
||||
- $ref: "#/components/parameters/CreatedAfter"
|
||||
- $ref: "#/components/parameters/JobKinds"
|
||||
|
||||
- name: is_skipped
|
||||
description: is the job skipped
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
- name: success
|
||||
description: filter on successful jobs
|
||||
in: query
|
||||
@@ -3115,8 +3132,8 @@ components:
|
||||
language:
|
||||
type: string
|
||||
enum: [python3, deno]
|
||||
trigger_reco_interval:
|
||||
type: integer
|
||||
is_trigger:
|
||||
type: boolean
|
||||
required:
|
||||
- hash
|
||||
- path
|
||||
@@ -3129,6 +3146,7 @@ components:
|
||||
- is_template
|
||||
- extra_perms
|
||||
- language
|
||||
- is_trigger
|
||||
|
||||
ScriptArgs:
|
||||
type: object
|
||||
|
||||
@@ -557,6 +557,7 @@ pub struct ListCompletedQuery {
|
||||
pub parent_job: Option<String>,
|
||||
pub order_desc: Option<bool>,
|
||||
pub job_kinds: Option<String>,
|
||||
pub is_skipped: Option<bool>,
|
||||
}
|
||||
fn list_completed_jobs_query(
|
||||
w_id: &str,
|
||||
@@ -597,6 +598,9 @@ fn list_completed_jobs_query(
|
||||
if let Some(dt) = &lq.created_after {
|
||||
sqlb.and_where_gt("created_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
}
|
||||
if let Some(sk) = &lq.is_skipped {
|
||||
sqlb.and_where_eq("is_skipped", sk);
|
||||
}
|
||||
if let Some(jk) = &lq.job_kinds {
|
||||
sqlb.and_where_in(
|
||||
"job_kind",
|
||||
|
||||
@@ -129,7 +129,7 @@ pub struct Script {
|
||||
pub lock: Option<String>,
|
||||
pub lock_error_logs: Option<String>,
|
||||
pub language: ScriptLang,
|
||||
pub trigger_reco_interval: Option<i32>,
|
||||
pub is_trigger: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, sqlx::Type, Debug)]
|
||||
@@ -156,7 +156,7 @@ pub struct NewScript {
|
||||
pub is_template: Option<bool>,
|
||||
pub lock: Option<Vec<String>>,
|
||||
pub language: ScriptLang,
|
||||
pub trigger_reco_interval: Option<i32>,
|
||||
pub is_trigger: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -171,6 +171,7 @@ pub struct ListScriptQuery {
|
||||
pub order_by: Option<String>,
|
||||
pub order_desc: Option<bool>,
|
||||
pub is_template: Option<bool>,
|
||||
pub is_trigger: Option<bool>,
|
||||
}
|
||||
|
||||
async fn list_scripts(
|
||||
@@ -201,7 +202,7 @@ async fn list_scripts(
|
||||
"null as lock",
|
||||
"CASE WHEN lock_error_logs IS NOT NULL THEN 'error' ELSE null END as lock_error_logs",
|
||||
"language",
|
||||
"trigger_reco_interval",
|
||||
"is_trigger",
|
||||
])
|
||||
.order_by("created_at", lq.order_desc.unwrap_or(true))
|
||||
.and_where("workspace_id = ? OR workspace_id = 'starter'".bind(&w_id))
|
||||
@@ -239,6 +240,9 @@ async fn list_scripts(
|
||||
if let Some(it) = &lq.is_template {
|
||||
sqlb.and_where_eq("is_template", it);
|
||||
}
|
||||
if let Some(it) = &lq.is_trigger {
|
||||
sqlb.and_where_eq("is_trigger", it);
|
||||
}
|
||||
|
||||
let sql = sqlb.sql().map_err(|e| Error::InternalErr(e.to_string()))?;
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
@@ -411,7 +415,7 @@ async fn create_script(
|
||||
//::text::json is to ensure we use serde_json with preserve order
|
||||
sqlx::query!(
|
||||
"INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, content, \
|
||||
created_by, schema, is_template, extra_perms, lock, language, trigger_reco_interval) VALUES \
|
||||
created_by, schema, is_template, extra_perms, lock, language, is_trigger) VALUES \
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9::text::json, $10, $11, $12, $13, $14)",
|
||||
&w_id,
|
||||
&hash.0,
|
||||
@@ -426,7 +430,7 @@ async fn create_script(
|
||||
extra_perms,
|
||||
lock,
|
||||
ns.language: ScriptLang,
|
||||
ns.trigger_reco_interval,
|
||||
ns.is_trigger.unwrap_or(false),
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
export let contentEncoding = ''
|
||||
export let type: string | undefined = undefined
|
||||
export let required = false
|
||||
export let pattern: undefined | string
|
||||
export let pattern: undefined | string = undefined
|
||||
export let valid = required ? false : true
|
||||
export let minRows = 1
|
||||
export let maxRows = 10
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
import { page } from '$app/stores'
|
||||
import { FlowService, ScriptService, type Flow } from '$lib/gen'
|
||||
import { clearPreviewResults, hubScripts, workspaceStore } from '$lib/stores'
|
||||
import { pathIsEmpty, sendUserToast } from '$lib/utils'
|
||||
import { sendUserToast, setQueryWithoutLoad } from '$lib/utils'
|
||||
import { onMount } from 'svelte'
|
||||
import SvelteMarkdown from 'svelte-markdown'
|
||||
import FlowEditor from './FlowEditor.svelte'
|
||||
import { flowStore, initFlow, type FlowMode } from './flows/flowStore'
|
||||
import { flowStore, type FlowMode } from './flows/flowStore'
|
||||
import { flowToMode } from './flows/utils'
|
||||
import Path from './Path.svelte'
|
||||
import Required from './Required.svelte'
|
||||
@@ -68,12 +68,9 @@
|
||||
}
|
||||
|
||||
flowStore.subscribe((flow: Flow) => {
|
||||
$page.url.searchParams.set('state', btoa(JSON.stringify(flowToMode(flow, mode))))
|
||||
history.replaceState({}, '', $page.url)
|
||||
setQueryWithoutLoad($page.url, 'state', btoa(JSON.stringify(flowToMode(flow, mode))))
|
||||
})
|
||||
|
||||
$: flow && initFlow(flow)
|
||||
|
||||
onMount(() => {
|
||||
loadSearchData()
|
||||
clearPreviewResults()
|
||||
|
||||
@@ -66,31 +66,33 @@
|
||||
let validateTimeout: NodeJS.Timeout | undefined = undefined
|
||||
|
||||
async function validatePath(path: string, kind: PathKind): Promise<void> {
|
||||
if (initialPath == '' && initialPath != path) {
|
||||
if (initialPath != '' && initialPath != path) {
|
||||
if (validateTimeout) {
|
||||
clearTimeout(validateTimeout)
|
||||
}
|
||||
validateTimeout = setTimeout(async () => {
|
||||
if (
|
||||
(kind == 'flow' &&
|
||||
initialPath != '' &&
|
||||
initialPath != path &&
|
||||
((kind == 'flow' &&
|
||||
(await FlowService.existsFlowByPath({ workspace: $workspaceStore!, path: path }))) ||
|
||||
(kind == 'script' &&
|
||||
(await ScriptService.existsScriptByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
}))) ||
|
||||
(kind == 'resource' &&
|
||||
(await ResourceService.existsResource({
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
}))) ||
|
||||
(kind == 'variable' &&
|
||||
(await VariableService.existsVariable({
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
}))) ||
|
||||
(kind == 'schedule' &&
|
||||
(await ScheduleService.existsSchedule({ workspace: $workspaceStore!, path: path })))
|
||||
(kind == 'script' &&
|
||||
(await ScriptService.existsScriptByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
}))) ||
|
||||
(kind == 'resource' &&
|
||||
(await ResourceService.existsResource({
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
}))) ||
|
||||
(kind == 'variable' &&
|
||||
(await VariableService.existsVariable({
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
}))) ||
|
||||
(kind == 'schedule' &&
|
||||
(await ScheduleService.existsSchedule({ workspace: $workspaceStore!, path: path }))))
|
||||
) {
|
||||
error = 'path already used'
|
||||
} else if (validateName(meta)) {
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
export let property: ModalSchemaProperty = DEFAULT_PROPERTY
|
||||
export let error = ''
|
||||
export let editing = false
|
||||
export let oldArgName: string | undefined
|
||||
export let oldArgName: string | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let modal: Modal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { ScriptService, type Script } from '$lib/gen'
|
||||
|
||||
import { emptySchema, pathIsEmpty, sendUserToast } from '$lib/utils'
|
||||
import { emptySchema, sendUserToast, setQueryWithoutLoad } from '$lib/utils'
|
||||
import { onDestroy } from 'svelte'
|
||||
import ScriptEditor from './ScriptEditor.svelte'
|
||||
import { page } from '$app/stores'
|
||||
@@ -13,7 +13,7 @@
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import Required from './Required.svelte'
|
||||
import RadioButton from './RadioButton.svelte'
|
||||
import { DENO_INIT_CODE, PYTHON_INIT_CODE } from '$lib/script_helpers'
|
||||
import { DENO_INIT_CODE, DENO_INIT_CODE_TRIGGER, initialCode } from '$lib/script_helpers'
|
||||
|
||||
let editor: ScriptEditor
|
||||
let scriptSchema: ScriptSchema
|
||||
@@ -25,15 +25,21 @@
|
||||
let pathError = ''
|
||||
|
||||
$: {
|
||||
$page.url.searchParams.set('state', btoa(JSON.stringify(script)))
|
||||
history.replaceState({}, '', $page.url)
|
||||
setQueryWithoutLoad($page.url, 'state', btoa(JSON.stringify(script)))
|
||||
}
|
||||
|
||||
$: {
|
||||
if (script.language == 'python3') {
|
||||
script.is_trigger = false
|
||||
}
|
||||
}
|
||||
|
||||
if (script.content == '') {
|
||||
initContent(script.language)
|
||||
initContent(script.language, script.is_trigger)
|
||||
}
|
||||
function initContent(lang: string) {
|
||||
script.content = lang == 'deno' ? DENO_INIT_CODE : PYTHON_INIT_CODE
|
||||
|
||||
function initContent(lang: 'deno' | 'python3', is_trigger: boolean) {
|
||||
script.content = initialCode(lang, is_trigger)
|
||||
}
|
||||
|
||||
async function editScript(): Promise<void> {
|
||||
@@ -48,7 +54,8 @@
|
||||
parent_hash: script.hash != '' ? script.hash : undefined,
|
||||
schema: script.schema,
|
||||
is_template: script.is_template,
|
||||
language: script.language
|
||||
language: script.language,
|
||||
is_trigger: script.is_trigger
|
||||
}
|
||||
})
|
||||
sendUserToast(`Success! New script version created with hash ${newHash}`)
|
||||
@@ -176,10 +183,10 @@
|
||||
label="Language"
|
||||
small={true}
|
||||
options={[
|
||||
['Python 3.10', 'python3'],
|
||||
['Typescript (Deno)', 'deno']
|
||||
['Typescript (Deno)', 'deno'],
|
||||
['Python 3.10', 'python3']
|
||||
]}
|
||||
on:change={(e) => initContent(e.detail)}
|
||||
on:change={(e) => initContent(e.detail, script.is_trigger)}
|
||||
bind:value={script.language}
|
||||
/>
|
||||
</div>
|
||||
@@ -221,11 +228,26 @@
|
||||
/>
|
||||
</span></label
|
||||
>
|
||||
|
||||
<label class="block">
|
||||
<span class="text-gray-700 mr-2">Save as template</span>
|
||||
<input type="checkbox" bind:checked={script.is_template} />
|
||||
</label>
|
||||
|
||||
<label class="block">
|
||||
<span class="text-gray-700 mr-2">Save as trigger script</span>
|
||||
<input
|
||||
disabled={script.language == 'python3'}
|
||||
type="checkbox"
|
||||
bind:checked={script.is_trigger}
|
||||
on:change={() => {
|
||||
if (script.content == DENO_INIT_CODE || script.content == DENO_INIT_CODE_TRIGGER) {
|
||||
initContent(script.language, script.is_trigger)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<h3 class="text-gray-700 ">Description rendered</h3>
|
||||
<div
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
import { ScriptService, FlowService, Script } from '$lib/gen'
|
||||
|
||||
import Icon from 'svelte-awesome'
|
||||
@@ -18,6 +17,7 @@
|
||||
export let allowFlow = false
|
||||
export let allowHub = false
|
||||
export let itemKind: 'hub' | 'script' | 'flow' = allowHub ? 'hub' : 'script'
|
||||
export let isTrigger: boolean | undefined = undefined
|
||||
|
||||
let items: { summary: String; path: String; version?: String }[] = []
|
||||
let itemPicker: ItemPicker
|
||||
@@ -48,7 +48,7 @@
|
||||
if (itemKind == 'flow') {
|
||||
items = await FlowService.listFlows({ workspace: $workspaceStore! })
|
||||
} else if (itemKind == 'script') {
|
||||
items = await ScriptService.listScripts({ workspace: $workspaceStore! })
|
||||
items = await ScriptService.listScripts({ workspace: $workspaceStore!, isTrigger })
|
||||
} else {
|
||||
items = $hubScripts ?? []
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
on:click={() => dispatch('new', { language: FlowModuleValue.language.DENO })}
|
||||
/>
|
||||
<FlowScriptPicker
|
||||
disabled={isTrigger}
|
||||
label="New Python {isTrigger ? 'trigger ' : ''}script (3.10)"
|
||||
icon={faCode}
|
||||
iconColor="text-yellow-500"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Schema } from '$lib/common'
|
||||
import { FlowModuleValue, ScriptService, type Flow, type FlowModule } from '$lib/gen'
|
||||
import { DENO_INIT_CODE, DENO_INIT_CODE_TRIGGER, PYTHON_INIT_CODE, PYTHON_INIT_CODE_TRIGGER } from '$lib/script_helpers'
|
||||
import { initialCode } from '$lib/script_helpers'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { derived, get, writable } from 'svelte/store'
|
||||
import { createInlineScriptModuleFromPath, getFirstStepSchema, loadSchemaFromModule } from './utils'
|
||||
@@ -18,12 +18,16 @@ export function initFlow(flow: Flow) {
|
||||
})
|
||||
}
|
||||
|
||||
export const isCopyFirstStepSchemaDisabled = derived(flowStore, (flow: Flow) => {
|
||||
const modules = flow.value.modules
|
||||
const [firstModule] = modules
|
||||
return (
|
||||
modules.length === 0 || (firstModule.value.path === '' && firstModule.value.type === 'script')
|
||||
)
|
||||
export const isCopyFirstStepSchemaDisabled = derived(flowStore, (flow: Flow | undefined) => {
|
||||
if (flow) {
|
||||
const modules = flow.value.modules
|
||||
const [firstModule] = modules
|
||||
return (
|
||||
modules.length === 0 || (firstModule.value.path === '' && firstModule.value.type === 'script')
|
||||
)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
export function addModule() {
|
||||
@@ -51,10 +55,7 @@ export async function pickScript(path: string, step: number) {
|
||||
}
|
||||
|
||||
export async function createInlineScriptModule(language: FlowModuleValue.language, step: number, mode: FlowMode) {
|
||||
const code = language === FlowModuleValue.language.DENO ? (
|
||||
(mode === 'pull' && step == 0) ? DENO_INIT_CODE_TRIGGER : DENO_INIT_CODE) : (
|
||||
(mode === 'pull' && step == 0) ? PYTHON_INIT_CODE_TRIGGER : PYTHON_INIT_CODE
|
||||
)
|
||||
const code = initialCode(language, (mode === 'pull' && step == 0))
|
||||
flowStore.update((flow: Flow) => {
|
||||
flow.value.modules[step].value = {
|
||||
type: FlowModuleValue.type.RAWSCRIPT,
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
import type { IconDefinition } from '@fortawesome/free-brands-svg-icons'
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
export let disabled: boolean = false
|
||||
export let icon: IconDefinition
|
||||
export let label: string
|
||||
export let iconColor: string
|
||||
</script>
|
||||
|
||||
<button type="button" on:click class="default-secondary-button-v2 mb-2 w-full">
|
||||
<button {disabled} type="button" on:click class="default-secondary-button-v2 mb-2 w-full">
|
||||
<Icon data={icon} class={`w-4 h-4 mr-2 -ml-2 ${iconColor}`} />
|
||||
{label}
|
||||
</button>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function loadItems(): Promise<void> {
|
||||
items = await ScriptService.listScripts({ workspace: $workspaceStore! })
|
||||
items = await ScriptService.listScripts({ workspace: $workspaceStore!, isTrigger })
|
||||
}
|
||||
|
||||
$: {
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
} from '$lib/gen'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import { loadSchema } from '$lib/scripts'
|
||||
import { DENO_INIT_CODE, PYTHON_INIT_CODE } from '$lib/script_helpers'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { emptySchema } from '$lib/utils'
|
||||
import { get } from 'svelte/store'
|
||||
@@ -121,16 +120,6 @@ export async function getFirstStepSchema(flow: Flow): Promise<Schema> {
|
||||
return emptySchema()
|
||||
}
|
||||
|
||||
export function createInlineScriptModule(language: FlowModuleValue.language): FlowModuleValue {
|
||||
const code = language === FlowModuleValue.language.DENO ? DENO_INIT_CODE : PYTHON_INIT_CODE
|
||||
|
||||
return {
|
||||
type: FlowModuleValue.type.RAWSCRIPT,
|
||||
content: code,
|
||||
language
|
||||
}
|
||||
}
|
||||
|
||||
export async function getScriptByPath(path: string): Promise<{
|
||||
content: string
|
||||
language: FlowModuleValue.language
|
||||
|
||||
@@ -33,11 +33,6 @@ def main(name: str = "Nicolas Bourbaki",
|
||||
return {"version": version, "splitted": name.split(), "user": user}
|
||||
`
|
||||
|
||||
export const PYTHON_INIT_CODE_TRIGGER = `import wmill
|
||||
|
||||
def main():
|
||||
return [1,2,3]
|
||||
`
|
||||
export const DENO_INIT_CODE = `// only do the following import if you require your script to interact with the windmill
|
||||
// for instance to get a variable or resource
|
||||
// import * as wmill from 'https://deno.land/x/windmill@v${__pkg__.version}/mod.ts'
|
||||
@@ -68,3 +63,9 @@ export async function main() {
|
||||
return [1,2,3]
|
||||
}
|
||||
`
|
||||
|
||||
export function initialCode(language: 'deno' | 'python3', is_trigger: boolean): string {
|
||||
return language === 'deno' ?
|
||||
(is_trigger ? DENO_INIT_CODE_TRIGGER : DENO_INIT_CODE) : PYTHON_INIT_CODE
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { goto } from '$app/navigation'
|
||||
import type { User } from '$lib/gen'
|
||||
import { toast } from '@zerodevx/svelte-toast'
|
||||
import type { Schema } from './common'
|
||||
@@ -194,6 +195,17 @@ export function pathIsEmpty(path: string): boolean {
|
||||
return path == undefined || path.split('/')[2] == ''
|
||||
}
|
||||
|
||||
export async function setQuery(url: URL, key: string, value: string): Promise<void> {
|
||||
url.searchParams.set(key, value)
|
||||
await goto(`?${url.searchParams.toString()}`)
|
||||
}
|
||||
|
||||
export async function setQueryWithoutLoad(url: URL, key: string, value: string): Promise<void> {
|
||||
const nurl = new URL(url.toString())
|
||||
nurl.searchParams.set(key, value)
|
||||
history.replaceState(null, '', nurl.toString())
|
||||
}
|
||||
|
||||
export function groupBy<T>(
|
||||
scripts: T[],
|
||||
toGroup: (t: T) => string,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { page } from '$app/stores'
|
||||
|
||||
import FlowBuilder from '$lib/components/FlowBuilder.svelte'
|
||||
import { initFlow } from '$lib/components/flows/flowStore'
|
||||
import type { Flow } from '$lib/gen'
|
||||
import { emptySchema } from '$lib/utils'
|
||||
|
||||
@@ -20,6 +21,8 @@
|
||||
extra_perms: {},
|
||||
schema: emptySchema()
|
||||
}
|
||||
|
||||
initFlow(flow)
|
||||
</script>
|
||||
|
||||
<FlowBuilder {flow} />
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { emptySchema } from '$lib/utils'
|
||||
import { flattenForloopFlows } from '$lib/components/flows/utils'
|
||||
import { initFlow } from '$lib/components/flows/flowStore'
|
||||
|
||||
const initialState = $page.url.searchParams.get('state')
|
||||
let flowLoadedFromUrl = initialState != undefined ? JSON.parse(atob(initialState)) : undefined
|
||||
@@ -33,6 +34,7 @@
|
||||
})
|
||||
flow = flattenForloopFlows(flow)
|
||||
initialPath = flow.path
|
||||
initFlow(flow)
|
||||
}
|
||||
|
||||
$: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy } from 'svelte'
|
||||
import { JobService, Job, CompletedJob } from '$lib/gen'
|
||||
import { displayDate, displayDaysAgo, forLater, truncateHash } from '$lib/utils'
|
||||
import { displayDate, displayDaysAgo, forLater, setQuery, truncateHash } from '$lib/utils'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { check } from 'svelte-awesome/icons'
|
||||
import {
|
||||
@@ -28,45 +28,76 @@
|
||||
let intervalId: NodeJS.Timer | undefined
|
||||
let path: string = $page.params.path
|
||||
let createdBefore: string | undefined = $page.url.searchParams.get('createdBefore') ?? undefined
|
||||
|
||||
let success: boolean | undefined =
|
||||
$page.url.searchParams.get('success') != undefined
|
||||
? $page.url.searchParams.get('success') == 'true'
|
||||
: undefined
|
||||
let isSkipped: boolean | undefined =
|
||||
$page.url.searchParams.get('is_skipped') != undefined
|
||||
? $page.url.searchParams.get('is_skipped') == 'true'
|
||||
: false
|
||||
|
||||
let showOlderJobs = true
|
||||
// The API returns 30 jobs per page. We use it to display a next page button or not.
|
||||
const jobsPerPage = 30
|
||||
|
||||
let jobKinds: string
|
||||
let jobKindsCat: string | undefined = $page.url.searchParams.get('job_kinds') ?? 'runs'
|
||||
|
||||
$: jobKinds =
|
||||
$page.url.searchParams.get('jobKinds') ??
|
||||
`${CompletedJob.job_kind.SCRIPT},${CompletedJob.job_kind.FLOW}`
|
||||
$: jobKinds = computeJobKinds(jobKindsCat)
|
||||
|
||||
$: jobKinds && $workspaceStore && loadJobs()
|
||||
function computeJobKinds(jobKindsCat: string | undefined): string {
|
||||
console.log('jobKindsCat', jobKindsCat)
|
||||
if (jobKindsCat == 'all') {
|
||||
return `${CompletedJob.job_kind.SCRIPT},${CompletedJob.job_kind.FLOW},${CompletedJob.job_kind.DEPENDENCIES},${CompletedJob.job_kind.PREVIEW},${CompletedJob.job_kind.FLOWPREVIEW},${CompletedJob.job_kind.SCRIPT_HUB}`
|
||||
} else if (jobKindsCat == 'dependencies') {
|
||||
return CompletedJob.job_kind.DEPENDENCIES
|
||||
} else if (jobKindsCat == 'previews') {
|
||||
return `${CompletedJob.job_kind.PREVIEW},${CompletedJob.job_kind.FLOWPREVIEW}`
|
||||
} else {
|
||||
return `${CompletedJob.job_kind.SCRIPT},${CompletedJob.job_kind.FLOW},${CompletedJob.job_kind.SCRIPT_HUB}`
|
||||
}
|
||||
}
|
||||
|
||||
$: $workspaceStore && loadJobs(createdBefore, success, isSkipped, jobKinds)
|
||||
|
||||
const SMALL_ICON_SCALE = 0.7
|
||||
|
||||
async function fetchJobs(
|
||||
createdBefore: string | undefined,
|
||||
createdAfter?: string
|
||||
createdAfter: string | undefined
|
||||
): Promise<Job[]> {
|
||||
return JobService.listJobs({
|
||||
workspace: $workspaceStore!,
|
||||
createdBefore,
|
||||
createdAfter,
|
||||
scriptPathExact: path === '' ? undefined : path,
|
||||
jobKinds
|
||||
jobKinds,
|
||||
success,
|
||||
isSkipped
|
||||
})
|
||||
}
|
||||
|
||||
async function fetchCompletedJobs(createdBefore: string): Promise<CompletedJob[]> {
|
||||
async function fetchCompletedJobs(createdBefore: string | undefined): Promise<CompletedJob[]> {
|
||||
return JobService.listCompletedJobs({
|
||||
workspace: $workspaceStore!,
|
||||
createdBefore,
|
||||
scriptPathExact: path === '' ? undefined : path,
|
||||
jobKinds: jobKinds
|
||||
jobKinds: jobKinds,
|
||||
success,
|
||||
isSkipped
|
||||
})
|
||||
}
|
||||
|
||||
async function loadJobs(): Promise<void> {
|
||||
async function loadJobs(
|
||||
createdBefore: string | undefined,
|
||||
success: boolean | undefined,
|
||||
isSkipped: boolean | undefined,
|
||||
jobKinds: string | undefined
|
||||
): Promise<void> {
|
||||
try {
|
||||
const newJobs = await fetchJobs(createdBefore)
|
||||
console.log(jobKinds, success, isSkipped)
|
||||
const newJobs = await fetchJobs(createdBefore, undefined)
|
||||
showOlderJobs = newJobs.length === jobsPerPage
|
||||
jobs = newJobs
|
||||
} catch (err) {
|
||||
@@ -102,7 +133,10 @@
|
||||
|
||||
$: {
|
||||
if ($workspaceStore) {
|
||||
loadJobs()
|
||||
loadJobs(createdBefore, success, isSkipped, jobKinds)
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId)
|
||||
}
|
||||
intervalId = setInterval(syncer, 5000)
|
||||
}
|
||||
}
|
||||
@@ -128,23 +162,36 @@ the bearer token they use has less privilege."
|
||||
/>
|
||||
|
||||
<div class="max-w-7x">
|
||||
<div class="flex flex-row space-x-4">
|
||||
<select
|
||||
bind:value={success}
|
||||
on:change={async () => await setQuery($page.url, 'success', String(success))}
|
||||
>
|
||||
<option value={undefined}>Successful and not jobs</option>
|
||||
<option value={true}>Only successful jobs</option>
|
||||
<option value={false}>Only non successful jobs</option>
|
||||
</select>
|
||||
<select
|
||||
bind:value={isSkipped}
|
||||
on:change={async () => await setQuery($page.url, 'is_skipped', String(isSkipped))}
|
||||
>
|
||||
<option value={false}>Ignore skipped flow jobs</option>
|
||||
<option value={true}>If a flow jobs, show only if it was not skipped</option>
|
||||
<option value={undefined}>Show flow jobs regardless of being skipped</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="xl:max-w-screen-lg">
|
||||
<Tabs
|
||||
tabs={[
|
||||
[
|
||||
`${CompletedJob.job_kind.SCRIPT},${CompletedJob.job_kind.FLOW},${CompletedJob.job_kind.DEPENDENCIES},${CompletedJob.job_kind.PREVIEW},${CompletedJob.job_kind.FLOWPREVIEW},${CompletedJob.job_kind.SCRIPT_HUB}`,
|
||||
'all'
|
||||
],
|
||||
[
|
||||
`${CompletedJob.job_kind.SCRIPT},${CompletedJob.job_kind.FLOW},${CompletedJob.job_kind.SCRIPT_HUB}`,
|
||||
'runs'
|
||||
],
|
||||
[`${CompletedJob.job_kind.PREVIEW},${CompletedJob.job_kind.FLOWPREVIEW}`, 'previews'],
|
||||
[CompletedJob.job_kind.DEPENDENCIES, 'dependencies']
|
||||
['all', 'all'],
|
||||
['runs', 'runs'],
|
||||
['previews', 'previews'],
|
||||
['dependencies', 'dependencies']
|
||||
]}
|
||||
dflt={1}
|
||||
on:update={(tab) => {
|
||||
goto(`?jobKinds=${tab.detail}`)
|
||||
bind:tab={jobKindsCat}
|
||||
on:update={async (tab) => {
|
||||
await setQuery($page.url, 'job_kinds', tab.detail)
|
||||
}}
|
||||
/>
|
||||
{#if jobs}
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
<p class="text-xs text-gray-600">
|
||||
Pick a script or flow to be triggered by the schedule<Required required={true} />
|
||||
</p>
|
||||
<ScriptPicker allowFlow={true} bind:itemKind bind:scriptPath={script_path} />
|
||||
<ScriptPicker isTrigger={false} allowFlow={true} bind:itemKind bind:scriptPath={script_path} />
|
||||
<div class="max-w-5xl {edit ? '' : 'mt-2 md:mt-6'}">
|
||||
<h2>Arguments</h2>
|
||||
{#if runnable}
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<th>Schedule</th>
|
||||
<th>Script or Flow</th>
|
||||
<th>schedule</th>
|
||||
<th>on/off</th>
|
||||
<th>off/on</th>
|
||||
<th>timezone</th>
|
||||
<th>last edit</th>
|
||||
<th />
|
||||
|
||||
@@ -207,7 +207,7 @@
|
||||
</p>
|
||||
{:else}
|
||||
<div class="grid md:grid-cols-2 gap-4 sm:grid-cols-1 2xl:grid-cols-3">
|
||||
{#each scripts as { summary, path, hash, language, extra_perms, canWrite, lock_error_logs }}
|
||||
{#each scripts as { summary, path, hash, language, extra_perms, canWrite, lock_error_logs, is_trigger }}
|
||||
<div
|
||||
class="flex flex-col justify-between script max-w-lg overflow-visible shadow-sm shadow-blue-100 border border-gray-200 bg-gray-50 py-2"
|
||||
>
|
||||
@@ -227,6 +227,9 @@
|
||||
<div class="mr-3 w-full">
|
||||
<SharedBadge {canWrite} extraPerms={extra_perms} />
|
||||
<Badge twBgColor="bg-blue-200">{language}</Badge>
|
||||
{#if is_trigger}
|
||||
<Badge twBgColor="bg-blue-300">{is_trigger}</Badge>
|
||||
{/if}
|
||||
{#if lock_error_logs}<Badge
|
||||
twBgColor="bg-red-200"
|
||||
tooltip="The script was not deployed due to an error during deployment. See more details about the error on the script page."
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
deleted: false,
|
||||
is_template: false,
|
||||
extra_perms: {},
|
||||
language: 'python3'
|
||||
language: 'deno',
|
||||
is_trigger: false
|
||||
}
|
||||
|
||||
async function loadTemplate(): Promise<void> {
|
||||
|
||||
@@ -120,6 +120,11 @@
|
||||
>Template</span
|
||||
>
|
||||
{/if}
|
||||
{#if script?.is_trigger}
|
||||
<span class="mx-2 bg-blue-500 rounded-md bg-opacity-25 text-sm font-normal px-1 py-px"
|
||||
>Trigger</span
|
||||
>
|
||||
{/if}
|
||||
<SharedBadge canWrite={can_write} extraPerms={script?.extra_perms ?? {}} />
|
||||
{#if deploymentInProgress}
|
||||
<span class="bg-yellow-200 text-gray-700 text-xs rounded px-1 mx-3">
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
</button>
|
||||
{/if}
|
||||
<h3 class="mt-5 text-gray-700">Select a script to run on /windmill command:</h3>
|
||||
<ScriptPicker bind:scriptPath on:select={editSlackCommand} />
|
||||
<ScriptPicker isTrigger={false} bind:scriptPath on:select={editSlackCommand} />
|
||||
|
||||
<p class="text-sm text-gray-700 italic mt-3">
|
||||
A script run from slack /windmill command is run as group 'slack' and hence every variables or
|
||||
|
||||
Reference in New Issue
Block a user