From 9efd1a76f0d5964728c35ef88439df3667341e5e Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 19 Feb 2025 14:05:15 +0100 Subject: [PATCH 1/7] Fix getting proper contexts --- packages/builder/src/stores/builder/screenComponent.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/stores/builder/screenComponent.ts b/packages/builder/src/stores/builder/screenComponent.ts index bb10bb2307..310bf2172c 100644 --- a/packages/builder/src/stores/builder/screenComponent.ts +++ b/packages/builder/src/stores/builder/screenComponent.ts @@ -8,6 +8,7 @@ import { UIComponentError, ComponentDefinition, DependsOnComponentSetting, + Screen, } from "@budibase/types" import { queries } from "./queries" import { views } from "./views" @@ -66,6 +67,7 @@ export const screenComponentErrorList = derived( if (!$selectedScreen) { return [] } + const screen = $selectedScreen const datasources = { ...reduceBy("_id", $tables.list), @@ -79,7 +81,9 @@ export const screenComponentErrorList = derived( const errors: UIComponentError[] = [] function checkComponentErrors(component: Component, ancestors: string[]) { - errors.push(...getInvalidDatasources(component, datasources, definitions)) + errors.push( + ...getInvalidDatasources(screen, component, datasources, definitions) + ) errors.push(...getMissingRequiredSettings(component, definitions)) errors.push(...getMissingAncestors(component, definitions, ancestors)) @@ -95,6 +99,7 @@ export const screenComponentErrorList = derived( ) function getInvalidDatasources( + screen: Screen, component: Component, datasources: Record, definitions: Record From 885873ef298da3d211d15fc9e49afe9c47f22489 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 19 Feb 2025 15:23:56 +0000 Subject: [PATCH 2/7] Fix CouchDB datasource. --- packages/backend-core/src/sql/utils.ts | 2 +- packages/server/src/integrations/couchdb.ts | 21 +-- .../src/integrations/tests/couchdb.spec.ts | 141 +++++++++--------- 3 files changed, 84 insertions(+), 80 deletions(-) diff --git a/packages/backend-core/src/sql/utils.ts b/packages/backend-core/src/sql/utils.ts index b07854b2a0..746a949ef3 100644 --- a/packages/backend-core/src/sql/utils.ts +++ b/packages/backend-core/src/sql/utils.ts @@ -5,10 +5,10 @@ import { SqlQuery, Table, TableSourceType, + SEPARATOR, } from "@budibase/types" import { DEFAULT_BB_DATASOURCE_ID } from "../constants" import { Knex } from "knex" -import { SEPARATOR } from "../db" import environment from "../environment" const DOUBLE_SEPARATOR = `${SEPARATOR}${SEPARATOR}` diff --git a/packages/server/src/integrations/couchdb.ts b/packages/server/src/integrations/couchdb.ts index 4004bc47c6..39d8e17243 100644 --- a/packages/server/src/integrations/couchdb.ts +++ b/packages/server/src/integrations/couchdb.ts @@ -62,12 +62,16 @@ const SCHEMA: Integration = { type: DatasourceFieldType.STRING, required: true, }, + rev: { + type: DatasourceFieldType.STRING, + required: true, + }, }, }, }, } -class CouchDBIntegration implements IntegrationBase { +export class CouchDBIntegration implements IntegrationBase { private readonly client: Database constructor(config: CouchDBConfig) { @@ -82,7 +86,8 @@ class CouchDBIntegration implements IntegrationBase { connected: false, } try { - response.connected = await this.client.exists() + await this.client.allDocs({ limit: 1 }) + response.connected = true } catch (e: any) { response.error = e.message as string } @@ -99,13 +104,9 @@ class CouchDBIntegration implements IntegrationBase { } async read(query: { json: string | object }) { - const parsed = this.parse(query) - const params = { - include_docs: true, - ...parsed, - } + const params = { include_docs: true, ...this.parse(query) } const result = await this.client.allDocs(params) - return result.rows.map(row => row.doc) + return result.rows.map(row => row.doc!) } async update(query: { json: string | object }) { @@ -121,8 +122,8 @@ class CouchDBIntegration implements IntegrationBase { return await this.client.get(query.id) } - async delete(query: { id: string }) { - return await this.client.remove(query.id) + async delete(query: { id: string; rev: string }) { + return await this.client.remove(query.id, query.rev) } } diff --git a/packages/server/src/integrations/tests/couchdb.spec.ts b/packages/server/src/integrations/tests/couchdb.spec.ts index 6cb0c438c0..bc8c4fd38e 100644 --- a/packages/server/src/integrations/tests/couchdb.spec.ts +++ b/packages/server/src/integrations/tests/couchdb.spec.ts @@ -1,84 +1,87 @@ -jest.mock("@budibase/backend-core", () => { - const core = jest.requireActual("@budibase/backend-core") - return { - ...core, - db: { - ...core.db, - DatabaseWithConnection: function () { - return { - allDocs: jest.fn().mockReturnValue({ rows: [] }), - put: jest.fn(), - get: jest.fn().mockReturnValue({ _rev: "a" }), - remove: jest.fn(), - } - }, - }, - } -}) +import { env } from "@budibase/backend-core" +import { CouchDBIntegration } from "../couchdb" +import { generator } from "@budibase/backend-core/tests" -import { default as CouchDBIntegration } from "../couchdb" +function couchSafeID(): string { + // CouchDB IDs must start with a letter, so we prepend an 'a'. + return `a${generator.guid()}` +} -class TestConfiguration { - integration: any +function doc(data: Record): string { + return JSON.stringify({ _id: couchSafeID(), ...data }) +} - constructor( - config: any = { url: "http://somewhere", database: "something" } - ) { - this.integration = new CouchDBIntegration.integration(config) - } +function query(data?: Record): { json: string } { + return { json: doc(data || {}) } } describe("CouchDB Integration", () => { - let config: any + let couchdb: CouchDBIntegration beforeEach(() => { - config = new TestConfiguration() - }) - - it("calls the create method with the correct params", async () => { - const doc = { - test: 1, - } - await config.integration.create({ - json: JSON.stringify(doc), - }) - expect(config.integration.client.put).toHaveBeenCalledWith(doc) - }) - - it("calls the read method with the correct params", async () => { - const doc = { - name: "search", - } - - await config.integration.read({ - json: JSON.stringify(doc), - }) - - expect(config.integration.client.allDocs).toHaveBeenCalledWith({ - include_docs: true, - name: "search", + couchdb = new CouchDBIntegration({ + url: env.COUCH_DB_URL, + database: couchSafeID(), }) }) - it("calls the update method with the correct params", async () => { - const doc = { - _id: "1234", - name: "search", - } - - await config.integration.update({ - json: JSON.stringify(doc), - }) - - expect(config.integration.client.put).toHaveBeenCalledWith({ - ...doc, - _rev: "a", - }) + it("successfully connects", async () => { + const { connected } = await couchdb.testConnection() + expect(connected).toBe(true) }) - it("calls the delete method with the correct params", async () => { - const id = "1234" - await config.integration.delete({ id }) - expect(config.integration.client.remove).toHaveBeenCalledWith(id) + it("can create documents", async () => { + const { id, ok, rev } = await couchdb.create(query({ test: 1 })) + expect(id).toBeDefined() + expect(ok).toBe(true) + expect(rev).toBeDefined() + }) + + it("can read created documents", async () => { + const { id, ok, rev } = await couchdb.create(query({ test: 1 })) + expect(id).toBeDefined() + expect(ok).toBe(true) + expect(rev).toBeDefined() + + const docs = await couchdb.read(query()) + expect(docs).toEqual([ + { + _id: id, + _rev: rev, + test: 1, + createdAt: expect.any(String), + updatedAt: expect.any(String), + }, + ]) + }) + + it("can update documents", async () => { + const { id, ok, rev } = await couchdb.create(query({ test: 1 })) + expect(ok).toBe(true) + + const { id: newId, rev: newRev } = await couchdb.update( + query({ _id: id, _rev: rev, test: 2 }) + ) + const docs = await couchdb.read(query()) + expect(docs).toEqual([ + { + _id: newId, + _rev: newRev, + test: 2, + createdAt: expect.any(String), + updatedAt: expect.any(String), + }, + ]) + }) + + it("can delete documents", async () => { + const { id, ok, rev } = await couchdb.create(query({ test: 1 })) + expect(ok).toBe(true) + + const deleteResponse = await couchdb.delete({ id, rev }) + expect(deleteResponse.ok).toBe(true) + + const docs = await couchdb.read(query()) + expect(docs).toBeEmpty() }) }) From ddf64b9f70790f969bdf4a3eb1fe532adc031d5c Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 19 Feb 2025 16:51:09 +0000 Subject: [PATCH 3/7] Improve APM traces around automations. --- .../backend-core/src/context/mainContext.ts | 2 +- packages/server/src/automations/utils.ts | 82 +++--- packages/server/src/threads/automation.ts | 250 ++++++++++-------- 3 files changed, 170 insertions(+), 164 deletions(-) diff --git a/packages/backend-core/src/context/mainContext.ts b/packages/backend-core/src/context/mainContext.ts index 6a00c125ad..8e0c71ff18 100644 --- a/packages/backend-core/src/context/mainContext.ts +++ b/packages/backend-core/src/context/mainContext.ts @@ -123,7 +123,7 @@ export async function doInAutomationContext(params: { task: () => T }): Promise { await ensureSnippetContext() - return newContext( + return await newContext( { tenantId: getTenantIDFromAppID(params.appId), appId: params.appId, diff --git a/packages/server/src/automations/utils.ts b/packages/server/src/automations/utils.ts index 3ec8f41621..e135c123f4 100644 --- a/packages/server/src/automations/utils.ts +++ b/packages/server/src/automations/utils.ts @@ -40,39 +40,35 @@ function loggingArgs(job: AutomationJob) { } export async function processEvent(job: AutomationJob) { - return tracer.trace( - "processEvent", - { resource: "automation" }, - async span => { - const appId = job.data.event.appId! - const automationId = job.data.automation._id! + return tracer.trace("processEvent", async span => { + const appId = job.data.event.appId! + const automationId = job.data.automation._id! - span?.addTags({ - appId, - automationId, - job: { - id: job.id, - name: job.name, - attemptsMade: job.attemptsMade, - opts: { - attempts: job.opts.attempts, - priority: job.opts.priority, - delay: job.opts.delay, - repeat: job.opts.repeat, - backoff: job.opts.backoff, - lifo: job.opts.lifo, - timeout: job.opts.timeout, - jobId: job.opts.jobId, - removeOnComplete: job.opts.removeOnComplete, - removeOnFail: job.opts.removeOnFail, - stackTraceLimit: job.opts.stackTraceLimit, - preventParsingData: job.opts.preventParsingData, - }, - }, - }) + span.addTags({ + appId, + automationId, + job: { + id: job.id, + name: job.name, + attemptsMade: job.attemptsMade, + attempts: job.opts.attempts, + priority: job.opts.priority, + delay: job.opts.delay, + repeat: job.opts.repeat, + backoff: job.opts.backoff, + lifo: job.opts.lifo, + timeout: job.opts.timeout, + jobId: job.opts.jobId, + removeOnComplete: job.opts.removeOnComplete, + removeOnFail: job.opts.removeOnFail, + stackTraceLimit: job.opts.stackTraceLimit, + preventParsingData: job.opts.preventParsingData, + }, + }) - const task = async () => { - try { + const task = async () => { + try { + return await tracer.trace("task", async () => { if (isCronTrigger(job.data.automation) && !job.data.event.timestamp) { // Requires the timestamp at run time job.data.event.timestamp = Date.now() @@ -81,25 +77,19 @@ export async function processEvent(job: AutomationJob) { console.log("automation running", ...loggingArgs(job)) const runFn = () => Runner.run(job) - const result = await quotas.addAutomation(runFn, { - automationId, - }) + const result = await quotas.addAutomation(runFn, { automationId }) console.log("automation completed", ...loggingArgs(job)) return result - } catch (err) { - span?.addTags({ error: true }) - console.error( - `automation was unable to run`, - err, - ...loggingArgs(job) - ) - return { err } - } + }) + } catch (err) { + span.addTags({ error: true }) + console.error(`automation was unable to run`, err, ...loggingArgs(job)) + return { err } } - - return await context.doInAutomationContext({ appId, automationId, task }) } - ) + + return await context.doInAutomationContext({ appId, automationId, task }) + }) } export async function updateTestHistory( diff --git a/packages/server/src/threads/automation.ts b/packages/server/src/threads/automation.ts index 6ee467023f..8b2aac662c 100644 --- a/packages/server/src/threads/automation.ts +++ b/packages/server/src/threads/automation.ts @@ -310,87 +310,83 @@ class Orchestrator { } async execute(): Promise { - return tracer.trace( - "Orchestrator.execute", - { resource: "automation" }, - async span => { - span?.addTags({ appId: this.appId, automationId: this.automation._id }) + return await tracer.trace("execute", async span => { + span.addTags({ appId: this.appId, automationId: this.automation._id }) - const job = cloneDeep(this.job) - delete job.data.event.appId - delete job.data.event.metadata + const job = cloneDeep(this.job) + delete job.data.event.appId + delete job.data.event.metadata - if (this.isCron() && !job.data.event.timestamp) { - job.data.event.timestamp = Date.now() - } - - const trigger: AutomationTriggerResult = { - id: job.data.automation.definition.trigger.id, - stepId: job.data.automation.definition.trigger.stepId, - inputs: null, - outputs: job.data.event, - } - const result: AutomationResults = { trigger, steps: [trigger] } - - const ctx: AutomationContext = { - trigger: trigger.outputs, - steps: [trigger.outputs], - stepsById: {}, - stepsByName: {}, - user: trigger.outputs.user, - } - await enrichBaseContext(ctx) - - const timeout = - this.job.data.event.timeout || env.AUTOMATION_THREAD_TIMEOUT - - try { - await helpers.withTimeout(timeout, async () => { - const [stepOutputs, executionTime] = await utils.time(() => - this.executeSteps(ctx, job.data.automation.definition.steps) - ) - - result.steps.push(...stepOutputs) - - console.info( - `Automation ID: ${ - this.automation._id - } Execution time: ${executionTime.toMs()} milliseconds`, - { - _logKey: "automation", - executionTime, - } - ) - }) - } catch (e: any) { - if (e.errno === "ETIME") { - span?.addTags({ timedOut: true }) - console.warn(`Automation execution timed out after ${timeout}ms`) - } - } - - let errorCount = 0 - if (this.isProdApp() && this.isCron() && this.hasErrored(ctx)) { - errorCount = (await this.incrementErrorCount()) || 0 - } - - if (errorCount >= MAX_AUTOMATION_RECURRING_ERRORS) { - await this.stopCron("errors", { result }) - span?.addTags({ shouldStop: true }) - } else { - await this.logResult(result) - } - - return result + if (this.isCron() && !job.data.event.timestamp) { + job.data.event.timestamp = Date.now() } - ) + + const trigger: AutomationTriggerResult = { + id: job.data.automation.definition.trigger.id, + stepId: job.data.automation.definition.trigger.stepId, + inputs: null, + outputs: job.data.event, + } + const result: AutomationResults = { trigger, steps: [trigger] } + + const ctx: AutomationContext = { + trigger: trigger.outputs, + steps: [trigger.outputs], + stepsById: {}, + stepsByName: {}, + user: trigger.outputs.user, + } + await enrichBaseContext(ctx) + + const timeout = + this.job.data.event.timeout || env.AUTOMATION_THREAD_TIMEOUT + + try { + await helpers.withTimeout(timeout, async () => { + const [stepOutputs, executionTime] = await utils.time(() => + this.executeSteps(ctx, job.data.automation.definition.steps) + ) + + result.steps.push(...stepOutputs) + + console.info( + `Automation ID: ${ + this.automation._id + } Execution time: ${executionTime.toMs()} milliseconds`, + { + _logKey: "automation", + executionTime, + } + ) + }) + } catch (e: any) { + if (e.errno === "ETIME") { + span?.addTags({ timedOut: true }) + console.warn(`Automation execution timed out after ${timeout}ms`) + } + } + + let errorCount = 0 + if (this.isProdApp() && this.isCron() && this.hasErrored(ctx)) { + errorCount = (await this.incrementErrorCount()) || 0 + } + + if (errorCount >= MAX_AUTOMATION_RECURRING_ERRORS) { + await this.stopCron("errors", { result }) + span?.addTags({ shouldStop: true }) + } else { + await this.logResult(result) + } + + return result + }) } private async executeSteps( ctx: AutomationContext, steps: AutomationStep[] ): Promise { - return tracer.trace("Orchestrator.executeSteps", async () => { + return await tracer.trace("executeSteps", async () => { let stepIndex = 0 const results: AutomationStepResult[] = [] @@ -446,74 +442,92 @@ class Orchestrator { step: LoopStep, stepToLoop: AutomationStep ): Promise { - await processObject(step.inputs, prepareContext(ctx)) + return await tracer.trace("executeLoopStep", async span => { + await processObject(step.inputs, prepareContext(ctx)) - const maxIterations = getLoopMaxIterations(step) - const items: Record[] = [] - let iterations = 0 - let iterable: any[] = [] - try { - iterable = getLoopIterable(step) - } catch (err) { - return stepFailure(stepToLoop, { - status: AutomationStepStatus.INCORRECT_TYPE, - }) - } - - for (; iterations < iterable.length; iterations++) { - const currentItem = iterable[iterations] - - if (iterations === maxIterations) { - return stepFailure(stepToLoop, { - status: AutomationStepStatus.MAX_ITERATIONS, + const maxIterations = getLoopMaxIterations(step) + const items: Record[] = [] + let iterations = 0 + let iterable: any[] = [] + try { + iterable = getLoopIterable(step) + } catch (err) { + span.addTags({ + status: AutomationStepStatus.INCORRECT_TYPE, iterations, }) - } - - if (matchesLoopFailureCondition(step, currentItem)) { return stepFailure(stepToLoop, { - status: AutomationStepStatus.FAILURE_CONDITION, + status: AutomationStepStatus.INCORRECT_TYPE, }) } - ctx.loop = { currentItem } - const result = await this.executeStep(ctx, stepToLoop) - items.push(result.outputs) - ctx.loop = undefined - } + for (; iterations < iterable.length; iterations++) { + const currentItem = iterable[iterations] - const status = - iterations === 0 ? AutomationStatus.NO_CONDITION_MET : undefined - return stepSuccess(stepToLoop, { status, iterations, items }) + if (iterations === maxIterations) { + span.addTags({ + status: AutomationStepStatus.MAX_ITERATIONS, + iterations, + }) + return stepFailure(stepToLoop, { + status: AutomationStepStatus.MAX_ITERATIONS, + iterations, + }) + } + + if (matchesLoopFailureCondition(step, currentItem)) { + span.addTags({ + status: AutomationStepStatus.FAILURE_CONDITION, + iterations, + }) + return stepFailure(stepToLoop, { + status: AutomationStepStatus.FAILURE_CONDITION, + }) + } + + ctx.loop = { currentItem } + const result = await this.executeStep(ctx, stepToLoop) + items.push(result.outputs) + ctx.loop = undefined + } + + const status = + iterations === 0 ? AutomationStatus.NO_CONDITION_MET : undefined + return stepSuccess(stepToLoop, { status, iterations, items }) + }) } private async executeBranchStep( ctx: AutomationContext, step: BranchStep ): Promise { - const { branches, children } = step.inputs + return await tracer.trace("executeBranchStep", async span => { + const { branches, children } = step.inputs - for (const branch of branches) { - if (await branchMatches(ctx, branch)) { - return [ - stepSuccess(step, { - branchName: branch.name, - status: `${branch.name} branch taken`, - branchId: `${branch.id}`, - }), - ...(await this.executeSteps(ctx, children?.[branch.id] || [])), - ] + for (const branch of branches) { + if (await branchMatches(ctx, branch)) { + span.addTags({ branchName: branch.name, branchId: branch.id }) + return [ + stepSuccess(step, { + branchName: branch.name, + status: `${branch.name} branch taken`, + branchId: `${branch.id}`, + }), + ...(await this.executeSteps(ctx, children?.[branch.id] || [])), + ] + } } - } - return [stepFailure(step, { status: AutomationStatus.NO_CONDITION_MET })] + span.addTags({ status: AutomationStatus.NO_CONDITION_MET }) + return [stepFailure(step, { status: AutomationStatus.NO_CONDITION_MET })] + }) } private async executeStep( ctx: AutomationContext, step: Readonly ): Promise { - return tracer.trace("Orchestrator.executeStep", async span => { + return await tracer.trace(step.stepId, async span => { span.addTags({ step: { stepId: step.stepId, @@ -524,6 +538,7 @@ class Orchestrator { internal: step.internal, deprecated: step.deprecated, }, + inputsKeys: Object.keys(step.inputs), }) if (this.stopped) { @@ -557,6 +572,7 @@ class Orchestrator { ;(outputs as any).status = AutomationStatus.STOPPED } + span.addTags({ outputsKeys: Object.keys(outputs) }) return stepSuccess(step, outputs, inputs) }) } From 406c60c9737248b626a4d6863746e98d55c8e2e8 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 20 Feb 2025 09:24:49 +0000 Subject: [PATCH 4/7] Bump version to 3.4.14 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index b6eb31f2b0..79a0eac346 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "3.4.13", + "version": "3.4.14", "npmClient": "yarn", "concurrency": 20, "command": { From 4092f4c3e158d67401883d94c5c6cbd85fd1805c Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 20 Feb 2025 10:11:04 +0000 Subject: [PATCH 5/7] Fix loops being given empty strings. --- .../src/automations/tests/steps/loop.spec.ts | 22 +++++++++++++++++++ packages/server/src/threads/automation.ts | 8 +++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/server/src/automations/tests/steps/loop.spec.ts b/packages/server/src/automations/tests/steps/loop.spec.ts index 883732330f..19f7e5506f 100644 --- a/packages/server/src/automations/tests/steps/loop.spec.ts +++ b/packages/server/src/automations/tests/steps/loop.spec.ts @@ -7,6 +7,8 @@ import { CreateRowStepOutputs, FieldType, FilterCondition, + AutomationStatus, + AutomationStepStatus, } from "@budibase/types" import { createAutomationBuilder } from "../utilities/AutomationTestBuilder" import TestConfiguration from "../../../tests/utilities/TestConfiguration" @@ -560,5 +562,25 @@ describe("Attempt to run a basic loop automation", () => { status: "stopped", }) }) + + it("should not fail if queryRows returns nothing", async () => { + const table = await config.api.table.save(basicTable()) + const results = await createAutomationBuilder(config) + .onAppAction() + .queryRows({ + tableId: table._id!, + }) + .loop({ + option: LoopStepType.ARRAY, + binding: "{{ steps.1.rows }}", + }) + .serverLog({ text: "Message {{loop.currentItem}}" }) + .test({ fields: {} }) + + expect(results.steps[1].outputs.success).toBe(true) + expect(results.steps[1].outputs.status).toBe( + AutomationStepStatus.NO_ITERATIONS + ) + }) }) }) diff --git a/packages/server/src/threads/automation.ts b/packages/server/src/threads/automation.ts index 8b2aac662c..762da1cbc1 100644 --- a/packages/server/src/threads/automation.ts +++ b/packages/server/src/threads/automation.ts @@ -68,7 +68,11 @@ function getLoopIterable(step: LoopStep): any[] { let input = step.inputs.binding if (option === LoopStepType.ARRAY && typeof input === "string") { - input = JSON.parse(input) + if (input === "") { + input = [] + } else { + input = JSON.parse(input) + } } if (option === LoopStepType.STRING && Array.isArray(input)) { @@ -492,7 +496,7 @@ class Orchestrator { } const status = - iterations === 0 ? AutomationStatus.NO_CONDITION_MET : undefined + iterations === 0 ? AutomationStepStatus.NO_ITERATIONS : undefined return stepSuccess(stepToLoop, { status, iterations, items }) }) } From ef9cbfce5936cb26fe981b6e2a4903ae3bd9bc1d Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 20 Feb 2025 10:16:13 +0000 Subject: [PATCH 6/7] Fix lint. --- packages/server/src/automations/tests/steps/loop.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/server/src/automations/tests/steps/loop.spec.ts b/packages/server/src/automations/tests/steps/loop.spec.ts index 19f7e5506f..2bdf33b253 100644 --- a/packages/server/src/automations/tests/steps/loop.spec.ts +++ b/packages/server/src/automations/tests/steps/loop.spec.ts @@ -7,7 +7,6 @@ import { CreateRowStepOutputs, FieldType, FilterCondition, - AutomationStatus, AutomationStepStatus, } from "@budibase/types" import { createAutomationBuilder } from "../utilities/AutomationTestBuilder" From 1328076d03f88d656edb1a188c24d121904297fa Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 20 Feb 2025 10:26:46 +0000 Subject: [PATCH 7/7] Bump version to 3.4.15 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 79a0eac346..91980e0a15 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "3.4.14", + "version": "3.4.15", "npmClient": "yarn", "concurrency": 20, "command": {