diff --git a/packages/builder/src/builderStore/store/automation/index.js b/packages/builder/src/builderStore/store/automation/index.js
index 5a2019b5c1..73e3d83832 100644
--- a/packages/builder/src/builderStore/store/automation/index.js
+++ b/packages/builder/src/builderStore/store/automation/index.js
@@ -6,6 +6,7 @@ import analytics, { Events } from "analytics"
const initialAutomationState = {
automations: [],
+ showTestPanel: false,
blockDefinitions: {
TRIGGER: [],
ACTION: [],
@@ -121,6 +122,12 @@ const automationActions = store => ({
return state
})
},
+ getLogs: async (automationId, startDate) => {
+ return await API.getAutomationLogs({
+ automationId,
+ startDate,
+ })
+ },
addTestDataToAutomation: data => {
store.update(state => {
state.selectedAutomation.addTestData(data)
diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte
index 3e58b25ff6..9c987c89d8 100644
--- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte
+++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowChart.svelte
@@ -65,7 +65,7 @@
{
- $automationStore.selectedAutomation.automation.showTestPanel = true
+ $automationStore.showTestPanel = true
}}
size="M">Test Details
diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte
index fecd0fcc7e..b86cffb1f9 100644
--- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte
+++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/TestDataModal.svelte
@@ -51,7 +51,7 @@
$automationStore.selectedAutomation?.automation,
testData
)
- $automationStore.selectedAutomation.automation.showTestPanel = true
+ $automationStore.showTestPanel = true
} catch (error) {
notifications.error("Error testing notification")
}
diff --git a/packages/builder/src/components/automation/AutomationBuilder/TestDisplay.svelte b/packages/builder/src/components/automation/AutomationBuilder/TestDisplay.svelte
index e8bcc2e349..6d4c83de11 100644
--- a/packages/builder/src/components/automation/AutomationBuilder/TestDisplay.svelte
+++ b/packages/builder/src/components/automation/AutomationBuilder/TestDisplay.svelte
@@ -10,7 +10,7 @@
let blocks
function prepTestResults(results) {
- return results.steps.filter(x => x.stepId !== "LOOP" || [])
+ return results?.steps.filter(x => x.stepId !== "LOOP" || [])
}
function textArea(results, message) {
diff --git a/packages/builder/src/components/automation/AutomationBuilder/TestPanel.svelte b/packages/builder/src/components/automation/AutomationBuilder/TestPanel.svelte
index 2fb4994a76..9e84350cd0 100644
--- a/packages/builder/src/components/automation/AutomationBuilder/TestPanel.svelte
+++ b/packages/builder/src/components/automation/AutomationBuilder/TestPanel.svelte
@@ -36,7 +36,7 @@
{
- $automationStore.selectedAutomation.automation.showTestPanel = false
+ $automationStore.showTestPanel = false
}}
hoverable
name="Close"
diff --git a/packages/builder/src/components/portal/overview/HistoryTab.svelte b/packages/builder/src/components/portal/overview/HistoryTab.svelte
index f38becece8..7925419091 100644
--- a/packages/builder/src/components/portal/overview/HistoryTab.svelte
+++ b/packages/builder/src/components/portal/overview/HistoryTab.svelte
@@ -14,12 +14,12 @@
const runHistorySchema = {
status: { displayName: "Status" },
- timestamp: { displayName: "Time" },
- name: { displayName: "Automation" },
+ createdAt: { displayName: "Time" },
+ automationName: { displayName: "Automation" },
}
const customRenderers = [
- { column: "time", component: DateTimeRenderer },
+ { column: "createdAt", component: DateTimeRenderer },
{ column: "status", component: StatusRenderer },
]
@@ -54,57 +54,10 @@
onMount(async () => {
let definitions = await automationStore.actions.definitions()
- runHistory = enrichHistory(definitions, [
- {
- status: "Success",
- timestamp: "2022-05-11T16:06:14.438Z",
- name: "automation name",
- steps: [
- {
- stepId: "ROW_SAVED",
- inputs: null,
- outputs: {
- id: "awd",
- revision: "awd",
- row: {
- tableId: "ta_240cfde36405479fa814b8a2c46655b5",
- name: "",
- suppliers: [],
- "supplier name": "",
- _id: "awd",
- _rev: "awd",
- },
- },
- },
- {
- stepId: "SERVER_LOG",
- inputs: {
- text: "awdawdawd",
- },
- outputs: {
- success: true,
- },
- },
- ],
- },
- {
- status: "Error",
- timestamp: "2022-05-11T16:03:14.438Z",
- name: "automation name",
- steps: [
- {
- stepId: "ROW_SAVED",
- inputs: {},
- outputs: {},
- },
- {
- stepId: "SEND_EMAIL_SMTP",
- inputs: {},
- outputs: {},
- },
- ],
- },
- ])
+ runHistory = enrichHistory(
+ definitions,
+ await automationStore.actions.getLogs()
+ )
})
diff --git a/packages/builder/src/components/portal/overview/StatusRenderer.svelte b/packages/builder/src/components/portal/overview/StatusRenderer.svelte
index 7abfebb2f2..8cbcd6c10e 100644
--- a/packages/builder/src/components/portal/overview/StatusRenderer.svelte
+++ b/packages/builder/src/components/portal/overview/StatusRenderer.svelte
@@ -2,7 +2,7 @@
import { Icon } from "@budibase/bbui"
export let value
- $: isError = value === "Error"
+ $: isError = !value || value.toLowerCase() === "error"
$: color = isError
? "var(--spectrum-semantic-negative-color-background)"
: "var(--green)"
diff --git a/packages/builder/src/pages/builder/app/[application]/automate/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/automate/_layout.svelte
index a713067bbe..447441b2fc 100644
--- a/packages/builder/src/pages/builder/app/[application]/automate/_layout.svelte
+++ b/packages/builder/src/pages/builder/app/[application]/automate/_layout.svelte
@@ -45,7 +45,7 @@
{/if}
- {#if automation?.showTestPanel}
+ {#if $automationStore.showTestPanel}
diff --git a/packages/frontend-core/src/api/automations.js b/packages/frontend-core/src/api/automations.js
index 3b60b6dbac..2072337073 100644
--- a/packages/frontend-core/src/api/automations.js
+++ b/packages/frontend-core/src/api/automations.js
@@ -73,4 +73,19 @@ export const buildAutomationEndpoints = API => ({
url: `/api/automations/${automationId}/${automationRev}`,
})
},
+
+ /**
+ * Get the logs for the app, or by automation ID.
+ * @param automationId The ID of the automation to get logs for.
+ * @param startDate An ISO date string to state the start of the date range.
+ */
+ getAutomationLogs: async ({ automationId, startDate }) => {
+ return await API.post({
+ url: "/api/automations/logs/search",
+ body: {
+ automationId,
+ startDate,
+ },
+ })
+ },
})
diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js
index 74942dad40..c3c4f4da98 100644
--- a/packages/server/src/api/controllers/automation.js
+++ b/packages/server/src/api/controllers/automation.js
@@ -1,5 +1,6 @@
const actions = require("../../automations/actions")
const triggers = require("../../automations/triggers")
+const { getLogs, oneDayAgo } = require("../../automations/history")
const { getAutomationParams, generateAutomationID } = require("../../db/utils")
const {
checkForWebhooks,
@@ -150,6 +151,14 @@ exports.destroy = async function (ctx) {
ctx.body = await db.remove(automationId, ctx.params.rev)
}
+exports.logSearch = async function (ctx) {
+ const { automationId } = ctx.request.body
+ // TODO: check if there is a date range in the search params
+ // also check the date range vs their license, see if it is allowed
+ const startDate = oneDayAgo()
+ ctx.body = await getLogs(startDate, automationId)
+}
+
exports.getActionList = async function (ctx) {
ctx.body = ACTION_DEFS
}
diff --git a/packages/server/src/api/routes/automation.js b/packages/server/src/api/routes/automation.js
index be1af1f786..d25f7bc328 100644
--- a/packages/server/src/api/routes/automation.js
+++ b/packages/server/src/api/routes/automation.js
@@ -57,6 +57,11 @@ router
authorized(BUILDER),
controller.destroy
)
+ .post(
+ "/api/automations/logs/search",
+ authorized(BUILDER),
+ controller.logSearch
+ )
.post(
"/api/automations/:id/trigger",
appInfoMiddleware({ appType: AppType.PROD }),
diff --git a/packages/server/src/automations/history/index.ts b/packages/server/src/automations/history/index.ts
index b1fd761291..65ebb84ccd 100644
--- a/packages/server/src/automations/history/index.ts
+++ b/packages/server/src/automations/history/index.ts
@@ -1,24 +1,55 @@
-import { AutomationResults } from "../../definitions/automation"
+import {
+ AutomationLog,
+ AutomationResults,
+ AutomationStatus,
+} from "../../definitions/automation"
import { getAppDB } from "@budibase/backend-core/context"
import {
generateAutomationLogID,
getAutomationLogParams,
- // getQueryIndex,
- // ViewNames,
+ getQueryIndex,
+ ViewNames,
} from "../../db/utils"
-// import { createLogByAutomationView } from "../../db/views/staticViews"
+import { createLogByAutomationView } from "../../db/views/staticViews"
+import { Automation } from "../../definitions/common"
+const EARLIEST_DATE = new Date(0).toISOString()
const FREE_EXPIRY_SEC = 86400
-// const PRO_EXPIRY_SEC = FREE_EXPIRY_SEC * 30
+const PRO_EXPIRY_SEC = FREE_EXPIRY_SEC * 30
+
+function getStatus(results: AutomationResults) {
+ let status = AutomationStatus.SUCCESS
+ let first = true
+ for (let step of results.steps) {
+ // skip the trigger, its always successful if automation ran
+ if (first) {
+ first = false
+ continue
+ }
+ if (!step.outputs?.success) {
+ status = AutomationStatus.ERROR
+ }
+ }
+ return status
+}
+
+// export function oneMonthAgo() {
+// return new Date(
+// new Date().getTime() - PRO_EXPIRY_SEC * 1000
+// ).toISOString()
+// }
+
+export function oneDayAgo() {
+ return new Date(new Date().getTime() - FREE_EXPIRY_SEC * 1000).toISOString()
+}
async function clearOldHistory() {
const db = getAppDB()
// TODO: handle license lookup for deletion
- const time = new Date(new Date().getTime() - FREE_EXPIRY_SEC * 1000)
- const queryParams: any = {
- endIso: time,
- }
- const results = await db.allDocs(getAutomationLogParams(queryParams))
+ const expiredEnd = oneDayAgo()
+ const results = await getAllLogs(EARLIEST_DATE, expiredEnd, {
+ include_docs: false,
+ })
const toDelete = results.map((doc: any) => ({
_id: doc.id,
_rev: doc.rev,
@@ -27,36 +58,66 @@ async function clearOldHistory() {
await db.bulkDocs(toDelete)
}
-// async function getByAutomationID(automationId: string) {
-// const db = getAppDB()
-// try {
-// const queryParams: any = {
-// automationId,
-// }
-// return (
-// await db.query(
-// getQueryIndex(ViewNames.LOGS_BY_AUTOMATION),
-// getAutomationLogParams(queryParams, { include_docs: true })
-// )
-// ).rows.map((row: any) => row.doc)
-// } catch (err: any) {
-// if (err != null && err.name === "not_found") {
-// await createLogByAutomationView()
-// return getByAutomationID(automationId)
-// }
-// }
-// }
-
-export async function storeHistory(
- automationId: string,
- results: AutomationResults
+async function getAllLogs(
+ startDate: string,
+ endDate: string,
+ opts: any = { include_docs: true }
) {
+ const db = getAppDB()
+ const queryParams: any = {
+ endDate,
+ startDate,
+ }
+ let response = (await db.allDocs(getAutomationLogParams(queryParams, opts)))
+ .rows
+ if (opts?.include_docs) {
+ response = response.map((row: any) => row.doc)
+ }
+ return response
+}
+
+async function getLogsByAutomationID(
+ automationId: string,
+ opts: { startDate?: string; endDate?: string } = {}
+): Promise {
+ const db = getAppDB()
+ try {
+ const queryParams = {
+ startDate: opts?.startDate,
+ endDate: opts?.startDate,
+ automationId,
+ }
+ return (
+ await db.query(
+ getQueryIndex(ViewNames.LOGS_BY_AUTOMATION),
+ getAutomationLogParams(queryParams, { include_docs: true })
+ )
+ ).rows.map((row: any) => row.doc)
+ } catch (err: any) {
+ if (err != null && err.name === "not_found") {
+ await createLogByAutomationView()
+ return getLogsByAutomationID(automationId, opts)
+ }
+ }
+ return []
+}
+
+export async function storeLog(
+ automation: Automation,
+ results: AutomationResults
+) {
+ const automationId = automation._id
+ const name = automation.name
const db = getAppDB()
const isoDate = new Date().toISOString()
- const id = generateAutomationLogID(isoDate, automationId)
+ const id = generateAutomationLogID(automationId, isoDate)
+
await db.put({
// results contain automationId and status for view
...results,
+ automationId,
+ automationName: name,
+ status: getStatus(results),
createdAt: isoDate,
_id: id,
})
@@ -64,4 +125,16 @@ export async function storeHistory(
await clearOldHistory()
}
-export async function retrieve() {}
+export async function getLogs(startDate: string, automationId?: string) {
+ let logs: AutomationLog[]
+ let endDate = new Date().toISOString()
+ if (automationId) {
+ logs = await getLogsByAutomationID(automationId, {
+ startDate,
+ endDate,
+ })
+ } else {
+ logs = await getAllLogs(startDate, endDate)
+ }
+ return logs
+}
diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js
index 1cf7b54bbc..daf6bac82e 100644
--- a/packages/server/src/db/utils.js
+++ b/packages/server/src/db/utils.js
@@ -369,12 +369,12 @@ exports.generateAutomationLogID = (automationId, isoDate) => {
}
exports.getAutomationLogParams = (
- { startIso, endIso, automationId } = {},
+ { startDate, endDate, automationId } = {},
otherProps = {}
) => {
const base = `${DocumentTypes.AUTOMATION_LOG}${SEPARATOR}`
- let start = startIso || "",
- end = endIso || ""
+ let start = startDate || "",
+ end = endDate || ""
// reverse for view
if (automationId) {
start = `${automationId}${SEPARATOR}${start}`
diff --git a/packages/server/src/definitions/automation.ts b/packages/server/src/definitions/automation.ts
index f48f3f370a..5ec2415a3f 100644
--- a/packages/server/src/definitions/automation.ts
+++ b/packages/server/src/definitions/automation.ts
@@ -1,6 +1,12 @@
+export enum AutomationStatus {
+ SUCCESS = "success",
+ ERROR = "error",
+}
+
export interface AutomationResults {
automationId: string
status: string
+ trigger?: any
steps: {
stepId: string
inputs: {
@@ -11,3 +17,9 @@ export interface AutomationResults {
}
}[]
}
+
+export interface AutomationLog extends AutomationResults {
+ createdAt: string
+ _id: string
+ _rev: string
+}
diff --git a/packages/server/src/threads/automation.js b/packages/server/src/threads/automation.js
index 7c5c96fc39..55f4ffd772 100644
--- a/packages/server/src/threads/automation.js
+++ b/packages/server/src/threads/automation.js
@@ -9,6 +9,7 @@ const { doInTenant } = require("@budibase/backend-core/tenancy")
const { definitions: triggerDefs } = require("../automations/triggerInfo")
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
const { AutomationErrors, LoopStepTypes } = require("../constants")
+const { storeLog } = require("../automations/history")
const FILTER_STEP_ID = actions.ACTION_DEFINITIONS.FILTER.stepId
const LOOP_STEP_ID = actions.ACTION_DEFINITIONS.LOOP.stepId
@@ -325,6 +326,8 @@ class Orchestrator {
}
}
+ // store the history for the automation run
+ await storeLog(this._automation, this.executionOutput)
return this.executionOutput
}
}