From 715399b755ae39b98c6c7b08014c9f854f57a1f3 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 13 Dec 2024 13:34:32 +0000 Subject: [PATCH] Typing improvements for webhooks - fixing issue with parameter change - working on test case. --- .../server/src/api/controllers/webhook.ts | 14 +++- .../tests/scenarios/webhook.spec.ts | 42 +++++++++++ .../tests/utilities/AutomationTestBuilder.ts | 74 +++++++++++-------- packages/server/src/automations/triggers.ts | 8 +- .../server/src/tests/utilities/api/index.ts | 3 + .../server/src/tests/utilities/api/webhook.ts | 38 ++++++++++ .../src/sdk/documents/automations.ts | 14 ++-- packages/types/package.json | 3 +- .../app/automation/StepInputsOutputs.ts | 9 +++ packages/types/src/documents/app/webhook.ts | 3 +- yarn.lock | 74 ++----------------- 11 files changed, 174 insertions(+), 108 deletions(-) create mode 100644 packages/server/src/automations/tests/scenarios/webhook.spec.ts create mode 100644 packages/server/src/tests/utilities/api/webhook.ts diff --git a/packages/server/src/api/controllers/webhook.ts b/packages/server/src/api/controllers/webhook.ts index 543ab80e59..6fbf873f4d 100644 --- a/packages/server/src/api/controllers/webhook.ts +++ b/packages/server/src/api/controllers/webhook.ts @@ -15,6 +15,7 @@ import { BuildWebhookSchemaResponse, TriggerWebhookRequest, TriggerWebhookResponse, + AutomationIOType, } from "@budibase/types" import sdk from "../../sdk" import * as pro from "@budibase/pro" @@ -60,14 +61,21 @@ export async function buildSchema( if (webhook.action.type === WebhookActionType.AUTOMATION) { let automation = await db.get(webhook.action.target) const autoOutputs = automation.definition.trigger.schema.outputs - let properties = webhook.bodySchema.properties + let properties = webhook.bodySchema?.properties // reset webhook outputs autoOutputs.properties = { body: autoOutputs.properties.body, } - for (let prop of Object.keys(properties)) { + for (let prop of Object.keys(properties || {})) { + if (!properties?.[prop]) { + continue + } + const def = properties[prop] + if (typeof def === "boolean") { + continue + } autoOutputs.properties[prop] = { - type: properties[prop].type, + type: def.type as AutomationIOType, description: AUTOMATION_DESCRIPTION, } } diff --git a/packages/server/src/automations/tests/scenarios/webhook.spec.ts b/packages/server/src/automations/tests/scenarios/webhook.spec.ts new file mode 100644 index 0000000000..78648d80d4 --- /dev/null +++ b/packages/server/src/automations/tests/scenarios/webhook.spec.ts @@ -0,0 +1,42 @@ +import * as automation from "../../index" +import * as setup from "../utilities" +import { Table, Webhook, WebhookActionType } from "@budibase/types" +import { createAutomationBuilder } from "../utilities/AutomationTestBuilder" + +describe("Branching automations", () => { + let config = setup.getConfig(), + table: Table, + webhook: Webhook + + async function createWebhookAutomation(testName: string) { + const builder = createAutomationBuilder({ + name: testName, + }) + const automation = await builder + .webhook({ fields: { parameter: "string" } }) + .createRow({ + row: { tableId: table._id!, name: "{{ trigger.parameter }}" }, + }) + .save() + + webhook = await config.api.webhook.save({ + name: "hook", + live: true, + action: { + type: WebhookActionType.AUTOMATION, + target: automation._id!, + }, + bodySchema: {}, + }) + } + + beforeEach(async () => { + await automation.init() + await config.init() + table = await config.createTable() + }) + + afterAll(setup.afterAll) + + it("should run the webhook automation", async () => {}) +}) diff --git a/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts b/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts index a3ea174da1..dbde178603 100644 --- a/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts +++ b/packages/server/src/automations/tests/utilities/AutomationTestBuilder.ts @@ -1,42 +1,43 @@ import { v4 as uuidv4 } from "uuid" import { testAutomation } from "../../../api/routes/tests/utilities/TestFunctions" -import {} from "../../steps/createRow" import { BUILTIN_ACTION_DEFINITIONS } from "../../actions" import { TRIGGER_DEFINITIONS } from "../../triggers" import { - LoopStepInputs, - DeleteRowStepInputs, - UpdateRowStepInputs, - CreateRowStepInputs, + AppActionTriggerInputs, + AppActionTriggerOutputs, Automation, - AutomationTrigger, - AutomationResults, - SmtpEmailStepInputs, - ExecuteQueryStepInputs, - QueryRowsStepInputs, AutomationActionStepId, - AutomationTriggerStepId, + AutomationResults, AutomationStep, + AutomationStepInputs, + AutomationTrigger, AutomationTriggerDefinition, - RowDeletedTriggerInputs, - RowDeletedTriggerOutputs, - RowUpdatedTriggerOutputs, - RowUpdatedTriggerInputs, + AutomationTriggerInputs, + AutomationTriggerStepId, + BashStepInputs, + Branch, + BranchStepInputs, + CreateRowStepInputs, + CronTriggerOutputs, + DeleteRowStepInputs, + ExecuteQueryStepInputs, + ExecuteScriptStepInputs, + FilterStepInputs, + LoopStepInputs, + OpenAIStepInputs, + QueryRowsStepInputs, RowCreatedTriggerInputs, RowCreatedTriggerOutputs, - AppActionTriggerOutputs, - CronTriggerOutputs, - AppActionTriggerInputs, - AutomationStepInputs, - AutomationTriggerInputs, - ServerLogStepInputs, - BranchStepInputs, + RowDeletedTriggerInputs, + RowDeletedTriggerOutputs, + RowUpdatedTriggerInputs, + RowUpdatedTriggerOutputs, SearchFilters, - Branch, - FilterStepInputs, - ExecuteScriptStepInputs, - OpenAIStepInputs, - BashStepInputs, + ServerLogStepInputs, + SmtpEmailStepInputs, + UpdateRowStepInputs, + WebhookTriggerInputs, + WebhookTriggerOutputs, } from "@budibase/types" import TestConfiguration from "../../../tests/utilities/TestConfiguration" import * as setup from "../utilities" @@ -47,6 +48,7 @@ type TriggerOutputs = | RowUpdatedTriggerOutputs | RowDeletedTriggerOutputs | AppActionTriggerOutputs + | WebhookTriggerOutputs | CronTriggerOutputs | undefined @@ -329,6 +331,16 @@ class AutomationBuilder extends BaseStepBuilder { ) } + webhook(outputs: WebhookTriggerOutputs, inputs?: WebhookTriggerInputs) { + this.triggerOutputs = outputs + return this.trigger( + TRIGGER_DEFINITIONS.WEBHOOK, + AutomationTriggerStepId.WEBHOOK, + inputs, + outputs + ) + } + private trigger( triggerSchema: AutomationTriggerDefinition, stepId: TStep, @@ -361,12 +373,16 @@ class AutomationBuilder extends BaseStepBuilder { return this.automationConfig } - async run() { + async save() { if (!Object.keys(this.automationConfig.definition.trigger).length) { throw new Error("Please add a trigger to this automation test") } this.automationConfig.definition.steps = this.steps - const automation = await this.config.createAutomation(this.build()) + return await this.config.createAutomation(this.build()) + } + + async run() { + const automation = await this.save() const results = await testAutomation( this.config, automation, diff --git a/packages/server/src/automations/triggers.ts b/packages/server/src/automations/triggers.ts index ed0aaaf3ec..67d2dcb911 100644 --- a/packages/server/src/automations/triggers.ts +++ b/packages/server/src/automations/triggers.ts @@ -181,10 +181,14 @@ export async function externalTrigger( coercedFields[key] = coerce(params.fields[key], fields[key]) } params.fields = coercedFields - } else if (sdk.automations.isRowAction(automation)) { + } + // row actions and webhooks flatten the fields down + else if ( + sdk.automations.isRowAction(automation) || + sdk.automations.isWebhookAction(automation) + ) { params = { ...params, - // Until we don't refactor all the types, we want to flatten the nested "fields" object ...params.fields, fields: {}, } diff --git a/packages/server/src/tests/utilities/api/index.ts b/packages/server/src/tests/utilities/api/index.ts index 79514d4ece..c5eede18d6 100644 --- a/packages/server/src/tests/utilities/api/index.ts +++ b/packages/server/src/tests/utilities/api/index.ts @@ -16,6 +16,7 @@ import { TemplateAPI } from "./template" import { RowActionAPI } from "./rowAction" import { AutomationAPI } from "./automation" import { PluginAPI } from "./plugin" +import { WebhookAPI } from "./webhook" export default class API { table: TableAPI @@ -35,6 +36,7 @@ export default class API { rowAction: RowActionAPI automation: AutomationAPI plugin: PluginAPI + webhook: WebhookAPI constructor(config: TestConfiguration) { this.table = new TableAPI(config) @@ -54,5 +56,6 @@ export default class API { this.rowAction = new RowActionAPI(config) this.automation = new AutomationAPI(config) this.plugin = new PluginAPI(config) + this.webhook = new WebhookAPI(config) } } diff --git a/packages/server/src/tests/utilities/api/webhook.ts b/packages/server/src/tests/utilities/api/webhook.ts new file mode 100644 index 0000000000..9690c48bb6 --- /dev/null +++ b/packages/server/src/tests/utilities/api/webhook.ts @@ -0,0 +1,38 @@ +import { Expectations, TestAPI } from "./base" +import { + SaveWebhookResponse, + TriggerWebhookResponse, + Webhook, +} from "@budibase/types" + +export class WebhookAPI extends TestAPI { + save = async (webhook: Webhook, expectations?: Expectations) => { + const resp = await this._post("/api/webhooks", { + body: webhook, + expectations: { + status: 200, + ...expectations, + }, + }) + return resp.webhook + } + + trigger = async ( + appId: string, + webhookId: string, + fields: Record, + expectations?: Expectations + ) => { + const resp = await this._post( + `/api/webhooks/trigger/${appId}/${webhookId}`, + { + body: fields, + expectations: { + status: 200, + ...expectations, + }, + } + ) + return resp?.message + } +} diff --git a/packages/shared-core/src/sdk/documents/automations.ts b/packages/shared-core/src/sdk/documents/automations.ts index 86cbee75f7..9fe69677fd 100644 --- a/packages/shared-core/src/sdk/documents/automations.ts +++ b/packages/shared-core/src/sdk/documents/automations.ts @@ -1,13 +1,17 @@ import { Automation, AutomationTriggerStepId } from "@budibase/types" export function isRowAction(automation: Automation) { - const result = + return ( automation.definition.trigger?.stepId === AutomationTriggerStepId.ROW_ACTION - return result + ) +} + +export function isWebhookAction(automation: Automation) { + return ( + automation.definition.trigger?.stepId === AutomationTriggerStepId.WEBHOOK + ) } export function isAppAction(automation: Automation) { - const result = - automation.definition.trigger?.stepId === AutomationTriggerStepId.APP - return result + return automation.definition.trigger?.stepId === AutomationTriggerStepId.APP } diff --git a/packages/types/package.json b/packages/types/package.json index 9be5c8460b..ee3c059bc9 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -15,11 +15,12 @@ "jest": {}, "devDependencies": { "@budibase/nano": "10.1.5", + "@types/json-schema": "^7.0.15", "@types/koa": "2.13.4", "@types/redlock": "4.0.7", + "koa-useragent": "^4.1.0", "rimraf": "3.0.2", "typescript": "5.7.2", - "koa-useragent": "^4.1.0", "zod": "^3.23.8" }, "dependencies": { diff --git a/packages/types/src/documents/app/automation/StepInputsOutputs.ts b/packages/types/src/documents/app/automation/StepInputsOutputs.ts index b2f679edee..2d87bae65d 100644 --- a/packages/types/src/documents/app/automation/StepInputsOutputs.ts +++ b/packages/types/src/documents/app/automation/StepInputsOutputs.ts @@ -296,3 +296,12 @@ export type RowUpdatedTriggerOutputs = { id: string revision?: string } + +export type WebhookTriggerInputs = { + schemaUrl: string + triggerUrl: string +} + +export type WebhookTriggerOutputs = { + fields: Record +} diff --git a/packages/types/src/documents/app/webhook.ts b/packages/types/src/documents/app/webhook.ts index 1ced8627af..bfec8a9744 100644 --- a/packages/types/src/documents/app/webhook.ts +++ b/packages/types/src/documents/app/webhook.ts @@ -1,4 +1,5 @@ import { Document } from "../document" +import { JSONSchema7 } from "json-schema" export enum WebhookActionType { AUTOMATION = "automation", @@ -11,5 +12,5 @@ export interface Webhook extends Document { type: WebhookActionType target: string } - bodySchema?: any + bodySchema?: JSONSchema7 } diff --git a/yarn.lock b/yarn.lock index aa409fe4a1..d9124b0b25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2088,47 +2088,6 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@3.2.26": - version "0.0.0" - dependencies: - "@budibase/nano" "10.1.5" - "@budibase/pouchdb-replication-stream" "1.2.11" - "@budibase/shared-core" "*" - "@budibase/types" "*" - "@techpass/passport-openidconnect" "0.3.3" - aws-cloudfront-sign "3.0.2" - aws-sdk "2.1692.0" - bcrypt "5.1.0" - bcryptjs "2.4.3" - bull "4.10.1" - correlation-id "4.0.0" - dd-trace "5.26.0" - dotenv "16.0.1" - google-auth-library "^8.0.1" - google-spreadsheet "npm:@budibase/google-spreadsheet@4.1.5" - ioredis "5.3.2" - joi "17.6.0" - jsonwebtoken "9.0.2" - knex "2.4.2" - koa-passport "^6.0.0" - koa-pino-logger "4.0.0" - lodash "4.17.21" - node-fetch "2.6.7" - passport-google-oauth "2.0.0" - passport-local "1.0.0" - passport-oauth2-refresh "^2.1.0" - pino "8.11.0" - pino-http "8.3.3" - posthog-node "4.0.1" - pouchdb "9.0.0" - pouchdb-find "9.0.0" - redlock "4.2.0" - rotating-file-stream "3.1.0" - sanitize-s3-objectkey "0.0.1" - semver "^7.5.4" - tar-fs "2.1.1" - uuid "^8.3.2" - "@budibase/handlebars-helpers@^0.13.2": version "0.13.2" resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.13.2.tgz#73ab51c464e91fd955b429017648e0257060db77" @@ -2172,15 +2131,15 @@ through2 "^2.0.0" "@budibase/pro@npm:@budibase/pro@latest": - version "3.2.26" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-3.2.26.tgz#3525535e07827ff820eefeeb94ad9e0e818ac698" - integrity sha512-+V04NbKSBN3Up6vcyBFFpecQ3MrJvVuXN74JE9yLj+KVxQXJ1kwCrMgea/XyJomSc72PWb9sQzXADWTe5i5STA== + version "3.2.28" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-3.2.28.tgz#59b5b37225715bb8fbf5b1c5c989140b10b58710" + integrity sha512-eDPeZpYFRZYQhCulcQAUwFoPk68c8+K9mIsB6QD3oMHmHTDA1P2ZcXvLNqDTIqHB94DqnWinqDf4hTuGYApgPA== dependencies: "@anthropic-ai/sdk" "^0.27.3" - "@budibase/backend-core" "3.2.26" - "@budibase/shared-core" "3.2.26" - "@budibase/string-templates" "3.2.26" - "@budibase/types" "3.2.26" + "@budibase/backend-core" "*" + "@budibase/shared-core" "*" + "@budibase/string-templates" "*" + "@budibase/types" "*" "@koa/router" "13.1.0" bull "4.10.1" dd-trace "5.26.0" @@ -2193,25 +2152,6 @@ scim-patch "^0.8.1" scim2-parse-filter "^0.2.8" -"@budibase/shared-core@3.2.26": - version "0.0.0" - dependencies: - "@budibase/types" "*" - cron-validate "1.4.5" - -"@budibase/string-templates@3.2.26": - version "0.0.0" - dependencies: - "@budibase/handlebars-helpers" "^0.13.2" - dayjs "^1.10.8" - handlebars "^4.7.8" - lodash.clonedeep "^4.5.0" - -"@budibase/types@3.2.26": - version "0.0.0" - dependencies: - scim-patch "^0.8.1" - "@bull-board/api@5.10.2": version "5.10.2" resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-5.10.2.tgz#ae8ff6918b23897bf879a6ead3683f964374c4b3"