Files
windmill/frontend/src/lib/components/flows/flowStateUtils.ts
2022-12-06 23:38:06 +01:00

257 lines
6.6 KiB
TypeScript

import type { Schema } from '$lib/common'
import { Script, ScriptService, type FlowModule, type PathFlow, type PathScript, type RawScript } from '$lib/gen'
import { initialCode } from '$lib/script_helpers'
import { userStore, workspaceStore } from '$lib/stores'
import { getScriptByPath } from '$lib/utils'
import { get } from 'svelte/store'
import { flowStateStore, type FlowModuleState } from './flowState'
import { flowStore } from './flowStore'
import {
charsToNumber,
emptyFlowModuleState,
findNextAvailablePath,
loadSchemaFromModule,
NEVER_TESTED_THIS_FAR,
numberToChars
} from './utils'
import { Mutex } from 'async-mutex'
export async function loadFlowModuleState(flowModule: FlowModule): Promise<FlowModuleState> {
try {
const { input_transforms, schema } = await loadSchemaFromModule(flowModule)
if (flowModule.value.type == 'script' || flowModule.value.type == 'rawscript' || flowModule.value.type == 'flow') {
flowModule.value.input_transforms = input_transforms
}
return { schema, previewResult: NEVER_TESTED_THIS_FAR }
} catch (e) {
console.error(e)
return emptyFlowModuleState()
}
}
export const idMutex = new Mutex()
export function getNextId(currentKeys: string[]): string {
const max = currentKeys.reduce((acc, key) => {
if (key === 'failure' || key.includes('branch') || key.includes('loop')) {
return acc
} else {
const num = charsToNumber(key)
return Math.max(acc, num + 1)
}
}, 0)
return numberToChars(max)
}
// Computes the next available id
export function nextId(): string {
const flowState = get(flowStateStore)
const max = Object.keys(flowState).reduce((acc, key) => {
if (key === 'failure' || key.includes('branch') || key.includes('loop')) {
return acc
} else {
const num = charsToNumber(key)
return Math.max(acc, num + 1)
}
}, 0)
return numberToChars(max)
}
export async function pickScript(
path: string,
summary: string,
id: string,
hash?: string
): Promise<[FlowModule & { value: PathScript }, FlowModuleState]> {
const flowModule: FlowModule & { value: PathScript } = {
id,
value: { type: 'script', path, hash, input_transforms: {} },
summary
}
return [flowModule, await loadFlowModuleState(flowModule)]
}
export async function pickFlow(
path: string,
summary: string,
id: string,
): Promise<[FlowModule & { value: PathFlow }, FlowModuleState]> {
const flowModule: FlowModule & { value: PathFlow } = {
id,
value: { type: 'flow', path, input_transforms: {} },
summary
}
return [flowModule, await loadFlowModuleState(flowModule)]
}
export async function createInlineScriptModule(
language: RawScript.language,
kind: Script.kind,
subkind: 'pgsql' | 'flow',
id: string
): Promise<[FlowModule, FlowModuleState]> {
const code = initialCode(language, kind, subkind)
const flowModule: FlowModule = {
id,
value: { type: 'rawscript', content: code, language, input_transforms: {} }
}
return [flowModule, await loadFlowModuleState(flowModule)]
}
export async function createLoop(id: string): Promise<[FlowModule, FlowModuleState]> {
const loopFlowModule: FlowModule = {
id,
value: {
type: 'forloopflow',
modules: [],
iterator: { type: 'javascript', expr: '["dynamic or static array"]' },
skip_failures: true
}
}
const flowModuleState = await loadFlowModuleState(loopFlowModule)
return [loopFlowModule, flowModuleState]
}
export async function createBranches(id: string): Promise<[FlowModule, FlowModuleState]> {
const branchesFlowModules: FlowModule = {
id,
value: {
type: 'branchone',
branches: [],
default: []
},
summary: ''
}
const flowModuleState = await loadFlowModuleState(branchesFlowModules)
return [branchesFlowModules, flowModuleState]
}
export async function createBranchAll(id: string): Promise<[FlowModule, FlowModuleState]> {
const branchesFlowModules: FlowModule = {
id,
value: {
type: 'branchall',
branches: []
},
summary: ''
}
const flowModuleState = await loadFlowModuleState(branchesFlowModules)
return [branchesFlowModules, flowModuleState]
}
export async function createFlow(id: string): Promise<[FlowModule, FlowModuleState]> {
const flowFlowModules: FlowModule = {
id,
value: {
type: 'flow',
path: '',
input_transforms: {},
},
summary: ''
}
const flowModuleState = await loadFlowModuleState(flowFlowModules)
return [flowFlowModules, flowModuleState]
}
export async function fork(
flowModule: FlowModule
): Promise<[FlowModule & { value: RawScript }, FlowModuleState]> {
if (flowModule.value.type !== 'script') {
throw new Error('Can only fork a script module')
}
const forkedFlowModule = await createInlineScriptModuleFromPath(
flowModule.value.path ?? '',
flowModule.id
)
const flowModuleState = await loadFlowModuleState(forkedFlowModule)
return [forkedFlowModule, flowModuleState]
}
async function createInlineScriptModuleFromPath(
path: string,
id: string
): Promise<FlowModule & { value: RawScript }> {
const { content, language } = await getScriptByPath(path)
return {
id,
value: {
type: 'rawscript',
language: language as RawScript.language,
content: content,
path,
input_transforms: {}
}
}
}
export function emptyModule(flow?: boolean): FlowModule {
return {
id: nextId(),
value: { type: 'identity', flow }
}
}
export async function createScriptFromInlineScript(
flowModule: FlowModule,
suffix: string,
schema: Schema
): Promise<[FlowModule & { value: PathScript }, FlowModuleState]> {
const flow = get(flowStore)
const user = get(userStore)
if (flowModule.value.type != 'rawscript') {
throw new Error("Can't create script from non-inline script")
}
const originalScriptPath = flowModule.value.path
const wasForked = Boolean(originalScriptPath)
if (wasForked && originalScriptPath) {
const [first, second, ...others] = originalScriptPath.split('/')
suffix = others.join('/')
}
const path = `${flow.path}/${suffix}`
const forkedDescription = wasForked ? `as a fork of ${originalScriptPath}` : ''
const description = `This script was edited in place of flow ${flow.path} ${forkedDescription} by ${user?.username}.`
const availablePath = await findNextAvailablePath(path)
const hash = await ScriptService.createScript({
workspace: get(workspaceStore)!,
requestBody: {
path: availablePath,
summary: flowModule.summary ?? '',
description,
content: flowModule.value.content,
parent_hash: undefined,
schema,
is_template: false,
language: flowModule.value.language
}
})
return pickScript(availablePath, flowModule.summary ?? '', flowModule.id, hash)
}
export function deleteFlowStateById(id: string) {
flowStateStore.update((fss) => {
delete fss[id]
return fss
})
}