diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index 1dcbbd6874..36a7b4970e 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -3,9 +3,10 @@ const actions = require("../../automations/actions") const logic = require("../../automations/logic") const triggers = require("../../automations/triggers") const { getAutomationParams, generateAutomationID } = require("../../db/utils") -const { saveEntityMetadata } = require("../../utilities") -const { MetadataTypes } = require("../../constants") -const { checkForWebhooks } = require("../../automations/utils") +const { + checkForWebhooks, + updateTestHistory, +} = require("../../automations/utils") /************************* * * @@ -171,11 +172,9 @@ exports.test = async function (ctx) { { getResponses: true } ) // save a test history run - await saveEntityMetadata( - ctx.appId, - MetadataTypes.AUTOMATION_TEST_HISTORY, - automation._id, - ctx.request.body - ) + await updateTestHistory(ctx.appId, automation, { + ...ctx.request.body, + occurredAt: new Date().toISOString(), + }) ctx.body = response } diff --git a/packages/server/src/api/controllers/metadata.js b/packages/server/src/api/controllers/metadata.js index d5fa2b94cb..7e098b6586 100644 --- a/packages/server/src/api/controllers/metadata.js +++ b/packages/server/src/api/controllers/metadata.js @@ -14,7 +14,12 @@ exports.saveMetadata = async ctx => { if (type === MetadataTypes.AUTOMATION_TEST_HISTORY) { ctx.throw(400, "Cannot save automation history type") } - await saveEntityMetadata(ctx.appId, type, entityId, ctx.request.body) + ctx.body = await saveEntityMetadata( + ctx.appId, + type, + entityId, + ctx.request.body + ) } exports.deleteMetadata = async ctx => { @@ -34,7 +39,7 @@ exports.deleteMetadata = async ctx => { await db.remove(id, rev) } ctx.body = { - message: "Metadata deleted successfully.", + message: "Metadata deleted successfully", } } @@ -42,5 +47,13 @@ exports.getMetadata = async ctx => { const { type, entityId } = ctx.params const db = new CouchDB(ctx.appId) const id = generateMetadataID(type, entityId) - ctx.body = await db.get(id) + try { + ctx.body = await db.get(id) + } catch (err) { + if (err.status === 404) { + ctx.body = {} + } else { + ctx.throw(err.status, err) + } + } } diff --git a/packages/server/src/api/routes/index.js b/packages/server/src/api/routes/index.js index 0b09a78bb8..2e1353df98 100644 --- a/packages/server/src/api/routes/index.js +++ b/packages/server/src/api/routes/index.js @@ -22,6 +22,7 @@ const datasourceRoutes = require("./datasource") const queryRoutes = require("./query") const hostingRoutes = require("./hosting") const backupRoutes = require("./backup") +const metadataRoutes = require("./metadata") const devRoutes = require("./dev") exports.mainRoutes = [ @@ -46,6 +47,7 @@ exports.mainRoutes = [ queryRoutes, hostingRoutes, backupRoutes, + metadataRoutes, devRoutes, // these need to be handled last as they still use /api/:tableId // this could be breaking as koa may recognise other routes as this diff --git a/packages/server/src/api/routes/metadata.js b/packages/server/src/api/routes/metadata.js index 7c6ee7a163..f4eba64358 100644 --- a/packages/server/src/api/routes/metadata.js +++ b/packages/server/src/api/routes/metadata.js @@ -4,27 +4,33 @@ const { middleware: appInfoMiddleware, AppType, } = require("../../middleware/appInfo") +const authorized = require("../../middleware/authorized") +const { BUILDER } = require("@budibase/auth/permissions") const router = Router() router .post( "/api/metadata/:type/:entityId", + authorized(BUILDER), appInfoMiddleware({ appType: AppType.DEV }), controller.saveMetadata ) .delete( "/api/metadata/:type/:entityId", + authorized(BUILDER), appInfoMiddleware({ appType: AppType.DEV }), controller.deleteMetadata ) .get( "/api/metadata/type", + authorized(BUILDER), appInfoMiddleware({ appType: AppType.DEV }), controller.getTypes ) .get( "/api/metadata/:type/:entityId", + authorized(BUILDER), appInfoMiddleware({ appType: AppType.DEV }), controller.getMetadata ) diff --git a/packages/server/src/api/routes/tests/automation.spec.js b/packages/server/src/api/routes/tests/automation.spec.js index 09e8747813..28a3718f57 100644 --- a/packages/server/src/api/routes/tests/automation.spec.js +++ b/packages/server/src/api/routes/tests/automation.spec.js @@ -2,6 +2,7 @@ const { checkBuilderEndpoint, getAllTableRows, clearAllAutomations, + triggerAutomation, } = require("./utilities/TestFunctions") const setup = require("./utilities") const { basicAutomation } = setup.structures @@ -23,15 +24,6 @@ describe("/automations", () => { await config.init() }) - const triggerWorkflow = async automation => { - return await request - .post(`/api/automations/${automation._id}/trigger`) - .send({ name: "Test", description: "TEST" }) - .set(config.defaultHeaders()) - .expect('Content-Type', /json/) - .expect(200) - } - describe("get definitions", () => { it("returns a list of definitions for actions", async () => { const res = await request @@ -168,7 +160,7 @@ describe("/automations", () => { automation.definition.steps[0].inputs.row.tableId = table._id automation = await config.createAutomation(automation) await setup.delay(500) - const res = await triggerWorkflow(automation) + const res = await triggerAutomation(config, automation) // this looks a bit mad but we don't actually have a way to wait for a response from the automation to // know that it has finished all of its actions - this is currently the best way // also when this runs in CI it is very temper-mental so for now trying to make run stable by repeating until it works diff --git a/packages/server/src/api/routes/tests/metadata.spec.js b/packages/server/src/api/routes/tests/metadata.spec.js new file mode 100644 index 0000000000..68a7b5dbd3 --- /dev/null +++ b/packages/server/src/api/routes/tests/metadata.spec.js @@ -0,0 +1,64 @@ +const { testAutomation } = require("./utilities/TestFunctions") +const setup = require("./utilities") +const { MetadataTypes } = require("../../../constants") + +describe("/metadata", () => { + let request = setup.getRequest() + let config = setup.getConfig() + let automation + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + automation = await config.createAutomation() + }) + + async function createMetadata(data, type = MetadataTypes.AUTOMATION_TEST_INPUT) { + const res = await request + .post(`/api/metadata/${type}/${automation._id}`) + .send(data) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body._rev).toBeDefined() + } + + async function getMetadata(type) { + const res = await request + .get(`/api/metadata/${type}/${automation._id}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + return res.body + } + + describe("save", () => { + it("should be able to save some metadata", async () => { + await createMetadata({ test: "a" }) + const testInput = await getMetadata(MetadataTypes.AUTOMATION_TEST_INPUT) + expect(testInput.test).toBe("a") + }) + + it("should save history metadata on automation run", async () => { + // this should have created some history + await testAutomation(config, automation) + const metadata = await getMetadata(MetadataTypes.AUTOMATION_TEST_HISTORY) + expect(metadata).toBeDefined() + expect(metadata.history.length).toBe(1) + }) + }) + + describe("destroy", () => { + it("should be able to delete some test inputs", async () => { + const res = await request + .delete(`/api/metadata/${MetadataTypes.AUTOMATION_TEST_INPUT}/${automation._id}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.message).toBeDefined() + const metadata = await getMetadata(MetadataTypes.AUTOMATION_TEST_INPUT) + expect(metadata.test).toBeUndefined() + }) + }) +}) \ No newline at end of file diff --git a/packages/server/src/api/routes/tests/utilities/TestFunctions.js b/packages/server/src/api/routes/tests/utilities/TestFunctions.js index 944d2ac527..ed5094679c 100644 --- a/packages/server/src/api/routes/tests/utilities/TestFunctions.js +++ b/packages/server/src/api/routes/tests/utilities/TestFunctions.js @@ -101,3 +101,21 @@ exports.checkPermissionsEndpoint = async ({ exports.getDB = config => { return new CouchDB(config.getAppId()) } + +exports.triggerAutomation = async (config, automation) => { + return await config.request + .post(`/api/automations/${automation._id}/trigger`) + .send({ name: "Test", description: "TEST" }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) +} + +exports.testAutomation = async (config, automation) => { + return await config.request + .post(`/api/automations/${automation._id}/test`) + .send({ name: "Test", description: "TEST" }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) +} diff --git a/packages/server/src/automations/utils.js b/packages/server/src/automations/utils.js index 8a72e7cf8e..6815de3e62 100644 --- a/packages/server/src/automations/utils.js +++ b/packages/server/src/automations/utils.js @@ -7,6 +7,8 @@ const webhooks = require("../api/controllers/webhook") const CouchDB = require("../db") const { queue } = require("./bullboard") const newid = require("../db/newid") +const { updateEntityMetadata } = require("../utilities") +const { MetadataTypes } = require("../constants") const WH_STEP_ID = definitions.WEBHOOK.stepId const CRON_STEP_ID = definitions.CRON.stepId @@ -63,6 +65,24 @@ exports.processEvent = async job => { } } +exports.updateTestHistory = async (appId, automation, history) => { + return updateEntityMetadata( + appId, + MetadataTypes.AUTOMATION_TEST_HISTORY, + automation._id, + metadata => { + if (metadata && Array.isArray(metadata.history)) { + metadata.history.push(history) + } else { + metadata = { + history: [history], + } + } + return metadata + } + ) +} + // end the repetition and the job itself exports.disableAllCrons = async appId => { const promises = [] diff --git a/packages/server/src/utilities/index.js b/packages/server/src/utilities/index.js index 3f44f05a73..114e1bd5e6 100644 --- a/packages/server/src/utilities/index.js +++ b/packages/server/src/utilities/index.js @@ -58,16 +58,19 @@ exports.attachmentsRelativeURL = attachmentKey => { ) } -exports.saveEntityMetadata = async (appId, type, entityId, metadata) => { +exports.updateEntityMetadata = async (appId, type, entityId, updateFn) => { const db = new CouchDB(appId) const id = generateMetadataID(type, entityId) // read it to see if it exists, we'll overwrite it no matter what - let rev + let rev, + metadata = {} try { const oldMetadata = await db.get(id) rev = oldMetadata._rev + metadata = updateFn(oldMetadata) } catch (err) { rev = null + metadata = updateFn({}) } metadata._id = id if (rev) { @@ -80,3 +83,9 @@ exports.saveEntityMetadata = async (appId, type, entityId, metadata) => { _rev: response.rev, } } + +exports.saveEntityMetadata = async (appId, type, entityId, metadata) => { + return exports.updateEntityMetadata(appId, type, entityId, () => { + return metadata + }) +}