mirror of
https://github.com/FoxxMD/context-mod.git
synced 2026-04-19 03:00:07 -04:00
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
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -647,7 +647,7 @@ export class Manager extends EventEmitter {
|
||||
ePeek = peek;
|
||||
this.logger.info(`<EVENT> ${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)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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`
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -16,51 +16,121 @@
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-900 text-white font-sans">
|
||||
<script>localStorage.getItem('ms-dark') === 'no' ? document.body.classList.remove('dark') : document.body.classList.add('dark')</script>
|
||||
<div class="min-w-screen min-h-screen">
|
||||
<%- include('partials/title') %>
|
||||
<div class="container mx-auto">
|
||||
<div class="grid">
|
||||
<div class="pr-3 pt-3" style="text-align: right">
|
||||
<input type="checkbox" id="showAll">
|
||||
<label for="showAll">Show all non-triggered details</label>
|
||||
</div>
|
||||
<div class="px-3 py-6 space-y-3">
|
||||
<% if(data.length === 0) { %>
|
||||
No events have been actioned yet!
|
||||
<% } %>
|
||||
<% data.forEach(function (eRes){ %>
|
||||
<div class="shadow-lg">
|
||||
<div class="space-x-4 px-4 p-2 leading-2 font-semibold bg-gray-700">
|
||||
<% data.forEach(function (eRes) { %>
|
||||
<div class="shadow-lg bg-gray-700 py-1 triggeredStateWrapper">
|
||||
<div class="space-x-4 px-4 p-2 leading-2 font-semibold font-semibold triggeredStateToggle">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<span class="peek"><%- eRes.activity.peek %></span><a target="_blank" href="https://reddit.com<%= eRes.activity.link%>">(Link)</a>
|
||||
<span class="peek"><%- eRes.activity.peek %></span><a class="activityLink" target="_blank" href="https://reddit.com<%= eRes.activity.link%>">(Link)</a>
|
||||
</div>
|
||||
<div class="flex items-center flex-end">
|
||||
<%= eRes.subreddit %> @ <%= eRes.timestamp %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 pl-6 pt-3 space-y-2 bg-gray-500">
|
||||
<div><span class="font-semibold">Check:</span> <%= eRes.check %><span class="px-3">➔</span><%= eRes.ruleSummary %></div>
|
||||
<div>
|
||||
<span class="font-semibold">Rules:</span>
|
||||
<ul class="list-inside list-disc">
|
||||
<% eRes.ruleResults.forEach(function (ruleResult) { %>
|
||||
<li><%= ruleResult.name %> - <%= ruleResult.triggered%> - <%= ruleResult.result %></li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
<div class="border-t-2 border-gray-500 triggeredState <%= eRes.triggered ? 'triggered' : 'notTriggered'%>">
|
||||
<% eRes.runResults.forEach(function (runSum, index) { %>
|
||||
<div class="m-4 p-2 px-4 space-y-2 bg-gray-600 triggeredStateWrapper">
|
||||
<div class="triggeredStateToggle">
|
||||
<span class="font-semibold">Run:</span>
|
||||
<span class="px-1"><%= runSum.triggered %></span> <%= runSum.name %>
|
||||
</div>
|
||||
<div><span class="font-semibold">Actions</span>
|
||||
<ul class="list-inside list-disc">
|
||||
<% eRes.actionResults.forEach(function (aRes) { %>
|
||||
<li><%= aRes.name %><%= aRes.dryRun %> - <%= aRes.result %></li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
<div class="space-y-2 triggeredState <%= runSum.triggeredVal ? 'triggered' : 'notTriggered'%>">
|
||||
<% runSum.checkResults.forEach(function (chkSum, index) { %>
|
||||
<div class="py-3 px-4 space-y-2 bg-gray-500 triggeredStateWrapper <%= chkSum.triggeredVal ? 'border-2 border-gray-100' : ''%>">
|
||||
<div class="triggeredStateToggle">
|
||||
<span class="font-semibold">Check:</span><span class="px-1"><%= chkSum.triggered %></span><%= chkSum.name %><span class="px-3">➔</span><%= chkSum.ruleSummary %>
|
||||
</div>
|
||||
<div class="space-y-2 triggeredState <%= chkSum.triggeredVal ? 'triggered' : 'notTriggered'%>">
|
||||
<div>
|
||||
<span class="font-semibold">Rules:</span>
|
||||
<ul class="list-inside list-disc">
|
||||
<% chkSum.ruleResults.forEach(function (ruleResult) { %>
|
||||
<li><%= ruleResult.name %> - <%= ruleResult.triggered%> - <%= ruleResult.result %></li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
</div>
|
||||
<% if(chkSum.actionResults.length !== 0) { %>
|
||||
<div><span class="font-semibold">Actions</span>
|
||||
|
||||
<ul class="list-inside list-disc">
|
||||
<% chkSum.actionResults.forEach(function (aRes) { %>
|
||||
<li><%= aRes.name %><%= aRes.dryRun %> - <%= aRes.result %></li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
</div>
|
||||
<% } %>
|
||||
<div>
|
||||
<span class="font-semibold">Post <%= chkSum.triggered ? 'Trigger' : 'Fail' %> Behavior <span class="px-1">➔</span> <%= chkSum.postBehavior %></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }) %>
|
||||
</div>
|
||||
</div>
|
||||
<% }) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%- include('partials/footer') %>
|
||||
</div>
|
||||
<script>
|
||||
function toggleShowNonTriggered(val) {
|
||||
document.querySelectorAll('.triggeredState').forEach(el => {
|
||||
el.classList.remove('show','hide');
|
||||
if(val && el.classList.contains('notTriggered')) {
|
||||
el.classList.add('show');
|
||||
}
|
||||
});
|
||||
}
|
||||
document.querySelector('#showAll').addEventListener('change', (e) => {
|
||||
toggleShowNonTriggered(e.target.checked);
|
||||
localStorage.setItem('showNonTriggered', e.target.checked);
|
||||
});
|
||||
|
||||
const initShowNonTiggeredState = localStorage.getItem('showNonTriggered');
|
||||
if(initShowNonTiggeredState === 'true') {
|
||||
document.querySelector('#showAll').checked = true;
|
||||
toggleShowNonTriggered(true);
|
||||
}
|
||||
|
||||
document.querySelectorAll('.triggeredStateToggle').forEach(el => {
|
||||
el.addEventListener('click', (e) => {
|
||||
if(e.target.nodeName.toLowerCase() !== 'a') {
|
||||
const tElm = e.currentTarget.closest('.triggeredStateWrapper').querySelector('.triggeredState');
|
||||
if(tElm !== null) {
|
||||
if(tElm.classList.contains('triggered')) {
|
||||
if(tElm.classList.contains('hide')) {
|
||||
tElm.classList.remove('hide');
|
||||
} else {
|
||||
tElm.classList.add('hide');
|
||||
}
|
||||
} else {
|
||||
if(tElm.classList.contains('show')) {
|
||||
tElm.classList.remove('show');
|
||||
} else {
|
||||
tElm.classList.add('show');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user