Progress towards using couchdb for automation logs, after testing redis and realising it won't work.

This commit is contained in:
mike12345567 2022-05-31 19:52:35 +01:00
parent 37f9e71f5d
commit 0274516ad6
9 changed files with 149 additions and 13 deletions

View File

@ -19,6 +19,7 @@ exports.Databases = {
QUERY_VARS: "queryVars", QUERY_VARS: "queryVars",
LICENSES: "license", LICENSES: "license",
GENERIC_CACHE: "data_cache", GENERIC_CACHE: "data_cache",
AUTOMATION_LOGS: "autoLogs",
} }
exports.SEPARATOR = SEPARATOR exports.SEPARATOR = SEPARATOR

View File

@ -13,6 +13,13 @@
return results.steps.filter(x => x.stepId !== "LOOP" || []) return results.steps.filter(x => x.stepId !== "LOOP" || [])
} }
function textArea(results, message) {
if (!results) {
return message
}
return JSON.stringify(results, null, 2)
}
$: filteredResults = prepTestResults(testResults) $: filteredResults = prepTestResults(testResults)
$: { $: {
@ -66,11 +73,7 @@
<TextArea <TextArea
minHeight="80px" minHeight="80px"
disabled disabled
value={JSON.stringify( value={textArea(filteredResults?.[idx]?.inputs, "No input")}
filteredResults?.[idx]?.inputs,
null,
2
)}
/> />
</div></Tab </div></Tab
> >
@ -79,10 +82,9 @@
<TextArea <TextArea
minHeight="100px" minHeight="100px"
disabled disabled
value={JSON.stringify( value={textArea(
filteredResults?.[idx]?.outputs, filteredResults?.[idx]?.outputs,
null, "No output"
2
)} )}
/> />
</div> </div>

View File

@ -56,7 +56,7 @@
let definitions = await automationStore.actions.definitions() let definitions = await automationStore.actions.definitions()
runHistory = enrichHistory(definitions, [ runHistory = enrichHistory(definitions, [
{ {
status: "Error", status: "Success",
timestamp: "2022-05-11T16:06:14.438Z", timestamp: "2022-05-11T16:06:14.438Z",
name: "automation name", name: "automation name",
steps: [ steps: [
@ -88,7 +88,7 @@
], ],
}, },
{ {
status: "Success", status: "Error",
timestamp: "2022-05-11T16:03:14.438Z", timestamp: "2022-05-11T16:03:14.438Z",
name: "automation name", name: "automation name",
steps: [ steps: [
@ -151,7 +151,7 @@
} }
.panelOpen { .panelOpen {
grid-template-columns: auto 360px; grid-template-columns: auto 420px;
} }
.search { .search {
@ -178,7 +178,7 @@
position: absolute; position: absolute;
right: 0; right: 0;
height: 100%; height: 100%;
width: 360px; width: 420px;
overflow: hidden; overflow: hidden;
background-color: var(--background); background-color: var(--background);
} }

View File

@ -0,0 +1,67 @@
import { AutomationResults } from "../../definitions/automation"
import { getAppDB } from "@budibase/backend-core/context"
import {
generateAutomationLogID,
getAutomationLogParams,
// getQueryIndex,
// ViewNames,
} from "../../db/utils"
// import { createLogByAutomationView } from "../../db/views/staticViews"
const FREE_EXPIRY_SEC = 86400
// const PRO_EXPIRY_SEC = FREE_EXPIRY_SEC * 30
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 toDelete = results.map((doc: any) => ({
_id: doc.id,
_rev: doc.rev,
_deleted: true,
}))
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
) {
const db = getAppDB()
const isoDate = new Date().toISOString()
const id = generateAutomationLogID(isoDate, automationId)
await db.put({
// results contain automationId and status for view
...results,
createdAt: isoDate,
_id: id,
})
// clear up old history for app
await clearOldHistory()
}
export async function retrieve() {}

View File

@ -31,6 +31,7 @@ const DocumentTypes = {
ROW: "ro", ROW: "ro",
USER: "us", USER: "us",
AUTOMATION: "au", AUTOMATION: "au",
AUTOMATION_LOG: "log_au",
LINK: "li", LINK: "li",
WEBHOOK: "wh", WEBHOOK: "wh",
INSTANCE: "inst", INSTANCE: "inst",
@ -48,6 +49,7 @@ const DocumentTypes = {
const ViewNames = { const ViewNames = {
LINK: "by_link", LINK: "by_link",
ROUTING: "screen_routes", ROUTING: "screen_routes",
LOGS_BY_AUTOMATION: "log_by_auto",
} }
const InternalTables = { const InternalTables = {
@ -362,6 +364,29 @@ exports.getMemoryViewParams = (otherProps = {}) => {
return getDocParams(DocumentTypes.MEM_VIEW, null, otherProps) return getDocParams(DocumentTypes.MEM_VIEW, null, otherProps)
} }
exports.generateAutomationLogID = (automationId, isoDate) => {
return `${DocumentTypes.AUTOMATION_LOG}${SEPARATOR}${isoDate}${SEPARATOR}${automationId}`
}
exports.getAutomationLogParams = (
{ startIso, endIso, automationId } = {},
otherProps = {}
) => {
const base = `${DocumentTypes.AUTOMATION_LOG}${SEPARATOR}`
let start = startIso || "",
end = endIso || ""
// reverse for view
if (automationId) {
start = `${automationId}${SEPARATOR}${start}`
end = `${automationId}${SEPARATOR}${end}`
}
return {
...otherProps,
startkey: `${base}${start}`,
endkey: `${base}${end}${UNICODE_MAX}`,
}
}
/** /**
* This can be used with the db.allDocs to get a list of IDs * This can be used with the db.allDocs to get a list of IDs
*/ */

View File

@ -6,6 +6,7 @@ const {
SearchIndexes, SearchIndexes,
} = require("../utils") } = require("../utils")
const SCREEN_PREFIX = DocumentTypes.SCREEN + SEPARATOR const SCREEN_PREFIX = DocumentTypes.SCREEN + SEPARATOR
const LOG_PREFIX = DocumentTypes.AUTOMATION_LOG + SEPARATOR
/************************************************** /**************************************************
* INFORMATION * * INFORMATION *
@ -58,6 +59,28 @@ exports.createLinkView = async () => {
await db.put(designDoc) await db.put(designDoc)
} }
/**
* A separate view that allows us to perform queries by the automation ID and time series, while the
* main all_docs allows access to time series only
*/
exports.createLogByAutomationView = async () => {
const db = getAppDB()
const designDoc = await db.get("_design/database")
const view = {
map: `function(doc) {
if (doc._id.startsWith("${LOG_PREFIX}")) {
let key = doc.automationId + ${SEPARATOR} + doc.createdAt
emit(key, doc._id)
}
}`,
}
designDoc.views = {
...designDoc.views,
[ViewNames.LOGS_BY_AUTOMATION]: view,
}
await db.put(designDoc)
}
exports.createRoutingView = async () => { exports.createRoutingView = async () => {
const db = getAppDB() const db = getAppDB()
const designDoc = await db.get("_design/database") const designDoc = await db.get("_design/database")

View File

@ -0,0 +1,13 @@
export interface AutomationResults {
automationId: string
status: string
steps: {
stepId: string
inputs: {
[key: string]: any
}
outputs: {
[key: string]: any
}
}[]
}

View File

@ -9,3 +9,4 @@ declare module "@budibase/backend-core/constants"
declare module "@budibase/backend-core/auth" declare module "@budibase/backend-core/auth"
declare module "@budibase/backend-core/sessions" declare module "@budibase/backend-core/sessions"
declare module "@budibase/backend-core/encryption" declare module "@budibase/backend-core/encryption"
declare module "@budibase/backend-core/redis"

View File

@ -77,7 +77,7 @@ export class Thread {
}) })
} }
static shutdown() { static stopThreads() {
return new Promise<void>(resolve => { return new Promise<void>(resolve => {
if (Thread.workerRefs.length === 0) { if (Thread.workerRefs.length === 0) {
resolve() resolve()
@ -95,4 +95,8 @@ export class Thread {
Thread.workerRefs = [] Thread.workerRefs = []
}) })
} }
static async shutdown() {
await Thread.stopThreads()
}
} }