mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 08:01:35 +00:00
feat: share trigger drafts with global ai
This commit is contained in:
@@ -50,6 +50,46 @@ vi.mock('$lib/gen', async () => {
|
||||
existsFlowByPath: vi.fn(async () => false),
|
||||
listFlows: vi.fn(async () => [])
|
||||
}),
|
||||
ScheduleService: wrapService(actual.ScheduleService, {
|
||||
existsSchedule: vi.fn(async () => false),
|
||||
listSchedules: vi.fn(async () => [])
|
||||
}),
|
||||
HttpTriggerService: wrapService(actual.HttpTriggerService, {
|
||||
existsHttpTrigger: vi.fn(async () => false),
|
||||
listHttpTriggers: vi.fn(async () => [])
|
||||
}),
|
||||
WebsocketTriggerService: wrapService(actual.WebsocketTriggerService, {
|
||||
existsWebsocketTrigger: vi.fn(async () => false),
|
||||
listWebsocketTriggers: vi.fn(async () => [])
|
||||
}),
|
||||
KafkaTriggerService: wrapService(actual.KafkaTriggerService, {
|
||||
existsKafkaTrigger: vi.fn(async () => false),
|
||||
listKafkaTriggers: vi.fn(async () => [])
|
||||
}),
|
||||
NatsTriggerService: wrapService(actual.NatsTriggerService, {
|
||||
existsNatsTrigger: vi.fn(async () => false),
|
||||
listNatsTriggers: vi.fn(async () => [])
|
||||
}),
|
||||
PostgresTriggerService: wrapService(actual.PostgresTriggerService, {
|
||||
existsPostgresTrigger: vi.fn(async () => false),
|
||||
listPostgresTriggers: vi.fn(async () => [])
|
||||
}),
|
||||
MqttTriggerService: wrapService(actual.MqttTriggerService, {
|
||||
existsMqttTrigger: vi.fn(async () => false),
|
||||
listMqttTriggers: vi.fn(async () => [])
|
||||
}),
|
||||
SqsTriggerService: wrapService(actual.SqsTriggerService, {
|
||||
existsSqsTrigger: vi.fn(async () => false),
|
||||
listSqsTriggers: vi.fn(async () => [])
|
||||
}),
|
||||
GcpTriggerService: wrapService(actual.GcpTriggerService, {
|
||||
existsGcpTrigger: vi.fn(async () => false),
|
||||
listGcpTriggers: vi.fn(async () => [])
|
||||
}),
|
||||
AzureTriggerService: wrapService(actual.AzureTriggerService, {
|
||||
existsAzureTrigger: vi.fn(async () => false),
|
||||
listAzureTriggers: vi.fn(async () => [])
|
||||
}),
|
||||
AppService: wrapService(actual.AppService, {
|
||||
existsApp: vi.fn(async () => false)
|
||||
}),
|
||||
@@ -156,6 +196,83 @@ describe('global AI tools', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('writes schedule drafts into the shared UserDraft store', async () => {
|
||||
await callGlobalTool('write_schedule', {
|
||||
path: 'f/schedules/daily',
|
||||
summary: 'Daily schedule',
|
||||
schedule: '0 0 12 * * *',
|
||||
timezone: 'UTC',
|
||||
script_path: 'f/scripts/hello',
|
||||
is_flow: false,
|
||||
args: { name: 'Ada' },
|
||||
enabled: true
|
||||
})
|
||||
|
||||
expect(
|
||||
UserDraft.get<any>('trigger_schedule', 'f/schedules/daily', { workspace: WORKSPACE })
|
||||
).toMatchObject({
|
||||
path: 'f/schedules/daily',
|
||||
summary: 'Daily schedule',
|
||||
script_path: 'f/scripts/hello',
|
||||
is_flow: false
|
||||
})
|
||||
|
||||
const raw = await callGlobalTool('read_workspace_item', {
|
||||
type: 'schedule',
|
||||
path: 'f/schedules/daily'
|
||||
})
|
||||
|
||||
expect(JSON.parse(raw)).toMatchObject({
|
||||
type: 'schedule',
|
||||
path: 'f/schedules/daily',
|
||||
summary: 'Daily schedule',
|
||||
value: expect.objectContaining({
|
||||
schedule: '0 0 12 * * *',
|
||||
timezone: 'UTC'
|
||||
}),
|
||||
isDraft: true
|
||||
})
|
||||
})
|
||||
|
||||
it('writes trigger drafts into the shared trigger UserDraft kind', async () => {
|
||||
await callGlobalTool('write_trigger', {
|
||||
kind: 'http',
|
||||
config: {
|
||||
path: 'f/triggers/hook',
|
||||
summary: 'Hook trigger',
|
||||
script_path: 'f/scripts/hello',
|
||||
route_path: 'api/hook',
|
||||
is_flow: false,
|
||||
http_method: 'post',
|
||||
authentication_method: 'none',
|
||||
is_static_website: false
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
UserDraft.get<any>('trigger_http', 'f/triggers/hook', { workspace: WORKSPACE })
|
||||
).toMatchObject({
|
||||
path: 'f/triggers/hook',
|
||||
summary: 'Hook trigger',
|
||||
route_path: 'api/hook',
|
||||
is_flow: false
|
||||
})
|
||||
|
||||
const raw = await callGlobalTool('list_workspace_items', {
|
||||
types: ['trigger']
|
||||
})
|
||||
|
||||
expect(JSON.parse(raw)).toEqual([
|
||||
expect.objectContaining({
|
||||
type: 'trigger',
|
||||
triggerKind: 'http',
|
||||
path: 'f/triggers/hook',
|
||||
summary: 'Hook trigger',
|
||||
isDraft: true
|
||||
})
|
||||
])
|
||||
})
|
||||
|
||||
it('redacts variable draft values when reading workspace items', async () => {
|
||||
await callGlobalTool('write_variable', {
|
||||
path: 'f/secrets/api_key',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FlowService, ScriptService } from '$lib/gen'
|
||||
import type { Flow, NewScript, Script, ScriptLang } from '$lib/gen/types.gen'
|
||||
import type { Flow, NewSchedule, NewScript, Script, ScriptLang } from '$lib/gen/types.gen'
|
||||
import { UserDraft, type UserDraftItemKind, type UserDraftListEntry } from '$lib/userDraft.svelte'
|
||||
import { emptySchema } from '$lib/utils'
|
||||
import {
|
||||
@@ -7,14 +7,53 @@ import {
|
||||
globalDraftStore,
|
||||
type AppDraftValue,
|
||||
type FlowDraftValue,
|
||||
type TriggerRequestBody,
|
||||
type TriggerKind,
|
||||
type WorkspaceItem,
|
||||
type WorkspaceItemType
|
||||
} from './draftStore.svelte'
|
||||
|
||||
type SharedWorkspaceItemType = 'script' | 'flow' | 'app'
|
||||
type SharedWorkspaceItemType = 'script' | 'flow' | 'app' | 'schedule' | 'trigger'
|
||||
|
||||
const SHARED_DRAFT_KINDS = ['script', 'flow', 'raw_app'] as const satisfies UserDraftItemKind[]
|
||||
const TRIGGER_DRAFT_KIND_BY_TRIGGER_KIND = {
|
||||
http: 'trigger_http',
|
||||
websocket: 'trigger_websocket',
|
||||
kafka: 'trigger_kafka',
|
||||
nats: 'trigger_nats',
|
||||
postgres: 'trigger_postgres',
|
||||
mqtt: 'trigger_mqtt',
|
||||
sqs: 'trigger_sqs',
|
||||
gcp: 'trigger_gcp',
|
||||
azure: 'trigger_azure'
|
||||
} as const satisfies Record<TriggerKind, UserDraftItemKind>
|
||||
|
||||
const TRIGGER_KIND_BY_DRAFT_KIND = {
|
||||
trigger_http: 'http',
|
||||
trigger_websocket: 'websocket',
|
||||
trigger_kafka: 'kafka',
|
||||
trigger_nats: 'nats',
|
||||
trigger_postgres: 'postgres',
|
||||
trigger_mqtt: 'mqtt',
|
||||
trigger_sqs: 'sqs',
|
||||
trigger_gcp: 'gcp',
|
||||
trigger_azure: 'azure'
|
||||
} as const satisfies Partial<Record<UserDraftItemKind, TriggerKind>>
|
||||
|
||||
const SHARED_DRAFT_KINDS = [
|
||||
'script',
|
||||
'flow',
|
||||
'raw_app',
|
||||
'trigger_schedule',
|
||||
'trigger_http',
|
||||
'trigger_websocket',
|
||||
'trigger_kafka',
|
||||
'trigger_nats',
|
||||
'trigger_postgres',
|
||||
'trigger_mqtt',
|
||||
'trigger_sqs',
|
||||
'trigger_gcp',
|
||||
'trigger_azure'
|
||||
] as const satisfies UserDraftItemKind[]
|
||||
const DEFAULT_SCRIPT_LANGUAGE: ScriptLang = 'bun'
|
||||
const DEFAULT_APP_DATA = { tables: [], datatable: undefined, schema: undefined }
|
||||
|
||||
@@ -23,11 +62,30 @@ function clone<T>(value: T): T {
|
||||
}
|
||||
|
||||
function isSharedWorkspaceItemType(type: WorkspaceItemType): type is SharedWorkspaceItemType {
|
||||
return type === 'script' || type === 'flow' || type === 'app'
|
||||
return (
|
||||
type === 'script' ||
|
||||
type === 'flow' ||
|
||||
type === 'app' ||
|
||||
type === 'schedule' ||
|
||||
type === 'trigger'
|
||||
)
|
||||
}
|
||||
|
||||
function sharedDraftKind(type: SharedWorkspaceItemType): UserDraftItemKind {
|
||||
return type === 'app' ? 'raw_app' : type
|
||||
function sharedDraftKind(
|
||||
type: SharedWorkspaceItemType,
|
||||
triggerKind?: TriggerKind
|
||||
): UserDraftItemKind | undefined {
|
||||
switch (type) {
|
||||
case 'script':
|
||||
case 'flow':
|
||||
return type
|
||||
case 'app':
|
||||
return 'raw_app'
|
||||
case 'schedule':
|
||||
return 'trigger_schedule'
|
||||
case 'trigger':
|
||||
return triggerKind ? TRIGGER_DRAFT_KIND_BY_TRIGGER_KIND[triggerKind] : undefined
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeAppDraftValue(value: AppDraftValue): AppDraftValue {
|
||||
@@ -39,6 +97,18 @@ function normalizeAppDraftValue(value: AppDraftValue): AppDraftValue {
|
||||
}
|
||||
}
|
||||
|
||||
function getItemSummary(value: unknown): string | undefined {
|
||||
return ((value as { summary?: string | null } | undefined)?.summary ?? undefined) || undefined
|
||||
}
|
||||
|
||||
function applyItemSummary<T extends object>(value: T, summary: string | undefined): T {
|
||||
const draft = value as T & { summary?: string | null }
|
||||
if (draft.summary === undefined && summary !== undefined) {
|
||||
draft.summary = summary
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function scriptDraftToWorkspaceItem(path: string, draft: NewScript): WorkspaceItem {
|
||||
return {
|
||||
type: 'script',
|
||||
@@ -75,6 +145,31 @@ function appDraftToWorkspaceItem(path: string, draft: AppDraftValue): WorkspaceI
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleDraftToWorkspaceItem(path: string, draft: NewSchedule): WorkspaceItem {
|
||||
return {
|
||||
type: 'schedule',
|
||||
path,
|
||||
summary: draft.summary ?? undefined,
|
||||
value: clone(draft),
|
||||
isDraft: true
|
||||
}
|
||||
}
|
||||
|
||||
function triggerDraftToWorkspaceItem(
|
||||
kind: TriggerKind,
|
||||
path: string,
|
||||
draft: TriggerRequestBody
|
||||
): WorkspaceItem {
|
||||
return {
|
||||
type: 'trigger',
|
||||
triggerKind: kind,
|
||||
path,
|
||||
summary: getItemSummary(draft),
|
||||
value: clone(draft),
|
||||
isDraft: true
|
||||
}
|
||||
}
|
||||
|
||||
function sharedDraftEntryToWorkspaceItem(entry: UserDraftListEntry): WorkspaceItem | undefined {
|
||||
switch (entry.itemKind) {
|
||||
case 'script':
|
||||
@@ -83,8 +178,13 @@ function sharedDraftEntryToWorkspaceItem(entry: UserDraftListEntry): WorkspaceIt
|
||||
return flowDraftToWorkspaceItem(entry.path, entry.value as Flow)
|
||||
case 'raw_app':
|
||||
return appDraftToWorkspaceItem(entry.path, entry.value as AppDraftValue)
|
||||
case 'trigger_schedule':
|
||||
return scheduleDraftToWorkspaceItem(entry.path, entry.value as NewSchedule)
|
||||
default:
|
||||
return undefined
|
||||
const triggerKind = TRIGGER_KIND_BY_DRAFT_KIND[entry.itemKind]
|
||||
return triggerKind
|
||||
? triggerDraftToWorkspaceItem(triggerKind, entry.path, entry.value as TriggerRequestBody)
|
||||
: undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,6 +260,33 @@ function appItemToUserDraft(item: WorkspaceItem): AppDraftValue {
|
||||
})
|
||||
}
|
||||
|
||||
function scheduleItemToUserDraft(item: WorkspaceItem): NewSchedule {
|
||||
const value = item.value as NewSchedule | undefined
|
||||
if (!value) {
|
||||
throw new Error(`Draft schedule "${item.path}" is missing value.`)
|
||||
}
|
||||
const draft = {
|
||||
...clone(value),
|
||||
path: item.path
|
||||
}
|
||||
return applyItemSummary(draft, item.summary)
|
||||
}
|
||||
|
||||
function triggerItemToUserDraft(item: WorkspaceItem): TriggerRequestBody {
|
||||
const value = item.value as TriggerRequestBody | undefined
|
||||
if (!item.triggerKind) {
|
||||
throw new Error(`Draft trigger "${item.path}" is missing trigger kind.`)
|
||||
}
|
||||
if (!value) {
|
||||
throw new Error(`Draft trigger "${item.path}" is missing value.`)
|
||||
}
|
||||
const draft = {
|
||||
...clone(value),
|
||||
path: item.path
|
||||
}
|
||||
return applyItemSummary(draft, item.summary)
|
||||
}
|
||||
|
||||
async function loadExistingScript(
|
||||
workspace: string,
|
||||
path: string,
|
||||
@@ -181,9 +308,11 @@ async function loadExistingFlow(
|
||||
function getSharedDraft(
|
||||
workspace: string,
|
||||
type: SharedWorkspaceItemType,
|
||||
path: string
|
||||
path: string,
|
||||
triggerKind?: TriggerKind
|
||||
): WorkspaceItem | undefined {
|
||||
const itemKind = sharedDraftKind(type)
|
||||
const itemKind = sharedDraftKind(type, triggerKind)
|
||||
if (!itemKind) return undefined
|
||||
const draft = UserDraft.get(itemKind, path, { workspace })
|
||||
if (draft === undefined) return undefined
|
||||
|
||||
@@ -194,6 +323,12 @@ function getSharedDraft(
|
||||
return flowDraftToWorkspaceItem(path, draft as Flow)
|
||||
case 'app':
|
||||
return appDraftToWorkspaceItem(path, draft as AppDraftValue)
|
||||
case 'schedule':
|
||||
return scheduleDraftToWorkspaceItem(path, draft as NewSchedule)
|
||||
case 'trigger':
|
||||
return triggerKind
|
||||
? triggerDraftToWorkspaceItem(triggerKind, path, draft as TriggerRequestBody)
|
||||
: undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,13 +359,35 @@ async function setSharedDraft(
|
||||
UserDraft.save('raw_app', item.path, draft, { workspace })
|
||||
return appDraftToWorkspaceItem(item.path, draft)
|
||||
}
|
||||
case 'schedule': {
|
||||
const draft = scheduleItemToUserDraft(item)
|
||||
UserDraft.save('trigger_schedule', item.path, draft, { workspace })
|
||||
return scheduleDraftToWorkspaceItem(item.path, draft)
|
||||
}
|
||||
case 'trigger': {
|
||||
if (!item.triggerKind) {
|
||||
throw new Error(`Draft trigger "${item.path}" is missing trigger kind.`)
|
||||
}
|
||||
const draft = triggerItemToUserDraft(item)
|
||||
UserDraft.save(TRIGGER_DRAFT_KIND_BY_TRIGGER_KIND[item.triggerKind], item.path, draft, {
|
||||
workspace
|
||||
})
|
||||
return triggerDraftToWorkspaceItem(item.triggerKind, item.path, draft)
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported shared draft type: ${item.type}`)
|
||||
}
|
||||
}
|
||||
|
||||
function deleteSharedDraft(workspace: string, type: SharedWorkspaceItemType, path: string): void {
|
||||
UserDraft.remove(sharedDraftKind(type), path, { workspace })
|
||||
function deleteSharedDraft(
|
||||
workspace: string,
|
||||
type: SharedWorkspaceItemType,
|
||||
path: string,
|
||||
triggerKind?: TriggerKind
|
||||
): void {
|
||||
const itemKind = sharedDraftKind(type, triggerKind)
|
||||
if (!itemKind) return
|
||||
UserDraft.remove(itemKind, path, { workspace })
|
||||
}
|
||||
|
||||
export function getGlobalDraft(
|
||||
@@ -240,7 +397,7 @@ export function getGlobalDraft(
|
||||
triggerKind?: TriggerKind
|
||||
): WorkspaceItem | undefined {
|
||||
if (isSharedWorkspaceItemType(type)) {
|
||||
const shared = getSharedDraft(workspace, type, path)
|
||||
const shared = getSharedDraft(workspace, type, path, triggerKind)
|
||||
if (shared) return shared
|
||||
}
|
||||
return globalDraftStore.getDraft(workspace, type, path, triggerKind)
|
||||
@@ -290,7 +447,7 @@ export function deleteGlobalDraft(
|
||||
triggerKind?: TriggerKind
|
||||
): void {
|
||||
if (isSharedWorkspaceItemType(type)) {
|
||||
deleteSharedDraft(workspace, type, path)
|
||||
deleteSharedDraft(workspace, type, path, triggerKind)
|
||||
}
|
||||
globalDraftStore.deleteDraft(workspace, type, path, triggerKind)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user