Adding real history to the history tab, getting storage to couch and retrieval + api working.

This commit is contained in:
mike12345567 2022-06-01 16:01:06 +01:00
parent 702f4057bd
commit 34759c7916
15 changed files with 175 additions and 98 deletions

View File

@ -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)

View File

@ -65,7 +65,7 @@
<ActionButton
disabled={!$automationStore.selectedAutomation?.testResults}
on:click={() => {
$automationStore.selectedAutomation.automation.showTestPanel = true
$automationStore.showTestPanel = true
}}
size="M">Test Details</ActionButton
>

View File

@ -51,7 +51,7 @@
$automationStore.selectedAutomation?.automation,
testData
)
$automationStore.selectedAutomation.automation.showTestPanel = true
$automationStore.showTestPanel = true
} catch (error) {
notifications.error("Error testing notification")
}

View File

@ -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) {

View File

@ -36,7 +36,7 @@
<div style="padding-right: var(--spacing-xl)">
<Icon
on:click={async () => {
$automationStore.selectedAutomation.automation.showTestPanel = false
$automationStore.showTestPanel = false
}}
hoverable
name="Close"

View File

@ -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()
)
})
</script>

View File

@ -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)"

View File

@ -45,7 +45,7 @@
{/if}
</div>
{#if automation?.showTestPanel}
{#if $automationStore.showTestPanel}
<div class="setup">
<TestPanel {automation} />
</div>

View File

@ -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,
},
})
},
})

View File

@ -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
}

View File

@ -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 }),

View File

@ -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<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 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
}

View File

@ -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}`

View File

@ -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
}

View File

@ -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
}
}