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 = {
|
||||
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)
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
<ActionButton
|
||||
disabled={!$automationStore.selectedAutomation?.testResults}
|
||||
on:click={() => {
|
||||
$automationStore.selectedAutomation.automation.showTestPanel = true
|
||||
$automationStore.showTestPanel = true
|
||||
}}
|
||||
size="M">Test Details</ActionButton
|
||||
>
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
$automationStore.selectedAutomation?.automation,
|
||||
testData
|
||||
)
|
||||
$automationStore.selectedAutomation.automation.showTestPanel = true
|
||||
$automationStore.showTestPanel = true
|
||||
} catch (error) {
|
||||
notifications.error("Error testing notification")
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
{#if automation?.showTestPanel}
|
||||
{#if $automationStore.showTestPanel}
|
||||
<div class="setup">
|
||||
<TestPanel {automation} />
|
||||
</div>
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 }),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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}`
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue