mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-11 00:06:06 +00:00
339 lines
8.0 KiB
TypeScript
339 lines
8.0 KiB
TypeScript
import type { Schema } from '$lib/common'
|
|
import { CompletedJob, Job, Script, ScriptService, type FlowModule, type RawScript } from '$lib/gen'
|
|
import { initialCode } from '$lib/script_helpers'
|
|
import { userStore, workspaceStore } from '$lib/stores'
|
|
import {
|
|
buildExtraLib,
|
|
emptyModule,
|
|
emptySchema,
|
|
getScriptByPath,
|
|
objectToTsType,
|
|
schemaToObject,
|
|
schemaToTsType
|
|
} from '$lib/utils'
|
|
import { get } from 'svelte/store'
|
|
import type ObjectViewer from '../propertyPicker/ObjectViewer.svelte'
|
|
import { flowStateStore, type FlowModuleSchema, type FlowState } from './flowState'
|
|
import { flowStore } from './flowStore'
|
|
import { loadSchemaFromModule } from './utils'
|
|
|
|
export function emptyFlowModuleSchema(): FlowModuleSchema {
|
|
return {
|
|
flowModule: emptyModule(),
|
|
schema: emptySchema()
|
|
}
|
|
}
|
|
|
|
export async function loadFlowModuleSchema(flowModule: FlowModule): Promise<FlowModuleSchema> {
|
|
try {
|
|
const { input_transforms, schema } = await loadSchemaFromModule(flowModule)
|
|
|
|
flowModule.input_transforms = input_transforms
|
|
|
|
return { flowModule, schema }
|
|
} catch (e) {
|
|
return { flowModule, schema: emptySchema() }
|
|
}
|
|
}
|
|
|
|
export async function pickScript(path: string): Promise<FlowModuleSchema> {
|
|
const flowModule: FlowModule = {
|
|
value: { type: 'script', path },
|
|
input_transforms: {}
|
|
}
|
|
|
|
return await loadFlowModuleSchema(flowModule)
|
|
}
|
|
|
|
export async function createInlineScriptModule({
|
|
language,
|
|
kind,
|
|
subkind
|
|
}: {
|
|
language: RawScript.language
|
|
kind: Script.kind
|
|
subkind: 'pgsql' | 'flow'
|
|
}): Promise<FlowModuleSchema> {
|
|
const code = initialCode(language, kind, subkind)
|
|
|
|
const flowModule: FlowModule = {
|
|
value: { type: 'rawscript', content: code, language },
|
|
input_transforms: {}
|
|
}
|
|
|
|
return await loadFlowModuleSchema(flowModule)
|
|
}
|
|
|
|
export async function createLoop(): Promise<FlowModuleSchema> {
|
|
const loopFlowModule: FlowModule = {
|
|
value: {
|
|
type: 'forloopflow',
|
|
modules: [],
|
|
iterator: { type: 'javascript', expr: 'previous_result' },
|
|
skip_failures: true
|
|
},
|
|
input_transforms: {}
|
|
}
|
|
|
|
const { flowModule, schema } = await loadFlowModuleSchema(loopFlowModule)
|
|
|
|
return {
|
|
flowModule,
|
|
schema,
|
|
childFlowModules: [emptyFlowModuleSchema()],
|
|
previewResult: undefined
|
|
}
|
|
}
|
|
|
|
export async function fork(flowModule: FlowModule): Promise<FlowModuleSchema> {
|
|
if (flowModule.value.type !== 'script') {
|
|
throw new Error('Can only fork a script module')
|
|
}
|
|
const fm = await createInlineScriptModuleFromPath(flowModule.value.path ?? '')
|
|
|
|
return await loadFlowModuleSchema(fm)
|
|
}
|
|
|
|
export async function createInlineScriptModuleFromPath(path: string): Promise<FlowModule> {
|
|
const { content, language } = await getScriptByPath(path)
|
|
|
|
return {
|
|
value: {
|
|
type: 'rawscript',
|
|
language: language as RawScript.language,
|
|
content: content,
|
|
path
|
|
},
|
|
input_transforms: {}
|
|
}
|
|
}
|
|
|
|
export async function createScriptFromInlineScript({
|
|
flowModule,
|
|
suffix,
|
|
schema
|
|
}: {
|
|
flowModule: FlowModule
|
|
suffix: string
|
|
schema: Schema
|
|
}): Promise<FlowModuleSchema> {
|
|
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)
|
|
|
|
await ScriptService.createScript({
|
|
workspace: get(workspaceStore)!,
|
|
requestBody: {
|
|
path: availablePath,
|
|
summary: '',
|
|
description,
|
|
content: flowModule.value.content,
|
|
parent_hash: undefined,
|
|
schema,
|
|
is_template: false,
|
|
language: flowModule.value.language
|
|
}
|
|
})
|
|
|
|
return pickScript(availablePath)
|
|
}
|
|
|
|
async function findNextAvailablePath(path: string): Promise<string> {
|
|
try {
|
|
await ScriptService.getScriptByPath({
|
|
workspace: get(workspaceStore)!,
|
|
path
|
|
})
|
|
|
|
const [_, version] = path.split(/.*_([0-9]*)/)
|
|
|
|
if (version.length > 0) {
|
|
path = path.slice(0, -(version.length + 1))
|
|
}
|
|
|
|
path = `${path}_${Number(version) + 1}`
|
|
|
|
return findNextAvailablePath(path)
|
|
} catch (e) {
|
|
// Catching an error means the path is available
|
|
return path
|
|
}
|
|
}
|
|
|
|
export function isEmptyFlowModule(flowModule: FlowModule): boolean {
|
|
return flowModule.value.type === 'script' && flowModule.value.path === ''
|
|
}
|
|
|
|
type Result = any
|
|
|
|
type PickableProperties = {
|
|
flow_input?: Object
|
|
previous_result: Result | undefined
|
|
step: Result[]
|
|
}
|
|
|
|
type StepPropPicker = {
|
|
pickableProperties: PickableProperties
|
|
extraLib: string
|
|
}
|
|
|
|
export const NEVER_TESTED_THIS_FAR = "never tested this far"
|
|
|
|
export function getStepPropPicker(
|
|
indexes: number[],
|
|
flowInputSchema: Schema,
|
|
flowState: FlowState,
|
|
args: Record<string, any>
|
|
): StepPropPicker {
|
|
const isInsideLoop: boolean = indexes.length > 1
|
|
const [parentIndex, childIndex] = indexes
|
|
|
|
const flowInput = schemaToObject(flowInputSchema, args)
|
|
const results = getPreviousResults(flowState.modules, parentIndex)
|
|
|
|
const lastResult = parentIndex == 0 ? flowInput : (
|
|
results.length > 0 ? results[results.length - 1] : NEVER_TESTED_THIS_FAR)
|
|
|
|
if (isInsideLoop) {
|
|
const forLoopFlowInput = {
|
|
...flowInput,
|
|
iter: {
|
|
value: "Iteration's value",
|
|
index: "Iteration's index"
|
|
}
|
|
}
|
|
|
|
if (Array.isArray(lastResult) && lastResult.length > 0) {
|
|
const last = lastResult[lastResult.length - 1]
|
|
|
|
forLoopFlowInput.iter = {
|
|
value: last,
|
|
index: `Iteration's index (0 to ${lastResult.length - 1})`
|
|
}
|
|
}
|
|
|
|
const extraLib = buildExtraLib(objectToTsType(forLoopFlowInput), undefined)
|
|
|
|
return {
|
|
extraLib,
|
|
pickableProperties: {
|
|
flow_input: forLoopFlowInput,
|
|
previous_result: {},
|
|
step: [],
|
|
}
|
|
}
|
|
} else {
|
|
const extraLib = buildExtraLib(schemaToTsType(flowInputSchema), objectToTsType(lastResult))
|
|
|
|
return {
|
|
extraLib,
|
|
pickableProperties: {
|
|
flow_input: flowInput,
|
|
previous_result: lastResult,
|
|
step: results,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getPreviousResults(
|
|
flowModuleSchemas: FlowModuleSchema[] | undefined,
|
|
target: number
|
|
): Result[] {
|
|
if (!Array.isArray(flowModuleSchemas) || target < 1) {
|
|
return []
|
|
}
|
|
|
|
const results = extractPreviewResults(flowModuleSchemas)
|
|
return results.splice(0, target)
|
|
}
|
|
|
|
function extractPreviewResults(flowModuleSchemas: FlowModuleSchema[]) {
|
|
return flowModuleSchemas.map((fms) => fms.previewResult)
|
|
}
|
|
|
|
export type JobResult = {
|
|
job?: Job
|
|
innerJobs?: JobResult[]
|
|
loopJobs?: JobResult[]
|
|
}
|
|
|
|
export function mapJobResultsToFlowState(
|
|
jobs: JobResult,
|
|
config: 'upto' | 'justthis',
|
|
parentIndex: number,
|
|
j: number | undefined
|
|
): void {
|
|
if (config === 'justthis') {
|
|
const job = jobs.job as CompletedJob
|
|
|
|
flowStateStore.update((flowState: FlowState) => {
|
|
if (flowState.modules) {
|
|
const childFlowModules = flowState.modules[parentIndex].childFlowModules
|
|
if (j && childFlowModules) {
|
|
childFlowModules[j].previewResult = job.result
|
|
flowState.modules[parentIndex].childFlowModules = childFlowModules
|
|
} else {
|
|
flowState.modules[parentIndex].previewResult = job.result
|
|
}
|
|
}
|
|
|
|
return flowState
|
|
})
|
|
} else {
|
|
if (!Array.isArray(jobs.innerJobs) || jobs.innerJobs.length === 0) {
|
|
return
|
|
}
|
|
|
|
const results = jobs.innerJobs.map(({ job, loopJobs }) => {
|
|
if (Array.isArray(loopJobs) && loopJobs.length > 0) {
|
|
return loopJobs.map(({ job }) => {
|
|
if (job && 'result' in job) {
|
|
return job.result
|
|
}
|
|
})
|
|
} else {
|
|
if (job && 'result' in job) {
|
|
return job.result
|
|
}
|
|
}
|
|
})
|
|
|
|
flowStateStore.update((flowState: FlowState) => {
|
|
if (!Array.isArray(flowState.modules)) {
|
|
return flowState
|
|
}
|
|
|
|
const modules = flowState.modules.map((flowModuleSchema: FlowModuleSchema, index: number) => {
|
|
if (index <= parentIndex) {
|
|
flowModuleSchema.previewResult = results[index]
|
|
}
|
|
|
|
return flowModuleSchema
|
|
})
|
|
|
|
return {
|
|
modules,
|
|
failureModule: flowState.failureModule
|
|
}
|
|
})
|
|
}
|
|
}
|