From 063540fe0a76064d6df4d1759767d73d3a0f69bc Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 22 Jun 2022 20:23:18 +0100 Subject: [PATCH] Getting notifications working correctly, linking to errors in a better way, generally improving UI, getting some final touches here and there. --- packages/bbui/src/Table/Table.svelte | 3 +- .../builderStore/store/automation/index.js | 10 ++-- .../AutomationBuilder/TestDisplay.svelte | 2 + .../overview/HistoryDetailsPanel.svelte | 15 ++++-- .../portal/overview/HistoryTab.svelte | 51 +++++++++++++------ .../pages/builder/portal/apps/index.svelte | 49 ++++++++++-------- packages/frontend-core/src/api/automations.js | 18 +++++++ .../server/src/api/controllers/automation.js | 32 +++++++++++- packages/server/src/api/routes/automation.js | 15 ++++-- .../server/src/automations/logging/index.ts | 34 ++++++++----- packages/server/src/db/utils.js | 5 +- packages/server/src/definitions/common.ts | 2 +- 12 files changed, 167 insertions(+), 69 deletions(-) diff --git a/packages/bbui/src/Table/Table.svelte b/packages/bbui/src/Table/Table.svelte index baa84c91e0..e01d84e123 100644 --- a/packages/bbui/src/Table/Table.svelte +++ b/packages/bbui/src/Table/Table.svelte @@ -37,6 +37,7 @@ export let autoSortColumns = true export let compact = false export let customPlaceholder = false + export let placeholderText = "No rows found" const dispatch = createEventDispatcher() @@ -405,7 +406,7 @@ > -
No rows found
+
{placeholderText}
{/if} diff --git a/packages/builder/src/builderStore/store/automation/index.js b/packages/builder/src/builderStore/store/automation/index.js index f7db448693..dd09e3356a 100644 --- a/packages/builder/src/builderStore/store/automation/index.js +++ b/packages/builder/src/builderStore/store/automation/index.js @@ -129,7 +129,12 @@ const automationActions = store => ({ page, }) }, - clearLogErrors: async () => {}, + clearLogErrors: async ({ automationId, appId } = {}) => { + return await API.clearAutomationLogErrors({ + automationId, + appId, + }) + }, addTestDataToAutomation: data => { store.update(state => { state.selectedAutomation.addTestData(data) @@ -138,11 +143,10 @@ const automationActions = store => ({ }, addBlockToAutomation: (block, blockIdx) => { store.update(state => { - const newBlock = state.selectedAutomation.addBlock( + state.selectedBlock = state.selectedAutomation.addBlock( cloneDeep(block), blockIdx ) - state.selectedBlock = newBlock return state }) }, diff --git a/packages/builder/src/components/automation/AutomationBuilder/TestDisplay.svelte b/packages/builder/src/components/automation/AutomationBuilder/TestDisplay.svelte index 6d4c83de11..a2eb904c94 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/TestDisplay.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/TestDisplay.svelte @@ -103,6 +103,7 @@ diff --git a/packages/builder/src/pages/builder/portal/apps/index.svelte b/packages/builder/src/pages/builder/portal/apps/index.svelte index 6c51a6fd0a..de5ad178cb 100644 --- a/packages/builder/src/pages/builder/portal/apps/index.svelte +++ b/packages/builder/src/pages/builder/portal/apps/index.svelte @@ -96,27 +96,30 @@ const automationErrors = {} for (let app of apps) { if (app.automationErrors) { - automationErrors[app.devId] = app.automationErrors + if (errorCount(app.automationErrors) > 0) { + automationErrors[app.devId] = app.automationErrors + } } } return automationErrors } const goToAutomationError = appId => { - const params = new URLSearchParams({ tab: "Automation History" }) + const params = new URLSearchParams({ + tab: "Automation History", + open: "error", + }) $goto(`../overview/${appId}?${params.toString()}`) } - const errorCount = appId => { - return Object.values(automationErrors[appId]).reduce( - (prev, next) => prev + next, - 0 - ) + const errorCount = errors => { + return Object.values(errors).reduce((acc, next) => acc + next.length, 0) } const automationErrorMessage = appId => { const app = enrichedApps.find(app => app.devId === appId) - return `${app.name} - Automation error (${errorCount(appId)})` + const errors = automationErrors[appId] + return `${app.name} - Automation error (${errorCount(errors)})` } const initiateAppCreation = () => { @@ -238,19 +241,23 @@ {#if loaded} - {#if automationErrors} - {#each Object.keys(automationErrors) as appId} - goToAutomationError(appId)} - type="error" - icon="Alert" - actionMessage={errorCount(appId) > 1 ? "View errors" : "View error"} - message={automationErrorMessage(appId)} - /> - {/each} - {/if} + {#each Object.keys(automationErrors || {}) as appId} + goToAutomationError(appId)} + type="error" + icon="Alert" + actionMessage={errorCount(automationErrors[appId]) > 1 + ? "View errors" + : "View error"} + on:dismiss={async () => { + await automationStore.actions.clearLogErrors({ appId }) + await apps.load() + }} + message={automationErrorMessage(appId)} + /> + {/each}
diff --git a/packages/frontend-core/src/api/automations.js b/packages/frontend-core/src/api/automations.js index ebb7c5cf56..c0b770a1c2 100644 --- a/packages/frontend-core/src/api/automations.js +++ b/packages/frontend-core/src/api/automations.js @@ -78,6 +78,8 @@ export const buildAutomationEndpoints = API => ({ * 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. + * @param status The status, error or success. + * @param page The page to retrieve. */ getAutomationLogs: async ({ automationId, startDate, status, page }) => { return await API.post({ @@ -90,4 +92,20 @@ export const buildAutomationEndpoints = API => ({ }, }) }, + + /** + * Clears automation log errors (which are creating notification) for + * automation or the app. + * @param automationId optional - the ID of the automation to clear errors for. + * @param appId The app ID to clear errors for. + */ + clearAutomationLogErrors: async ({ automationId, appId }) => { + return await API.delete({ + url: "/api/automations/logs", + body: { + appId, + automationId, + }, + }) + }, }) diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index 9bcbfc1146..4224ff5b5b 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -1,7 +1,11 @@ const actions = require("../../automations/actions") const triggers = require("../../automations/triggers") const { getLogs, oneDayAgo } = require("../../automations/logging") -const { getAutomationParams, generateAutomationID } = require("../../db/utils") +const { + getAutomationParams, + generateAutomationID, + DocumentTypes, +} = require("../../db/utils") const { checkForWebhooks, updateTestHistory, @@ -10,8 +14,13 @@ const { const { deleteEntityMetadata } = require("../../utilities") const { MetadataTypes } = require("../../constants") const { setTestFlag, clearTestFlag } = require("../../utilities/redis") -const { getAppDB } = require("@budibase/backend-core/context") +const { + getAppDB, + getProdAppDB, + doInAppContext, +} = require("@budibase/backend-core/context") const { events } = require("@budibase/backend-core") +const { app } = require("@budibase/backend-core/cache") const ACTION_DEFS = removeDeprecated(actions.ACTION_DEFINITIONS) const TRIGGER_DEFS = removeDeprecated(triggers.TRIGGER_DEFINITIONS) @@ -192,6 +201,25 @@ exports.logSearch = async function (ctx) { ctx.body = await getLogs(startDate, status, automationId, page) } +exports.clearLogError = async function (ctx) { + const { automationId, appId } = ctx.request.body + await doInAppContext(appId, async () => { + const db = getProdAppDB() + const metadata = await db.get(DocumentTypes.APP_METADATA) + if (!automationId) { + delete metadata.automationErrors + } else if ( + metadata.automationErrors && + metadata.automationErrors[automationId] + ) { + delete metadata.automationErrors[automationId] + } + await db.put(metadata) + await app.invalidateAppMetadata(metadata.appId, metadata) + ctx.body = { message: `Error logs cleared.` } + }) +} + exports.getActionList = async function (ctx) { ctx.body = ACTION_DEFS } diff --git a/packages/server/src/api/routes/automation.js b/packages/server/src/api/routes/automation.js index d25f7bc328..e0f4744e1e 100644 --- a/packages/server/src/api/routes/automation.js +++ b/packages/server/src/api/routes/automation.js @@ -51,17 +51,22 @@ router automationValidator(false), controller.create ) + .post( + "/api/automations/logs/search", + authorized(BUILDER), + controller.logSearch + ) + .delete( + "/api/automations/logs", + authorized(BUILDER), + controller.clearLogError + ) .delete( "/api/automations/:id/:rev", paramResource("id"), authorized(BUILDER), controller.destroy ) - .post( - "/api/automations/logs/search", - authorized(BUILDER), - controller.logSearch - ) .post( "/api/automations/:id/trigger", appInfoMiddleware({ appType: AppType.PROD }), diff --git a/packages/server/src/automations/logging/index.ts b/packages/server/src/automations/logging/index.ts index 5ff46b007d..e6b851675f 100644 --- a/packages/server/src/automations/logging/index.ts +++ b/packages/server/src/automations/logging/index.ts @@ -69,13 +69,10 @@ async function clearOldHistory() { 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]}` - }) + .map((doc: any) => doc.id) await db.bulkDocs(toDelete) if (errorLogIds.length) { - await updateAppMetadataWithErrors(errorLogIds) + await updateAppMetadataWithErrors(errorLogIds, { clearing: true }) } } @@ -150,25 +147,34 @@ async function getLogsByView( } async function updateAppMetadataWithErrors( - automationIds: string[], + logIds: 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) { + for (let logId of logIds) { + const parts = logId.split(SEPARATOR) + const autoId = `${parts[parts.length - 3]}${SEPARATOR}${ + parts[parts.length - 2] + }` 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 (!Array.isArray(errors[autoId])) { + errors[autoId] = [] + } + const idx = errors[autoId].indexOf(logId) + if (clearing && idx !== -1) { + errors[autoId].splice(idx, 1) + } else { + errors[autoId].push(logId) + } // if clearing and reach zero, this will pass and will remove the element - if (!errors[automationId]) { - delete errors[automationId] + if (errors[autoId].length === 0) { + delete errors[autoId] } metadata.automationErrors = errors } @@ -204,7 +210,7 @@ export async function storeLog( // 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]) + await updateAppMetadataWithErrors([id]) } // clear up old logging for app diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index 45220ff86e..10c1c98f38 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -396,8 +396,9 @@ exports.getAutomationLogParams = ( } return { ...otherProps, - startkey: `${base}${startDate}`, - endkey: `${base}${endDate}${UNICODE_MAX}`, + descending: true, + startkey: `${base}${endDate}${UNICODE_MAX}`, + endkey: `${base}${startDate}`, } } diff --git a/packages/server/src/definitions/common.ts b/packages/server/src/definitions/common.ts index 7a7b0a70f8..c7573b4cc2 100644 --- a/packages/server/src/definitions/common.ts +++ b/packages/server/src/definitions/common.ts @@ -105,4 +105,4 @@ export interface Automation extends Base { } } -export type MetadataErrors = { [key: string]: number } +export type MetadataErrors = { [key: string]: string[] }