Adding pagination control to the API and to the frontend, as well as getting view working as expected, emitting different key combinations to be able to search by any pattern.
This commit is contained in:
parent
34759c7916
commit
fe84c0f21c
|
@ -122,10 +122,12 @@ const automationActions = store => ({
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getLogs: async (automationId, startDate) => {
|
getLogs: async ({ automationId, startDate, status, page } = {}) => {
|
||||||
return await API.getAutomationLogs({
|
return await API.getAutomationLogs({
|
||||||
automationId,
|
automationId,
|
||||||
startDate,
|
startDate,
|
||||||
|
status,
|
||||||
|
page,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
addTestDataToAutomation: data => {
|
addTestDataToAutomation: data => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { Layout, Table, Select } from "@budibase/bbui"
|
import { Layout, Table, Select, Pagination } from "@budibase/bbui"
|
||||||
import DateTimeRenderer from "components/common/renderers/DateTimeRenderer.svelte"
|
import DateTimeRenderer from "components/common/renderers/DateTimeRenderer.svelte"
|
||||||
import StatusRenderer from "./StatusRenderer.svelte"
|
import StatusRenderer from "./StatusRenderer.svelte"
|
||||||
import HistoryDetailsPanel from "./HistoryDetailsPanel.svelte"
|
import HistoryDetailsPanel from "./HistoryDetailsPanel.svelte"
|
||||||
|
@ -8,9 +8,24 @@
|
||||||
|
|
||||||
export let appId
|
export let appId
|
||||||
|
|
||||||
|
let runHistory = []
|
||||||
let showPanel = false
|
let showPanel = false
|
||||||
let selectedHistory = null
|
let selectedHistory = null
|
||||||
let runHistory = []
|
let automationOptions = []
|
||||||
|
let automationId = null
|
||||||
|
let status = null
|
||||||
|
let prevPage,
|
||||||
|
nextPage,
|
||||||
|
page,
|
||||||
|
hasNextPage,
|
||||||
|
pageNumber = 1
|
||||||
|
|
||||||
|
$: fetchLogs(automationId, status, page)
|
||||||
|
|
||||||
|
const statusOptions = [
|
||||||
|
{ value: "success", label: "Success" },
|
||||||
|
{ value: "error", label: "Error" },
|
||||||
|
]
|
||||||
|
|
||||||
const runHistorySchema = {
|
const runHistorySchema = {
|
||||||
status: { displayName: "Status" },
|
status: { displayName: "Status" },
|
||||||
|
@ -23,7 +38,33 @@
|
||||||
{ column: "status", component: StatusRenderer },
|
{ column: "status", component: StatusRenderer },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
async function fetchLogs(automationId, status, page) {
|
||||||
|
const response = await automationStore.actions.getLogs({
|
||||||
|
automationId,
|
||||||
|
status,
|
||||||
|
page,
|
||||||
|
})
|
||||||
|
nextPage = response.nextPage
|
||||||
|
hasNextPage = response.hasNextPage
|
||||||
|
runHistory = enrichHistory($automationStore.blockDefinitions, response.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToNextPage() {
|
||||||
|
pageNumber++
|
||||||
|
prevPage = page
|
||||||
|
page = nextPage
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToPrevPage() {
|
||||||
|
pageNumber--
|
||||||
|
nextPage = page
|
||||||
|
page = prevPage
|
||||||
|
}
|
||||||
|
|
||||||
function enrichHistory(definitions, runHistory) {
|
function enrichHistory(definitions, runHistory) {
|
||||||
|
if (!definitions) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
const finalHistory = []
|
const finalHistory = []
|
||||||
for (let history of runHistory) {
|
for (let history of runHistory) {
|
||||||
if (!history.steps) {
|
if (!history.steps) {
|
||||||
|
@ -31,8 +72,8 @@
|
||||||
}
|
}
|
||||||
let notFound = false
|
let notFound = false
|
||||||
for (let step of history.steps) {
|
for (let step of history.steps) {
|
||||||
const trigger = definitions.trigger[step.stepId],
|
const trigger = definitions.TRIGGER[step.stepId],
|
||||||
action = definitions.action[step.stepId]
|
action = definitions.ACTION[step.stepId]
|
||||||
if (!trigger && !action) {
|
if (!trigger && !action) {
|
||||||
notFound = true
|
notFound = true
|
||||||
break
|
break
|
||||||
|
@ -53,11 +94,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
let definitions = await automationStore.actions.definitions()
|
await automationStore.actions.fetch()
|
||||||
runHistory = enrichHistory(
|
await fetchLogs()
|
||||||
definitions,
|
automationOptions = []
|
||||||
await automationStore.actions.getLogs()
|
for (let automation of $automationStore.automations) {
|
||||||
)
|
automationOptions.push({ value: automation._id, label: automation.name })
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -65,13 +107,23 @@
|
||||||
<Layout paddingX="XL" gap="S" alignContent="start">
|
<Layout paddingX="XL" gap="S" alignContent="start">
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<Select placeholder="All automations" label="Automation" />
|
<Select
|
||||||
|
placeholder="All automations"
|
||||||
|
label="Automation"
|
||||||
|
bind:value={automationId}
|
||||||
|
options={automationOptions}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<Select placeholder="Past 30 days" label="Date range" />
|
<Select placeholder="Past 30 days" label="Date range" />
|
||||||
</div>
|
</div>
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<Select placeholder="All status" label="Status" />
|
<Select
|
||||||
|
placeholder="All status"
|
||||||
|
label="Status"
|
||||||
|
bind:value={status}
|
||||||
|
options={statusOptions}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if runHistory}
|
{#if runHistory}
|
||||||
|
@ -95,6 +147,15 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pagination">
|
||||||
|
<Pagination
|
||||||
|
page={pageNumber}
|
||||||
|
hasPrevPage={pageNumber > 1}
|
||||||
|
{hasNextPage}
|
||||||
|
{goToPrevPage}
|
||||||
|
{goToNextPage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root {
|
.root {
|
||||||
|
@ -118,12 +179,11 @@
|
||||||
flex-basis: 150px;
|
flex-basis: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator {
|
.pagination {
|
||||||
flex-grow: 1;
|
position: absolute;
|
||||||
}
|
bottom: 0;
|
||||||
|
margin-bottom: var(--spacing-xl);
|
||||||
.searchInput {
|
margin-left: var(--spacing-l);
|
||||||
margin-top: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
|
|
|
@ -10,7 +10,9 @@
|
||||||
|
|
||||||
<div class="cell">
|
<div class="cell">
|
||||||
<Icon {color} name={isError ? "Alert" : "CheckmarkCircle"} />
|
<Icon {color} name={isError ? "Alert" : "CheckmarkCircle"} />
|
||||||
<div class:green={!isError} class:red={isError}>{value}</div>
|
<div class:green={!isError} class:red={isError}>
|
||||||
|
{isError ? "Error" : "Success"}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import CreateAutomationModal from "components/automation/AutomationPanel/CreateAutomationModal.svelte"
|
import CreateAutomationModal from "components/automation/AutomationPanel/CreateAutomationModal.svelte"
|
||||||
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
|
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
|
||||||
import TestPanel from "components/automation/AutomationBuilder/TestPanel.svelte"
|
import TestPanel from "components/automation/AutomationBuilder/TestPanel.svelte"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
$: automation =
|
$: automation =
|
||||||
$automationStore.selectedAutomation?.automation ||
|
$automationStore.selectedAutomation?.automation ||
|
||||||
|
@ -12,6 +13,9 @@
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
let webhookModal
|
let webhookModal
|
||||||
|
onMount(() => {
|
||||||
|
$automationStore.showTestPanel = false
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- routify:options index=3 -->
|
<!-- routify:options index=3 -->
|
||||||
|
|
|
@ -79,12 +79,14 @@ export const buildAutomationEndpoints = API => ({
|
||||||
* @param automationId The ID of the automation to get logs for.
|
* @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.
|
* @param startDate An ISO date string to state the start of the date range.
|
||||||
*/
|
*/
|
||||||
getAutomationLogs: async ({ automationId, startDate }) => {
|
getAutomationLogs: async ({ automationId, startDate, status, page }) => {
|
||||||
return await API.post({
|
return await API.post({
|
||||||
url: "/api/automations/logs/search",
|
url: "/api/automations/logs/search",
|
||||||
body: {
|
body: {
|
||||||
automationId,
|
automationId,
|
||||||
startDate,
|
startDate,
|
||||||
|
status,
|
||||||
|
page,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +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 { getLogs, oneDayAgo } = require("../../automations/logging")
|
||||||
const { getAutomationParams, generateAutomationID } = require("../../db/utils")
|
const { getAutomationParams, generateAutomationID } = require("../../db/utils")
|
||||||
const {
|
const {
|
||||||
checkForWebhooks,
|
checkForWebhooks,
|
||||||
|
@ -152,11 +152,11 @@ exports.destroy = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.logSearch = async function (ctx) {
|
exports.logSearch = async function (ctx) {
|
||||||
const { automationId } = ctx.request.body
|
const { automationId, status, page } = ctx.request.body
|
||||||
// TODO: check if there is a date range in the search params
|
// 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
|
// also check the date range vs their license, see if it is allowed
|
||||||
const startDate = oneDayAgo()
|
const startDate = oneDayAgo()
|
||||||
ctx.body = await getLogs(startDate, automationId)
|
ctx.body = await getLogs(startDate, status, automationId, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getActionList = async function (ctx) {
|
exports.getActionList = async function (ctx) {
|
||||||
|
|
|
@ -1,140 +0,0 @@
|
||||||
import {
|
|
||||||
AutomationLog,
|
|
||||||
AutomationResults,
|
|
||||||
AutomationStatus,
|
|
||||||
} from "../../definitions/automation"
|
|
||||||
import { getAppDB } from "@budibase/backend-core/context"
|
|
||||||
import {
|
|
||||||
generateAutomationLogID,
|
|
||||||
getAutomationLogParams,
|
|
||||||
getQueryIndex,
|
|
||||||
ViewNames,
|
|
||||||
} from "../../db/utils"
|
|
||||||
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
|
|
||||||
|
|
||||||
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 expiredEnd = oneDayAgo()
|
|
||||||
const results = await getAllLogs(EARLIEST_DATE, expiredEnd, {
|
|
||||||
include_docs: false,
|
|
||||||
})
|
|
||||||
const toDelete = results.map((doc: any) => ({
|
|
||||||
_id: doc.id,
|
|
||||||
_rev: doc.rev,
|
|
||||||
_deleted: true,
|
|
||||||
}))
|
|
||||||
await db.bulkDocs(toDelete)
|
|
||||||
}
|
|
||||||
|
|
||||||
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(automationId, isoDate)
|
|
||||||
|
|
||||||
await db.put({
|
|
||||||
// results contain automationId and status for view
|
|
||||||
...results,
|
|
||||||
automationId,
|
|
||||||
automationName: name,
|
|
||||||
status: getStatus(results),
|
|
||||||
createdAt: isoDate,
|
|
||||||
_id: id,
|
|
||||||
})
|
|
||||||
// clear up old history for app
|
|
||||||
await clearOldHistory()
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -0,0 +1,180 @@
|
||||||
|
import {
|
||||||
|
AutomationLog,
|
||||||
|
AutomationLogPage,
|
||||||
|
AutomationResults,
|
||||||
|
AutomationStatus,
|
||||||
|
} from "../../definitions/automation"
|
||||||
|
import { getAppDB } from "@budibase/backend-core/context"
|
||||||
|
import {
|
||||||
|
generateAutomationLogID,
|
||||||
|
getAutomationLogParams,
|
||||||
|
getQueryIndex,
|
||||||
|
ViewNames,
|
||||||
|
} from "../../db/utils"
|
||||||
|
import { createLogByAutomationView } from "../../db/views/staticViews"
|
||||||
|
import { Automation } from "../../definitions/common"
|
||||||
|
|
||||||
|
const PAGE_SIZE = 9
|
||||||
|
const EARLIEST_DATE = new Date(0).toISOString()
|
||||||
|
const FREE_EXPIRY_SEC = 86400
|
||||||
|
// 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 expiredEnd = oneDayAgo()
|
||||||
|
const results = await getAllLogs(EARLIEST_DATE, expiredEnd, {
|
||||||
|
docs: false,
|
||||||
|
})
|
||||||
|
const toDelete = results.data.map((doc: any) => ({
|
||||||
|
_id: doc.id,
|
||||||
|
_rev: doc.rev,
|
||||||
|
_deleted: true,
|
||||||
|
}))
|
||||||
|
await db.bulkDocs(toDelete)
|
||||||
|
}
|
||||||
|
|
||||||
|
function pagination(
|
||||||
|
response: any,
|
||||||
|
paginate: boolean = true
|
||||||
|
): AutomationLogPage {
|
||||||
|
const data = response.rows.map((row: any) => {
|
||||||
|
return row.doc ? row.doc : row
|
||||||
|
})
|
||||||
|
if (!paginate) {
|
||||||
|
return { data, hasNextPage: false }
|
||||||
|
}
|
||||||
|
const hasNextPage = data.length > PAGE_SIZE
|
||||||
|
return {
|
||||||
|
data: data.slice(0, PAGE_SIZE),
|
||||||
|
hasNextPage,
|
||||||
|
nextPage: hasNextPage ? data[PAGE_SIZE]?._id : undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAllLogs(
|
||||||
|
startDate: string,
|
||||||
|
endDate: string,
|
||||||
|
opts: {
|
||||||
|
docs: boolean
|
||||||
|
status?: string
|
||||||
|
paginate?: boolean
|
||||||
|
page?: string
|
||||||
|
} = { docs: true }
|
||||||
|
): Promise<AutomationLogPage> {
|
||||||
|
const db = getAppDB()
|
||||||
|
let optional: any = { status: opts.status }
|
||||||
|
const params = getAutomationLogParams(startDate, endDate, optional, {
|
||||||
|
include_docs: opts.docs,
|
||||||
|
limit: opts?.paginate ? PAGE_SIZE + 1 : undefined,
|
||||||
|
})
|
||||||
|
if (opts?.page) {
|
||||||
|
params.startkey = opts.page
|
||||||
|
}
|
||||||
|
let response = await db.allDocs(params)
|
||||||
|
return pagination(response, opts?.paginate)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLogsByView(
|
||||||
|
startDate: string,
|
||||||
|
endDate: string,
|
||||||
|
viewParams: { automationId?: string; status?: string; page?: string } = {}
|
||||||
|
): Promise<AutomationLogPage> {
|
||||||
|
const db = getAppDB()
|
||||||
|
let response
|
||||||
|
try {
|
||||||
|
let optional = {
|
||||||
|
automationId: viewParams?.automationId,
|
||||||
|
status: viewParams?.status,
|
||||||
|
}
|
||||||
|
const params = getAutomationLogParams(startDate, endDate, optional, {
|
||||||
|
include_docs: true,
|
||||||
|
limit: PAGE_SIZE,
|
||||||
|
})
|
||||||
|
if (viewParams?.page) {
|
||||||
|
params.startkey = viewParams.page
|
||||||
|
}
|
||||||
|
response = await db.query(getQueryIndex(ViewNames.AUTO_LOGS), params)
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err != null && err.name === "not_found") {
|
||||||
|
await createLogByAutomationView()
|
||||||
|
return getLogsByView(startDate, endDate, viewParams)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pagination(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function storeLog(
|
||||||
|
automation: Automation,
|
||||||
|
results: AutomationResults
|
||||||
|
) {
|
||||||
|
const db = getAppDB()
|
||||||
|
const automationId = automation._id
|
||||||
|
const name = automation.name
|
||||||
|
const status = getStatus(results)
|
||||||
|
const isoDate = new Date().toISOString()
|
||||||
|
const id = generateAutomationLogID(isoDate, status, automationId)
|
||||||
|
|
||||||
|
await db.put({
|
||||||
|
// results contain automationId and status for view
|
||||||
|
...results,
|
||||||
|
automationId,
|
||||||
|
status,
|
||||||
|
automationName: name,
|
||||||
|
createdAt: isoDate,
|
||||||
|
_id: id,
|
||||||
|
})
|
||||||
|
// clear up old logging for app
|
||||||
|
await clearOldHistory()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getLogs(
|
||||||
|
startDate: string,
|
||||||
|
status?: string,
|
||||||
|
automationId?: string,
|
||||||
|
page?: string
|
||||||
|
): Promise<AutomationLogPage> {
|
||||||
|
let response: AutomationLogPage
|
||||||
|
let endDate = new Date().toISOString()
|
||||||
|
if (automationId || status) {
|
||||||
|
response = await getLogsByView(startDate, endDate, {
|
||||||
|
automationId,
|
||||||
|
status,
|
||||||
|
page,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
response = await getAllLogs(startDate, endDate, {
|
||||||
|
status,
|
||||||
|
page,
|
||||||
|
docs: true,
|
||||||
|
paginate: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
|
@ -49,7 +49,13 @@ const DocumentTypes = {
|
||||||
const ViewNames = {
|
const ViewNames = {
|
||||||
LINK: "by_link",
|
LINK: "by_link",
|
||||||
ROUTING: "screen_routes",
|
ROUTING: "screen_routes",
|
||||||
LOGS_BY_AUTOMATION: "log_by_auto",
|
AUTO_LOGS: "auto_log",
|
||||||
|
}
|
||||||
|
|
||||||
|
const ViewModes = {
|
||||||
|
ALL: "all",
|
||||||
|
AUTOMATION: "auto",
|
||||||
|
STATUS: "status",
|
||||||
}
|
}
|
||||||
|
|
||||||
const InternalTables = {
|
const InternalTables = {
|
||||||
|
@ -77,6 +83,7 @@ exports.isProdAppID = isProdAppID
|
||||||
exports.USER_METDATA_PREFIX = `${DocumentTypes.ROW}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}`
|
exports.USER_METDATA_PREFIX = `${DocumentTypes.ROW}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}`
|
||||||
exports.LINK_USER_METADATA_PREFIX = `${DocumentTypes.LINK}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}`
|
exports.LINK_USER_METADATA_PREFIX = `${DocumentTypes.LINK}${SEPARATOR}${InternalTables.USER_METADATA}${SEPARATOR}`
|
||||||
exports.ViewNames = ViewNames
|
exports.ViewNames = ViewNames
|
||||||
|
exports.ViewModes = ViewModes
|
||||||
exports.InternalTables = InternalTables
|
exports.InternalTables = InternalTables
|
||||||
exports.DocumentTypes = DocumentTypes
|
exports.DocumentTypes = DocumentTypes
|
||||||
exports.SEPARATOR = SEPARATOR
|
exports.SEPARATOR = SEPARATOR
|
||||||
|
@ -364,26 +371,32 @@ exports.getMemoryViewParams = (otherProps = {}) => {
|
||||||
return getDocParams(DocumentTypes.MEM_VIEW, null, otherProps)
|
return getDocParams(DocumentTypes.MEM_VIEW, null, otherProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.generateAutomationLogID = (automationId, isoDate) => {
|
exports.generateAutomationLogID = (isoDate, status, automationId) => {
|
||||||
return `${DocumentTypes.AUTOMATION_LOG}${SEPARATOR}${isoDate}${SEPARATOR}${automationId}`
|
return `${DocumentTypes.AUTOMATION_LOG}${SEPARATOR}${isoDate}${SEPARATOR}${automationId}`
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getAutomationLogParams = (
|
exports.getAutomationLogParams = (
|
||||||
{ startDate, endDate, automationId } = {},
|
startDate,
|
||||||
|
endDate,
|
||||||
|
{ status, automationId } = {},
|
||||||
otherProps = {}
|
otherProps = {}
|
||||||
) => {
|
) => {
|
||||||
const base = `${DocumentTypes.AUTOMATION_LOG}${SEPARATOR}`
|
const automationBase = automationId ? `${automationId}${SEPARATOR}` : ""
|
||||||
let start = startDate || "",
|
const statusBase = status ? `${status}${SEPARATOR}` : ""
|
||||||
end = endDate || ""
|
let base
|
||||||
// reverse for view
|
if (status && automationId) {
|
||||||
if (automationId) {
|
base = `${ViewModes.ALL}${SEPARATOR}${statusBase}${automationBase}`
|
||||||
start = `${automationId}${SEPARATOR}${start}`
|
} else if (status) {
|
||||||
end = `${automationId}${SEPARATOR}${end}`
|
base = `${ViewModes.STATUS}${SEPARATOR}${statusBase}`
|
||||||
|
} else if (automationId) {
|
||||||
|
base = `${ViewModes.AUTOMATION}${SEPARATOR}${automationBase}`
|
||||||
|
} else {
|
||||||
|
base = `${DocumentTypes.AUTOMATION_LOG}${SEPARATOR}`
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...otherProps,
|
...otherProps,
|
||||||
startkey: `${base}${start}`,
|
startkey: `${base}${startDate}`,
|
||||||
endkey: `${base}${end}${UNICODE_MAX}`,
|
endkey: `${base}${endDate}${UNICODE_MAX}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ const {
|
||||||
DocumentTypes,
|
DocumentTypes,
|
||||||
SEPARATOR,
|
SEPARATOR,
|
||||||
ViewNames,
|
ViewNames,
|
||||||
|
ViewModes,
|
||||||
SearchIndexes,
|
SearchIndexes,
|
||||||
} = require("../utils")
|
} = require("../utils")
|
||||||
const SCREEN_PREFIX = DocumentTypes.SCREEN + SEPARATOR
|
const SCREEN_PREFIX = DocumentTypes.SCREEN + SEPARATOR
|
||||||
|
@ -69,14 +70,20 @@ exports.createLogByAutomationView = async () => {
|
||||||
const view = {
|
const view = {
|
||||||
map: `function(doc) {
|
map: `function(doc) {
|
||||||
if (doc._id.startsWith("${LOG_PREFIX}")) {
|
if (doc._id.startsWith("${LOG_PREFIX}")) {
|
||||||
let key = doc.automationId + ${SEPARATOR} + doc.createdAt
|
let autoId = doc.automationId + "${SEPARATOR}"
|
||||||
emit(key, doc._id)
|
let status = doc.status + "${SEPARATOR}"
|
||||||
|
let autoKey = "${ViewModes.AUTOMATION}${SEPARATOR}" + autoId + doc.createdAt
|
||||||
|
let statusKey = "${ViewModes.STATUS}${SEPARATOR}" + status + doc.createdAt
|
||||||
|
let allKey = "${ViewModes.ALL}${SEPARATOR}" + status + autoId + doc.createdAt
|
||||||
|
emit(statusKey)
|
||||||
|
emit(autoKey)
|
||||||
|
emit(allKey)
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
}
|
}
|
||||||
designDoc.views = {
|
designDoc.views = {
|
||||||
...designDoc.views,
|
...designDoc.views,
|
||||||
[ViewNames.LOGS_BY_AUTOMATION]: view,
|
[ViewNames.AUTO_LOGS]: view,
|
||||||
}
|
}
|
||||||
await db.put(designDoc)
|
await db.put(designDoc)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,3 +23,9 @@ export interface AutomationLog extends AutomationResults {
|
||||||
_id: string
|
_id: string
|
||||||
_rev: string
|
_rev: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AutomationLogPage {
|
||||||
|
data: AutomationLog[]
|
||||||
|
hasNextPage: boolean
|
||||||
|
nextPage?: string
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +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 { storeLog } = require("../automations/logging")
|
||||||
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
|
||||||
|
|
||||||
|
@ -326,7 +326,7 @@ class Orchestrator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// store the history for the automation run
|
// store the logs for the automation run
|
||||||
await storeLog(this._automation, this.executionOutput)
|
await storeLog(this._automation, this.executionOutput)
|
||||||
return this.executionOutput
|
return this.executionOutput
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue