Adding real history to the history tab, getting storage to couch and retrieval + api working.
This commit is contained in:
parent
702f4057bd
commit
34759c7916
|
@ -6,6 +6,7 @@ import analytics, { Events } from "analytics"
|
||||||
|
|
||||||
const initialAutomationState = {
|
const initialAutomationState = {
|
||||||
automations: [],
|
automations: [],
|
||||||
|
showTestPanel: false,
|
||||||
blockDefinitions: {
|
blockDefinitions: {
|
||||||
TRIGGER: [],
|
TRIGGER: [],
|
||||||
ACTION: [],
|
ACTION: [],
|
||||||
|
@ -121,6 +122,12 @@ const automationActions = store => ({
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
getLogs: async (automationId, startDate) => {
|
||||||
|
return await API.getAutomationLogs({
|
||||||
|
automationId,
|
||||||
|
startDate,
|
||||||
|
})
|
||||||
|
},
|
||||||
addTestDataToAutomation: data => {
|
addTestDataToAutomation: data => {
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.selectedAutomation.addTestData(data)
|
state.selectedAutomation.addTestData(data)
|
||||||
|
|
|
@ -65,7 +65,7 @@
|
||||||
<ActionButton
|
<ActionButton
|
||||||
disabled={!$automationStore.selectedAutomation?.testResults}
|
disabled={!$automationStore.selectedAutomation?.testResults}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
$automationStore.selectedAutomation.automation.showTestPanel = true
|
$automationStore.showTestPanel = true
|
||||||
}}
|
}}
|
||||||
size="M">Test Details</ActionButton
|
size="M">Test Details</ActionButton
|
||||||
>
|
>
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
$automationStore.selectedAutomation?.automation,
|
$automationStore.selectedAutomation?.automation,
|
||||||
testData
|
testData
|
||||||
)
|
)
|
||||||
$automationStore.selectedAutomation.automation.showTestPanel = true
|
$automationStore.showTestPanel = true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error testing notification")
|
notifications.error("Error testing notification")
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
let blocks
|
let blocks
|
||||||
|
|
||||||
function prepTestResults(results) {
|
function prepTestResults(results) {
|
||||||
return results.steps.filter(x => x.stepId !== "LOOP" || [])
|
return results?.steps.filter(x => x.stepId !== "LOOP" || [])
|
||||||
}
|
}
|
||||||
|
|
||||||
function textArea(results, message) {
|
function textArea(results, message) {
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
<div style="padding-right: var(--spacing-xl)">
|
<div style="padding-right: var(--spacing-xl)">
|
||||||
<Icon
|
<Icon
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
$automationStore.selectedAutomation.automation.showTestPanel = false
|
$automationStore.showTestPanel = false
|
||||||
}}
|
}}
|
||||||
hoverable
|
hoverable
|
||||||
name="Close"
|
name="Close"
|
||||||
|
|
|
@ -14,12 +14,12 @@
|
||||||
|
|
||||||
const runHistorySchema = {
|
const runHistorySchema = {
|
||||||
status: { displayName: "Status" },
|
status: { displayName: "Status" },
|
||||||
timestamp: { displayName: "Time" },
|
createdAt: { displayName: "Time" },
|
||||||
name: { displayName: "Automation" },
|
automationName: { displayName: "Automation" },
|
||||||
}
|
}
|
||||||
|
|
||||||
const customRenderers = [
|
const customRenderers = [
|
||||||
{ column: "time", component: DateTimeRenderer },
|
{ column: "createdAt", component: DateTimeRenderer },
|
||||||
{ column: "status", component: StatusRenderer },
|
{ column: "status", component: StatusRenderer },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -54,57 +54,10 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
let definitions = await automationStore.actions.definitions()
|
let definitions = await automationStore.actions.definitions()
|
||||||
runHistory = enrichHistory(definitions, [
|
runHistory = enrichHistory(
|
||||||
{
|
definitions,
|
||||||
status: "Success",
|
await automationStore.actions.getLogs()
|
||||||
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: {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { Icon } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
export let value
|
export let value
|
||||||
|
|
||||||
$: isError = value === "Error"
|
$: isError = !value || value.toLowerCase() === "error"
|
||||||
$: color = isError
|
$: color = isError
|
||||||
? "var(--spectrum-semantic-negative-color-background)"
|
? "var(--spectrum-semantic-negative-color-background)"
|
||||||
: "var(--green)"
|
: "var(--green)"
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if automation?.showTestPanel}
|
{#if $automationStore.showTestPanel}
|
||||||
<div class="setup">
|
<div class="setup">
|
||||||
<TestPanel {automation} />
|
<TestPanel {automation} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -73,4 +73,19 @@ export const buildAutomationEndpoints = API => ({
|
||||||
url: `/api/automations/${automationId}/${automationRev}`,
|
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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const actions = require("../../automations/actions")
|
const actions = require("../../automations/actions")
|
||||||
const triggers = require("../../automations/triggers")
|
const triggers = require("../../automations/triggers")
|
||||||
|
const { getLogs, oneDayAgo } = require("../../automations/history")
|
||||||
const { getAutomationParams, generateAutomationID } = require("../../db/utils")
|
const { getAutomationParams, generateAutomationID } = require("../../db/utils")
|
||||||
const {
|
const {
|
||||||
checkForWebhooks,
|
checkForWebhooks,
|
||||||
|
@ -150,6 +151,14 @@ exports.destroy = async function (ctx) {
|
||||||
ctx.body = await db.remove(automationId, ctx.params.rev)
|
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) {
|
exports.getActionList = async function (ctx) {
|
||||||
ctx.body = ACTION_DEFS
|
ctx.body = ACTION_DEFS
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,11 @@ router
|
||||||
authorized(BUILDER),
|
authorized(BUILDER),
|
||||||
controller.destroy
|
controller.destroy
|
||||||
)
|
)
|
||||||
|
.post(
|
||||||
|
"/api/automations/logs/search",
|
||||||
|
authorized(BUILDER),
|
||||||
|
controller.logSearch
|
||||||
|
)
|
||||||
.post(
|
.post(
|
||||||
"/api/automations/:id/trigger",
|
"/api/automations/:id/trigger",
|
||||||
appInfoMiddleware({ appType: AppType.PROD }),
|
appInfoMiddleware({ appType: AppType.PROD }),
|
||||||
|
|
|
@ -1,24 +1,55 @@
|
||||||
import { AutomationResults } from "../../definitions/automation"
|
import {
|
||||||
|
AutomationLog,
|
||||||
|
AutomationResults,
|
||||||
|
AutomationStatus,
|
||||||
|
} from "../../definitions/automation"
|
||||||
import { getAppDB } from "@budibase/backend-core/context"
|
import { getAppDB } from "@budibase/backend-core/context"
|
||||||
import {
|
import {
|
||||||
generateAutomationLogID,
|
generateAutomationLogID,
|
||||||
getAutomationLogParams,
|
getAutomationLogParams,
|
||||||
// getQueryIndex,
|
getQueryIndex,
|
||||||
// ViewNames,
|
ViewNames,
|
||||||
} from "../../db/utils"
|
} 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 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() {
|
async function clearOldHistory() {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
// TODO: handle license lookup for deletion
|
// TODO: handle license lookup for deletion
|
||||||
const time = new Date(new Date().getTime() - FREE_EXPIRY_SEC * 1000)
|
const expiredEnd = oneDayAgo()
|
||||||
const queryParams: any = {
|
const results = await getAllLogs(EARLIEST_DATE, expiredEnd, {
|
||||||
endIso: time,
|
include_docs: false,
|
||||||
}
|
})
|
||||||
const results = await db.allDocs(getAutomationLogParams(queryParams))
|
|
||||||
const toDelete = results.map((doc: any) => ({
|
const toDelete = results.map((doc: any) => ({
|
||||||
_id: doc.id,
|
_id: doc.id,
|
||||||
_rev: doc.rev,
|
_rev: doc.rev,
|
||||||
|
@ -27,36 +58,66 @@ async function clearOldHistory() {
|
||||||
await db.bulkDocs(toDelete)
|
await db.bulkDocs(toDelete)
|
||||||
}
|
}
|
||||||
|
|
||||||
// async function getByAutomationID(automationId: string) {
|
async function getAllLogs(
|
||||||
// const db = getAppDB()
|
startDate: string,
|
||||||
// try {
|
endDate: string,
|
||||||
// const queryParams: any = {
|
opts: any = { include_docs: true }
|
||||||
// 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
|
|
||||||
) {
|
) {
|
||||||
|
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<AutomationLog[]> {
|
||||||
|
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 db = getAppDB()
|
||||||
const isoDate = new Date().toISOString()
|
const isoDate = new Date().toISOString()
|
||||||
const id = generateAutomationLogID(isoDate, automationId)
|
const id = generateAutomationLogID(automationId, isoDate)
|
||||||
|
|
||||||
await db.put({
|
await db.put({
|
||||||
// results contain automationId and status for view
|
// results contain automationId and status for view
|
||||||
...results,
|
...results,
|
||||||
|
automationId,
|
||||||
|
automationName: name,
|
||||||
|
status: getStatus(results),
|
||||||
createdAt: isoDate,
|
createdAt: isoDate,
|
||||||
_id: id,
|
_id: id,
|
||||||
})
|
})
|
||||||
|
@ -64,4 +125,16 @@ export async function storeHistory(
|
||||||
await clearOldHistory()
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -369,12 +369,12 @@ exports.generateAutomationLogID = (automationId, isoDate) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getAutomationLogParams = (
|
exports.getAutomationLogParams = (
|
||||||
{ startIso, endIso, automationId } = {},
|
{ startDate, endDate, automationId } = {},
|
||||||
otherProps = {}
|
otherProps = {}
|
||||||
) => {
|
) => {
|
||||||
const base = `${DocumentTypes.AUTOMATION_LOG}${SEPARATOR}`
|
const base = `${DocumentTypes.AUTOMATION_LOG}${SEPARATOR}`
|
||||||
let start = startIso || "",
|
let start = startDate || "",
|
||||||
end = endIso || ""
|
end = endDate || ""
|
||||||
// reverse for view
|
// reverse for view
|
||||||
if (automationId) {
|
if (automationId) {
|
||||||
start = `${automationId}${SEPARATOR}${start}`
|
start = `${automationId}${SEPARATOR}${start}`
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
|
export enum AutomationStatus {
|
||||||
|
SUCCESS = "success",
|
||||||
|
ERROR = "error",
|
||||||
|
}
|
||||||
|
|
||||||
export interface AutomationResults {
|
export interface AutomationResults {
|
||||||
automationId: string
|
automationId: string
|
||||||
status: string
|
status: string
|
||||||
|
trigger?: any
|
||||||
steps: {
|
steps: {
|
||||||
stepId: string
|
stepId: string
|
||||||
inputs: {
|
inputs: {
|
||||||
|
@ -11,3 +17,9 @@ export interface AutomationResults {
|
||||||
}
|
}
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AutomationLog extends AutomationResults {
|
||||||
|
createdAt: string
|
||||||
|
_id: string
|
||||||
|
_rev: string
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ const { doInTenant } = require("@budibase/backend-core/tenancy")
|
||||||
const { definitions: triggerDefs } = require("../automations/triggerInfo")
|
const { definitions: triggerDefs } = require("../automations/triggerInfo")
|
||||||
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
|
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
|
||||||
const { AutomationErrors, LoopStepTypes } = require("../constants")
|
const { AutomationErrors, LoopStepTypes } = require("../constants")
|
||||||
|
const { storeLog } = require("../automations/history")
|
||||||
const FILTER_STEP_ID = actions.ACTION_DEFINITIONS.FILTER.stepId
|
const FILTER_STEP_ID = actions.ACTION_DEFINITIONS.FILTER.stepId
|
||||||
const LOOP_STEP_ID = actions.ACTION_DEFINITIONS.LOOP.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
|
return this.executionOutput
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue