diff --git a/ui/heyapi-sdk-plugin/config.ts b/ui/heyapi-sdk-plugin/config.ts new file mode 100644 index 0000000000..bffae76a84 --- /dev/null +++ b/ui/heyapi-sdk-plugin/config.ts @@ -0,0 +1,18 @@ +import {definePluginConfig} from "@hey-api/openapi-ts"; + +import {handler} from "./plugin"; +import type {KestraSdkPlugin} from "./types"; + +export const defaultConfig: KestraSdkPlugin["Config"] = { + config: { + output: "kestra-sdk", + }, + dependencies: ["@hey-api/typescript", "@hey-api/client-axios", "@hey-api/sdk"], + handler, + name: "@kestra-io/sdk-plugin", +}; + +/** + * Type helper for `my-plugin` plugin, returns {@link Plugin.Config} object + */ +export const defineKestraHeyConfig = definePluginConfig(defaultConfig); \ No newline at end of file diff --git a/ui/heyapi-sdk-plugin/index.ts b/ui/heyapi-sdk-plugin/index.ts new file mode 100644 index 0000000000..935767ab5f --- /dev/null +++ b/ui/heyapi-sdk-plugin/index.ts @@ -0,0 +1,2 @@ +export {defaultConfig, defineKestraHeyConfig} from "./config"; +export type {KestraSdkPlugin} from "./types"; \ No newline at end of file diff --git a/ui/heyapi-sdk-plugin/plugin.ts b/ui/heyapi-sdk-plugin/plugin.ts new file mode 100644 index 0000000000..f77f8b268d --- /dev/null +++ b/ui/heyapi-sdk-plugin/plugin.ts @@ -0,0 +1,89 @@ +import {$} from "@hey-api/openapi-ts"; +import type {KestraSdkPlugin} from "./types"; + +export const handler: KestraSdkPlugin["Handler"] = ({plugin}) => { + const pluginSdk = plugin.getPluginOrThrow("@hey-api/sdk"); + + const addTenantToParametersSymbol = plugin.symbol("addTenantToParameters"); + const useRouterSymbol = plugin.symbol("useRouter"); + + useRouterSymbol.setImportKind("named") + + addTenantToParametersSymbol.setNode($.func("addTenantToParameters").generic("TParams") + .params( + $.param("parameters").type($.type("TParams")) + ).returns($.type.and($.type("TParams"), $.type.object().prop("tenant", (p) => p.type("string")))) + .do( + // const tenant = useRouter().params.tenant + $.return($.object() + .spread($.id("parameters")) + .prop("tenant", $.literal("main")) + ) + )) + + plugin.addNode(addTenantToParametersSymbol.node ?? null); + + plugin.forEach( + "operation", + ({operation}) => { + // on each operation, create a method that executes the operation from the sdk + const sdkMethodName = pluginSdk.config.methodNameBuilder?.(operation); + if (!sdkMethodName) { + return; + } + + const methodName = `ksApi${sdkMethodName.charAt(0).toUpperCase()}${sdkMethodName.slice(1)}`; + + const pathParams = operation.parameters?.path || {}; + + const originalOperationSymbol = $(plugin.querySymbol({ + category: "sdk", + resource: "operation", + resourceId: operation.id, + })); + + if(!pathParams || !("tenant" in pathParams)) { + // if there is no path parameter named "tenant", + // we export this method as is + const exportedFunctionNode = $.const(plugin.symbol(methodName)).export().assign(originalOperationSymbol); + plugin.addNode(exportedFunctionNode); + return; + } + + const paramId = "parameters" + const optionsId = "options" + const functionNode = $.func() + .params( + $.param(paramId) + .type( + $.type("Omit").generics( + $.type("Parameters") + .generic($.type.query(originalOperationSymbol)) + .idx(0), + $.type.literal("tenant") + ) + ), + $.param(optionsId) + .type( + $.type("Parameters") + .generic($.type.query(originalOperationSymbol)) + .idx(1) + ) + ) + .do( + $.return(originalOperationSymbol.call( + $(addTenantToParametersSymbol).call(paramId), + optionsId, + )) + ) + + const funcSymbol = plugin.symbol(methodName) + const exportedFunctionNode = $.const(funcSymbol).export().assign(functionNode); + + plugin.addNode(exportedFunctionNode); + }, + { + order: "declarations", + }, + ); +}; \ No newline at end of file diff --git a/ui/heyapi-sdk-plugin/types.d.ts b/ui/heyapi-sdk-plugin/types.d.ts new file mode 100644 index 0000000000..396104ed8c --- /dev/null +++ b/ui/heyapi-sdk-plugin/types.d.ts @@ -0,0 +1,16 @@ +import type {DefinePlugin} from "@hey-api/openapi-ts"; + +export type UserConfig = { + /** + * Plugin name. Must be unique. + */ + name: "@kestra-io/sdk-plugin"; + /** + * Name of the generated file. + * + * @default '@kestra-io/sdk-plugin' + */ + output?: string; +}; + +export type KestraSdkPlugin = DefinePlugin; \ No newline at end of file diff --git a/ui/openapi-ts.config.ts b/ui/openapi-ts.config.ts index be6913b1d0..bcfec87702 100644 --- a/ui/openapi-ts.config.ts +++ b/ui/openapi-ts.config.ts @@ -1,4 +1,5 @@ import {defineConfig} from "@hey-api/openapi-ts"; +import {defineKestraHeyConfig} from "./heyapi-sdk-plugin"; const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1); const deCapitalize = (s: string) => s.charAt(0).toLowerCase() + s.slice(1); @@ -10,7 +11,8 @@ export default defineConfig({ lint: "eslint" }, - plugins: [{ + plugins: [ + { name: "@hey-api/client-axios", }, { @@ -19,6 +21,9 @@ export default defineConfig({ methodNameBuilder(operation) { return `${deCapitalize(operation.tags?.[0] ?? "")}${capitalize(operation.operationId ?? "")}`; } - } + }, + defineKestraHeyConfig({ + output: "./src/generated/kestra-heyapi-sdk", + }) ], }); \ No newline at end of file diff --git a/ui/src/stores/flow.ts b/ui/src/stores/flow.ts index 1c484e7ddc..2f3af4be32 100644 --- a/ui/src/stores/flow.ts +++ b/ui/src/stores/flow.ts @@ -276,11 +276,13 @@ export const useFlowStore = defineStore("flow", () => { } // validate flow on first load - return validateFlow({flow: isCreating.value ? source : yamlWithNextRevision.value}) + return validateFlow({ + flow: isCreating.value ? source : yamlWithNextRevision.value + }) } - function findFlows(options: Parameters[0] & { onlyTotal?: boolean }) { - return sdk.Flows.searchFlows(options).then(response => { + function findFlows(options: Parameters[0] & { onlyTotal?: boolean }) { + return sdk.flowsSearchFlows(options).then(response => { if(!response.data){ return undefined } @@ -318,7 +320,7 @@ export const useFlowStore = defineStore("flow", () => { function loadFlow(options: { namespace: string, id: string, revision?: number, allowDeleted?: boolean, source?: boolean, store?: boolean, deleted?: boolean, httpClient?: any }) { const httpClient = options.httpClient - return sdk.Flows.getFlow({ + return sdk.flowsGetFlow({ id: options.id, namespace: options.namespace, revision: options.revision, @@ -631,7 +633,12 @@ function deleteFlowAndDependencies() { return axios.delete(`${apiUrl()}/flows/delete/by-query`, {params: options}) } - function validateFlow(options: { flow: string }) { + function validateFlow(options: { flow?: string }) { + if(!options.flow) { + return Promise.resolve({ + constraints: t("flow must not be empty") + }); + } const flowValidationIssues: FlowValidations = {}; if(isCreating.value) { const {namespace} = YAML_UTILS.getMetadata(options.flow); diff --git a/ui/src/utils/axios.ts b/ui/src/utils/axios.ts index 001e1ace2c..999fd90286 100644 --- a/ui/src/utils/axios.ts +++ b/ui/src/utils/axios.ts @@ -10,7 +10,8 @@ import {useMiscStore} from "override/stores/misc"; import {useUnsavedChangesStore} from "../stores/unsavedChanges" import {client} from "../generated/kestra-api/client.gen" import {Client} from "../generated/kestra-api/client" -import wrappedSdk, {setTenantProvider} from "./sdkWrapper" +import {setTenantProvider} from "./sdkWrapper" +export * as sdk from "../generated/kestra-api/sdk.gen" let pendingRoute = false let requestsTotal = 0