Adding metadata update with automation IDs and counts of errors which can be totalled in frontend, also moving to using production apps for history only.
This commit is contained in:
parent
5d82a39af3
commit
5106214bc6
|
@ -3,15 +3,20 @@ import {
|
||||||
AutomationResults,
|
AutomationResults,
|
||||||
AutomationStatus,
|
AutomationStatus,
|
||||||
} from "../../definitions/automation"
|
} from "../../definitions/automation"
|
||||||
import { getAppDB } from "@budibase/backend-core/context"
|
import { getAppId, getProdAppDB } from "@budibase/backend-core/context"
|
||||||
import {
|
import {
|
||||||
|
DocumentTypes,
|
||||||
generateAutomationLogID,
|
generateAutomationLogID,
|
||||||
getAutomationLogParams,
|
getAutomationLogParams,
|
||||||
getQueryIndex,
|
getQueryIndex,
|
||||||
ViewNames,
|
ViewNames,
|
||||||
|
SEPARATOR,
|
||||||
|
isProdAppID,
|
||||||
} from "../../db/utils"
|
} from "../../db/utils"
|
||||||
import { createLogByAutomationView } from "../../db/views/staticViews"
|
import { createLogByAutomationView } from "../../db/views/staticViews"
|
||||||
import { Automation } from "../../definitions/common"
|
import { Automation, MetadataErrors } from "../../definitions/common"
|
||||||
|
import { invalidateAppMetadata } from "@budibase/backend-core/cache"
|
||||||
|
import { backOff } from "../../utilities"
|
||||||
import * as env from "../../environment"
|
import * as env from "../../environment"
|
||||||
|
|
||||||
const PAGE_SIZE = 9
|
const PAGE_SIZE = 9
|
||||||
|
@ -46,18 +51,32 @@ export function oneDayAgo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clearOldHistory() {
|
async function clearOldHistory() {
|
||||||
const db = getAppDB()
|
const db = getProdAppDB()
|
||||||
// TODO: handle license lookup for deletion
|
// TODO: handle license lookup for deletion
|
||||||
const expiredEnd = oneDayAgo()
|
const expiredEnd = oneDayAgo()
|
||||||
const results = await getAllLogs(EARLIEST_DATE, expiredEnd, {
|
const results = await getAllLogs(EARLIEST_DATE, expiredEnd, {
|
||||||
docs: false,
|
docs: false,
|
||||||
|
paginate: false,
|
||||||
})
|
})
|
||||||
const toDelete = results.data.map((doc: any) => ({
|
const toDelete = results.data.map((doc: any) => ({
|
||||||
_id: doc.id,
|
_id: doc.id,
|
||||||
_rev: doc.rev,
|
_rev: doc.value.rev,
|
||||||
_deleted: true,
|
_deleted: true,
|
||||||
}))
|
}))
|
||||||
|
const errorLogIds = results.data
|
||||||
|
.filter((doc: any) => {
|
||||||
|
const parts = doc.id.split(SEPARATOR)
|
||||||
|
const status = parts[parts.length - 1]
|
||||||
|
return status === AutomationStatus.ERROR
|
||||||
|
})
|
||||||
|
.map((doc: any) => {
|
||||||
|
const parts = doc.id.split(SEPARATOR)
|
||||||
|
return `${parts[parts.length - 3]}${SEPARATOR}${parts[parts.length - 2]}`
|
||||||
|
})
|
||||||
await db.bulkDocs(toDelete)
|
await db.bulkDocs(toDelete)
|
||||||
|
if (errorLogIds.length) {
|
||||||
|
await updateAppMetadataWithErrors(errorLogIds)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pagination(
|
function pagination(
|
||||||
|
@ -88,7 +107,7 @@ async function getAllLogs(
|
||||||
page?: string
|
page?: string
|
||||||
} = { docs: true }
|
} = { docs: true }
|
||||||
): Promise<AutomationLogPage> {
|
): Promise<AutomationLogPage> {
|
||||||
const db = getAppDB()
|
const db = getProdAppDB()
|
||||||
let optional: any = { status: opts.status }
|
let optional: any = { status: opts.status }
|
||||||
const params = getAutomationLogParams(startDate, endDate, optional, {
|
const params = getAutomationLogParams(startDate, endDate, optional, {
|
||||||
include_docs: opts.docs,
|
include_docs: opts.docs,
|
||||||
|
@ -106,7 +125,7 @@ async function getLogsByView(
|
||||||
endDate: string,
|
endDate: string,
|
||||||
viewParams: { automationId?: string; status?: string; page?: string } = {}
|
viewParams: { automationId?: string; status?: string; page?: string } = {}
|
||||||
): Promise<AutomationLogPage> {
|
): Promise<AutomationLogPage> {
|
||||||
const db = getAppDB()
|
const db = getProdAppDB()
|
||||||
let response
|
let response
|
||||||
try {
|
try {
|
||||||
let optional = {
|
let optional = {
|
||||||
|
@ -130,21 +149,49 @@ async function getLogsByView(
|
||||||
return pagination(response)
|
return pagination(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function updateAppMetadataWithErrors(
|
||||||
|
automationIds: string[],
|
||||||
|
{ clearing } = { clearing: false }
|
||||||
|
) {
|
||||||
|
const db = getProdAppDB()
|
||||||
|
// this will try multiple times with a delay between to update the metadata
|
||||||
|
await backOff(async () => {
|
||||||
|
const metadata = await db.get(DocumentTypes.APP_METADATA)
|
||||||
|
for (let automationId of automationIds) {
|
||||||
|
let errors: MetadataErrors = {}
|
||||||
|
if (metadata.automationErrors) {
|
||||||
|
errors = metadata.automationErrors as MetadataErrors
|
||||||
|
}
|
||||||
|
const change = clearing ? -1 : 1
|
||||||
|
errors[automationId] = errors[automationId]
|
||||||
|
? errors[automationId] + change
|
||||||
|
: 1
|
||||||
|
// if clearing and reach zero, this will pass and will remove the element
|
||||||
|
if (!errors[automationId]) {
|
||||||
|
delete errors[automationId]
|
||||||
|
}
|
||||||
|
metadata.automationErrors = errors
|
||||||
|
}
|
||||||
|
await db.put(metadata)
|
||||||
|
// don't update cache until after DB put, make sure it has been stored successfully
|
||||||
|
await invalidateAppMetadata(metadata.appId, metadata)
|
||||||
|
}, "Failed to update app metadata with automation log error")
|
||||||
|
}
|
||||||
|
|
||||||
export async function storeLog(
|
export async function storeLog(
|
||||||
automation: Automation,
|
automation: Automation,
|
||||||
results: AutomationResults
|
results: AutomationResults
|
||||||
) {
|
) {
|
||||||
// can disable this if un-needed in self-host
|
// can disable this if un-needed in self-host, also only do this for prod apps
|
||||||
if (env.DISABLE_AUTOMATION_LOGS) {
|
if (env.DISABLE_AUTOMATION_LOGS || !isProdAppID(getAppId())) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const db = getAppDB()
|
const db = getProdAppDB()
|
||||||
const automationId = automation._id
|
const automationId = automation._id
|
||||||
const name = automation.name
|
const name = automation.name
|
||||||
const status = getStatus(results)
|
const status = getStatus(results)
|
||||||
const isoDate = new Date().toISOString()
|
const isoDate = new Date().toISOString()
|
||||||
const id = generateAutomationLogID(isoDate, status, automationId)
|
const id = generateAutomationLogID(isoDate, status, automationId)
|
||||||
|
|
||||||
await db.put({
|
await db.put({
|
||||||
// results contain automationId and status for view
|
// results contain automationId and status for view
|
||||||
...results,
|
...results,
|
||||||
|
@ -154,6 +201,12 @@ export async function storeLog(
|
||||||
createdAt: isoDate,
|
createdAt: isoDate,
|
||||||
_id: id,
|
_id: id,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// need to note on the app metadata that there is an error, store what the error is
|
||||||
|
if (status === AutomationStatus.ERROR) {
|
||||||
|
await updateAppMetadataWithErrors([automation._id as string])
|
||||||
|
}
|
||||||
|
|
||||||
// clear up old logging for app
|
// clear up old logging for app
|
||||||
await clearOldHistory()
|
await clearOldHistory()
|
||||||
}
|
}
|
||||||
|
|
|
@ -373,7 +373,7 @@ exports.getMemoryViewParams = (otherProps = {}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.generateAutomationLogID = (isoDate, status, automationId) => {
|
exports.generateAutomationLogID = (isoDate, status, automationId) => {
|
||||||
return `${DocumentTypes.AUTOMATION_LOG}${SEPARATOR}${isoDate}${SEPARATOR}${automationId}`
|
return `${DocumentTypes.AUTOMATION_LOG}${SEPARATOR}${isoDate}${SEPARATOR}${automationId}${SEPARATOR}${status}`
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getAutomationLogParams = (
|
exports.getAutomationLogParams = (
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { AutomationStatus } from "./automation"
|
||||||
|
|
||||||
export { Query, Datasource } from "./datasource"
|
export { Query, Datasource } from "./datasource"
|
||||||
|
|
||||||
export interface Base {
|
export interface Base {
|
||||||
|
@ -102,3 +104,5 @@ export interface Automation extends Base {
|
||||||
trigger?: AutomationStep
|
trigger?: AutomationStep
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MetadataErrors = { [key: string]: number }
|
||||||
|
|
|
@ -4,6 +4,7 @@ const { sanitizeKey } = require("@budibase/backend-core/objectStore")
|
||||||
const { generateMetadataID } = require("../db/utils")
|
const { generateMetadataID } = require("../db/utils")
|
||||||
const Readable = require("stream").Readable
|
const Readable = require("stream").Readable
|
||||||
const { getAppDB } = require("@budibase/backend-core/context")
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
const { logAlert } = require("@budibase/backend-core/logging")
|
||||||
|
|
||||||
const BB_CDN = "https://cdn.budi.live"
|
const BB_CDN = "https://cdn.budi.live"
|
||||||
|
|
||||||
|
@ -13,6 +14,42 @@ exports.isDev = env.isDev
|
||||||
|
|
||||||
exports.NUMBER_REGEX = /^[+-]?([0-9]*[.])?[0-9]+$/g
|
exports.NUMBER_REGEX = /^[+-]?([0-9]*[.])?[0-9]+$/g
|
||||||
|
|
||||||
|
exports.randomDelay = fn => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
resolve(await fn())
|
||||||
|
} catch (err) {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
}, Math.floor(Math.random() * 1000))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.backOff = async (fn, errMsg) => {
|
||||||
|
let attempts = 5,
|
||||||
|
success = false,
|
||||||
|
response,
|
||||||
|
first = true
|
||||||
|
for (; attempts > 0; attempts--) {
|
||||||
|
try {
|
||||||
|
if (first) {
|
||||||
|
response = await fn()
|
||||||
|
} else {
|
||||||
|
response = await exports.randomDelay(fn)
|
||||||
|
}
|
||||||
|
success = true
|
||||||
|
break
|
||||||
|
} catch (err) {
|
||||||
|
// ignore error here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
logAlert("Failed to backoff - ", errMsg)
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
exports.removeFromArray = (array, element) => {
|
exports.removeFromArray = (array, element) => {
|
||||||
const index = array.indexOf(element)
|
const index = array.indexOf(element)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
|
|
Loading…
Reference in New Issue