From 8d9fb29848de81f550a8d76bf48703b74014388b Mon Sep 17 00:00:00 2001 From: FoxxMD Date: Wed, 9 Feb 2022 13:08:48 -0500 Subject: [PATCH] feat(ui): Implement run context for actioned events * Refactor events view to show checks within runs * Build cohesive runs server-side before rendering so user can see all checks in a run together * Add collapse/expand behavior for activity/run/check with ability to toggle based on triggered state * Default to collapsing all non-triggered states * Build check summary on-the-fly instead of storing in event result data --- .../Migrations/Cache/1644350232664-init.ts | 2 - src/Common/interfaces.ts | 7 +- src/Subreddit/Manager.ts | 3 +- src/Web/Client/index.ts | 107 ++++++++++------- src/Web/assets/public/app.css | 16 +++ src/Web/assets/views/events.ejs | 112 ++++++++++++++---- 6 files changed, 174 insertions(+), 73 deletions(-) diff --git a/src/Common/Migrations/Cache/1644350232664-init.ts b/src/Common/Migrations/Cache/1644350232664-init.ts index ce19767..daf948b 100644 --- a/src/Common/Migrations/Cache/1644350232664-init.ts +++ b/src/Common/Migrations/Cache/1644350232664-init.ts @@ -51,7 +51,6 @@ export const up = async (context: any, next: any) => { run: 'Run1', postBehavior: 'nextRun', triggered: true, - ruleSummary, condition: ruleSummary.includes('OR') ? 'OR' : 'AND', ruleResults, actionResults, @@ -106,7 +105,6 @@ export const down = async (context: any, next: any) => { ruleResults: y.ruleResults, actionResults: y.actionResults, check: y.name, - ruleSummary: y.ruleSummary } }); return acc.concat(singleEvents); diff --git a/src/Common/interfaces.ts b/src/Common/interfaces.ts index 8dcfa14..2f0f5e7 100644 --- a/src/Common/interfaces.ts +++ b/src/Common/interfaces.ts @@ -1971,11 +1971,7 @@ export interface ActionedEvent { } author: string timestamp: number - //check: string - //ruleSummary: string, subreddit: string, - //ruleResults: RuleResult[] - //actionResults: ActionResult[] runResults: CheckSummary[] } @@ -1991,8 +1987,7 @@ export interface CheckSummary extends CheckResult { postBehavior: string error?: string actionResults: ActionResult[] - condition: string - ruleSummary: string + condition: 'AND' | 'OR' } export interface UserResultCache { diff --git a/src/Subreddit/Manager.ts b/src/Subreddit/Manager.ts index 3a7227c..19cb04a 100644 --- a/src/Subreddit/Manager.ts +++ b/src/Subreddit/Manager.ts @@ -647,7 +647,7 @@ export class Manager extends EventEmitter { ePeek = peek; this.logger.info(` ${peek}`); } catch (err: any) { - this.logger.error(`Error occurred while generate item peek for ${checkType} Activity ${itemId}`, err); + this.logger.error(`Error occurred while generating item peek for ${checkType} Activity ${itemId}`, err); } let checksRun = 0; @@ -911,7 +911,6 @@ export class Manager extends EventEmitter { actionResults: runActions, postBehavior: behavior, run: currRun.name, - ruleSummary: isFromCache ? `Check result was found in cache: ${triggeredIndicator(triggered)}` : resultsSummary(currentResults, check.condition) }); } diff --git a/src/Web/Client/index.ts b/src/Web/Client/index.ts index e3a551a..c6f62da 100644 --- a/src/Web/Client/index.ts +++ b/src/Web/Client/index.ts @@ -6,7 +6,7 @@ import cookieParser from 'cookie-parser'; import CacheManagerStore from 'express-session-cache-manager' import passport from 'passport'; import {Strategy as CustomStrategy} from 'passport-custom'; -import {OperatorConfig, BotConnection, LogInfo} from "../../Common/interfaces"; +import {OperatorConfig, BotConnection, LogInfo, CheckSummary} from "../../Common/interfaces"; import { buildCachePrefix, createCacheManager, defaultFormat, filterLogBySubreddit, @@ -14,7 +14,7 @@ import { intersect, isLogLineMinLevel, LogEntry, parseInstanceLogInfoName, parseInstanceLogName, parseRedditEntity, parseSubredditLogName, permissions, - randomId, sleep, triggeredIndicator + randomId, resultsSummary, sleep, triggeredIndicator } from "../../util"; import {Cache} from "cache-manager"; import session, {Session, SessionData} from "express-session"; @@ -954,57 +954,80 @@ const webClient = async (options: OperatorConfig) => { } }).json() as [any]; - return res.render('events', { - data: resp.map((x) => { - const {timestamp, activity: {peek, link}, ruleResults = [], actionResults = [], ...rest} = x; - const time = dayjs(timestamp).local().format('YY-MM-DD HH:mm:ss z'); - const formattedPeek = Autolinker.link(peek, { - email: false, - phone: false, - mention: false, - hashtag: false, - stripPrefix: false, - sanitizeHtml: true, - }); + const actionedEvents = resp.map((x) => { + const {timestamp, activity: {peek, link}, runResults = [], ...rest} = x; + const time = dayjs(timestamp).local().format('YY-MM-DD HH:mm:ss z'); + const formattedPeek = Autolinker.link(peek, { + email: false, + phone: false, + mention: false, + hashtag: false, + stripPrefix: false, + sanitizeHtml: true, + }); + const mergedRunResults = runResults.reduce((acc: any, summ: CheckSummary) => { + let currentRun = acc.curr; + if(currentRun.name !== undefined && summ.run !== currentRun.name) { + currentRun.triggeredVal = currentRun.checkResults.some((x: any) => x.triggered); + currentRun.triggered = triggeredIndicator(currentRun.triggeredVal); + acc.all.push(currentRun); + currentRun = {name: undefined, triggered: false, checkResults: []}; + } + currentRun.name = summ.run; + const {actionResults = [], ruleResults = [], triggered: checkTriggered, ...rest} = summ; const formattedRuleResults = ruleResults.map((y: any) => { const {triggered, result, ...restY} = y; - let t = triggeredIndicator(false); - if(triggered === null) { - t = 'Skipped'; - } else if(triggered === true) { - t = triggeredIndicator(true); - } return { ...restY, - triggered: t, + triggered: triggeredIndicator(triggered, 'Skipped'), result: result || '-' }; }); const formattedActionResults = actionResults.map((y: any) => { - const {run, runReason, success, result, dryRun, ...restA} = y; - let res = ''; - if(!run) { - res = `Not Run - ${runReason === undefined ? '(No Reason)' : runReason}`; - } else { - res = `${triggeredIndicator(success)}${result !== undefined ? ` - ${result}` : ''}`; - } - return { - ...restA, - dryRun: dryRun ? ' (DRYRUN)' : '', - result: res - }; + const {run, runReason, success, result, dryRun, ...restA} = y; + let res = ''; + if(!run) { + res = `Not Run - ${runReason === undefined ? '(No Reason)' : runReason}`; + } else { + res = `${triggeredIndicator(success)}${result !== undefined ? ` - ${result}` : ''}`; + } + return { + ...restA, + dryRun: dryRun ? ' (DRYRUN)' : '', + result: res + }; }); - return { + currentRun.checkResults.push({ ...rest, - timestamp: time, - activity: { - link, - peek: formattedPeek, - }, + triggered: triggeredIndicator(checkTriggered, 'Skipped'), + triggeredVal: checkTriggered, ruleResults: formattedRuleResults, - actionResults: formattedActionResults - } - }), + actionResults: formattedActionResults, + ruleSummary: summ.fromCache ? `Check result was found in cache: ${triggeredIndicator(checkTriggered, 'Skipped')}` : resultsSummary(ruleResults, summ.condition) + }); + acc.curr = currentRun; + return acc; + }, {curr: {name: undefined, triggered: false, checkResults: []}, all: []}); + + // handle last current run + const currentRun = mergedRunResults.curr; + currentRun.triggeredVal = currentRun.checkResults.some((x: any) => x.triggered); + currentRun.triggered = triggeredIndicator(currentRun.triggeredVal); + const rr = mergedRunResults.all.concat(currentRun); + return { + ...rest, + timestamp: time, + activity: { + link, + peek: formattedPeek, + }, + triggered: rr.some((x: any) => x.triggeredVal), + runResults: rr, + } + }); + + return res.render('events', { + data: actionedEvents, title: `${subreddit !== undefined ? `${subreddit} ` : ''}Actioned Events` }); }); diff --git a/src/Web/assets/public/app.css b/src/Web/assets/public/app.css index 1678ba7..0e5cd05 100644 --- a/src/Web/assets/public/app.css +++ b/src/Web/assets/public/app.css @@ -152,3 +152,19 @@ a { #saveTip .tooltip:hover { transition-delay: 1s; } + +.triggeredState.notTriggered { + display: none; +} + +.triggeredState.hide { + display: none; +} + +.triggeredState.notTriggered.show { + display: inherit; +} + +.triggeredStateToggle { + cursor: pointer; +} diff --git a/src/Web/assets/views/events.ejs b/src/Web/assets/views/events.ejs index 4d3b7e7..2c990a7 100644 --- a/src/Web/assets/views/events.ejs +++ b/src/Web/assets/views/events.ejs @@ -16,51 +16,121 @@ -
<%- include('partials/title') %>
+
+ + +
<% if(data.length === 0) { %> No events have been actioned yet! <% } %> - <% data.forEach(function (eRes){ %> -
-
+ <% data.forEach(function (eRes) { %> +
+
- <%- eRes.activity.peek %>(Link) + <%- eRes.activity.peek %>(Link)
<%= eRes.subreddit %> @ <%= eRes.timestamp %>
-
-
Check: <%= eRes.check %><%= eRes.ruleSummary %>
-
- Rules: -
    - <% eRes.ruleResults.forEach(function (ruleResult) { %> -
  • <%= ruleResult.name %> - <%= ruleResult.triggered%> - <%= ruleResult.result %>
  • - <% }) %> -
+
+ <% eRes.runResults.forEach(function (runSum, index) { %> +
+
+ Run: + <%= runSum.triggered %> <%= runSum.name %>
-
Actions -
    - <% eRes.actionResults.forEach(function (aRes) { %> -
  • <%= aRes.name %><%= aRes.dryRun %> - <%= aRes.result %>
  • - <% }) %> -
+
+ <% runSum.checkResults.forEach(function (chkSum, index) { %> +
+
+ Check:<%= chkSum.triggered %><%= chkSum.name %><%= chkSum.ruleSummary %> +
+
+
+ Rules: +
    + <% chkSum.ruleResults.forEach(function (ruleResult) { %> +
  • <%= ruleResult.name %> - <%= ruleResult.triggered%> - <%= ruleResult.result %>
  • + <% }) %> +
+
+ <% if(chkSum.actionResults.length !== 0) { %> +
Actions + +
    + <% chkSum.actionResults.forEach(function (aRes) { %> +
  • <%= aRes.name %><%= aRes.dryRun %> - <%= aRes.result %>
  • + <% }) %> +
+
+ <% } %> +
+ Post <%= chkSum.triggered ? 'Trigger' : 'Fail' %> Behavior <%= chkSum.postBehavior %> +
+
+
+ <% }) %>
-
+ <% }) %> +
+
<% }) %>
<%- include('partials/footer') %>
+