Skip to content

Commit

Permalink
UI - add filter for tables
Browse files Browse the repository at this point in the history
- introduce jest for unit testing
- created new tableFilter web component
- upgraded dependencies
- fixed error in tsconfig
- introduced basic filter to create queries in UI
- dropdown entries now can attach data
- split adding dropdown entries for single and multiple entries
- moved utility methods to utils
- added table filter to connection logs
- added table filter to incoming thing messages
- connection metrix now shows red numbers for failures
- removed unused symbol in Things tab

Signed-off-by: thfries <thomas.fries0@gmail.com>
  • Loading branch information
thfries committed Jan 14, 2024
1 parent c30d459 commit b585836
Show file tree
Hide file tree
Showing 19 changed files with 5,555 additions and 1,585 deletions.
119 changes: 119 additions & 0 deletions ui/__tests__/utils/basicFilter.test.ts
@@ -0,0 +1,119 @@
/*
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/

import { BasicFilters, Term, FilterType } from "../../modules/utils/basicFilters";

describe("create JSONpath", () => {
test("One property equals one keyword", () => {
expect(new BasicFilters()
.addPropEq('quick', 'brown')
.createJsonPath())
.toBe('$[?(@.quick=="brown")]');
});

test("One property equals two keywords", () => {
expect(new BasicFilters()
.addPropEq('quick', 'brown')
.addPropEq('quick', 'fox')
.createJsonPath())
.toBe('$[?(@.quick=="fox"||@.quick=="brown")]');
});

test("Two properties equal a keyword", () => {
expect(new BasicFilters()
.addPropEq('quick', 'brown')
.addPropEq('jumps', 'fox')
.createJsonPath())
.toBe('$[?((@.quick=="brown")&&(@.jumps=="fox"))]');
});

test("One property like one keyword", () => {
expect(new BasicFilters()
.addPropLike('quick', 'brown')
.createJsonPath())
.toBe('$[?(/brown/.test(@.quick))]');
});

test("One property like two keywords", () => {
expect(new BasicFilters()
.addPropLike('quick', 'brown')
.addPropLike('quick', 'fox')
.createJsonPath())
.toBe('$[?(/fox/.test(@.quick)||/brown/.test(@.quick))]');
});

test("Two properties like a keyword", () => {
expect(new BasicFilters()
.addPropLike('quick', 'brown')
.addPropLike('jumps', 'fox')
.createJsonPath())
.toBe('$[?((/brown/.test(@.quick))&&(/fox/.test(@.jumps)))]');
});

test("Full like keyword", () => {
expect(new BasicFilters()
.setAllLike('quick')
.createJsonPath())
.toBe('$[?(/quick/.test(JSON.stringify(@)))]');
});

test("Full like keyword and one property equals", () => {
expect(new BasicFilters()
.setAllLike('fox')
.addPropEq('quick', 'brown')
.createJsonPath())
.toBe('$[?((/fox/.test(JSON.stringify(@)))&&(@.quick=="brown"))]');
});

test("One property equals one property like", () => {
expect(new BasicFilters()
.addPropEq('quick', 'brown')
.addPropLike('jumps', 'fox')
.createJsonPath())
.toBe('$[?((@.quick=="brown")&&(/fox/.test(@.jumps)))]');
});

test("One property equals one property like and full search", () => {
expect(new BasicFilters()
.setAllLike('dog')
.addPropEq('quick', 'brown')
.addPropLike('jumps', 'fox')
.createJsonPath())
.toBe('$[?((/dog/.test(JSON.stringify(@)))&&(@.quick=="brown")&&(/fox/.test(@.jumps)))]');
});

test("One property like one keyword escaped", () => {
expect(new BasicFilters()
.addPropLike('quick', '/features/brown')
.createJsonPath())
.toBe('$[?(/\\/features\\/brown/.test(@.quick))]');
});

});

describe("Term from string", () => {
test("Property equals", () => {
expect(Term.fromString('quick:brown'))
.toEqual(new Term(FilterType.PROP_EQ, 'brown', 'quick'))
});

test("Property like", () => {
expect(Term.fromString('quick~brown'))
.toEqual(new Term(FilterType.PROP_LIKE, 'brown', 'quick'))
});

test("Find in root", () => {
expect(Term.fromString('quick'))
.toEqual(new Term(FilterType.PROP_LIKE, 'quick'))
});
});
22 changes: 22 additions & 0 deletions ui/jest.config.ts
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/

import type {Config} from 'jest';

const config: Config = {
verbose: true,
preset: 'ts-jest',
testEnvironment: 'node',
};

export default config;
3 changes: 2 additions & 1 deletion ui/main.ts
Expand Up @@ -43,6 +43,7 @@ import * as ThingsSSE from './modules/things/thingsSSE.js';
import { WoTDescription } from './modules/things/wotDescription.js';
import * as Utils from './modules/utils.js';
import './modules/utils/crudToolbar.js';
import './modules/utils/tableFilter.js';

let resized = false;
let mainNavbar;
Expand All @@ -68,7 +69,7 @@ document.addEventListener('DOMContentLoaded', async function() {
PoliciesResources.ready();
Connections.ready();
ConnectionsCRUD.ready();
ConnectionsMonitor.ready();
await ConnectionsMonitor.ready();
Operations.ready();
Authorization.ready();
await Environments.ready();
Expand Down
6 changes: 2 additions & 4 deletions ui/modules/connections/connections.html
Expand Up @@ -147,7 +147,7 @@ <h5 data-bs-toggle="collapse" data-bs-target="#tabConnectionHelper">
</div>
</div>
<div class="col-md-4 resizable_flex_column">
<h6>Connection Logs</h6>
<h6>Connection Logs <span class="badge rounded-pill bg-info" id="badgeConnectionLogsCount"></span></h6>
<div class="input-group input-group-sm mb-1">
<button class="btn btn-outline-secondary btn-sm button_round_left" id="buttonEnableConnectionLogs"
data-bs-toggle="tooltip" title="Click to enable connection logs for the selected connection">
Expand All @@ -166,9 +166,7 @@ <h6>Connection Logs</h6>
Refresh
</button>
</div>
<!-- <div class="input-group input-group-sm mb-1">
<input id="inputConnectionLogFilter" type="search" class="form-control form-control-sm" placeholder="Filter logs..."></input>
</div> -->
<table-filter id="tableFilterConnectionLogs"></table-filter>
<div class="table-wrap">
<table class="table table-striped table-hover table-sm">
<thead>
Expand Down
111 changes: 67 additions & 44 deletions ui/modules/connections/connectionsMonitor.ts
Expand Up @@ -10,16 +10,33 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
import {JSONPath} from 'jsonpath-plus';

import * as API from '../api.js';
import * as Utils from '../utils.js';
import * as Connections from './connections.js';
import { TableFilter } from '../utils/tableFilter.js';
import { FilterType, Term } from '../utils/basicFilters.js';
/* eslint-disable prefer-const */
/* eslint-disable max-len */
/* eslint-disable no-invalid-this */
/* eslint-disable require-jsdoc */

let dom = {
type DomElements = {
tbodyConnectionLogs: HTMLTableElement,
tbodyConnectionMetrics: HTMLTableElement,
buttonRetrieveConnectionStatus: HTMLButtonElement,
buttonRetrieveConnectionLogs: HTMLButtonElement,
buttonEnableConnectionLogs: HTMLButtonElement,
buttonResetConnectionLogs: HTMLButtonElement,
buttonRetrieveConnectionMetrics: HTMLButtonElement,
buttonResetConnectionMetrics: HTMLButtonElement,
tableValidationConnections: HTMLInputElement,
tableFilterConnectionLogs: TableFilter,
badgeConnectionLogsCount: HTMLSpanElement,
}

let dom: DomElements = {
tbodyConnectionLogs: null,
tbodyConnectionMetrics: null,
buttonRetrieveConnectionStatus: null,
Expand All @@ -29,17 +46,20 @@ let dom = {
buttonRetrieveConnectionMetrics: null,
buttonResetConnectionMetrics: null,
tableValidationConnections: null,
// inputConnectionLogFilter: null,
tableFilterConnectionLogs: null,
badgeConnectionLogsCount: null,
};

let connectionLogs;
let connectionLogs = [];
let filteredLogs: Array<any>;

let connectionLogDetail;

let connectionStatusDetail;

let selectedConnectionId;

export function ready() {
export async function ready() {
Connections.addChangeListener(onConnectionChange);

Utils.getAllElementsById(dom);
Expand All @@ -56,12 +76,14 @@ export function ready() {
dom.buttonResetConnectionLogs.onclick = onResetConnectionLogsClick;
dom.buttonRetrieveConnectionLogs.onclick = retrieveConnectionLogs;
dom.tbodyConnectionLogs.addEventListener('click', onConnectionLogTableClick);
dom.tableFilterConnectionLogs.addEventListener('filterChange', onConnectionLogFilterChange);
dom.tableFilterConnectionLogs.filterOptions = createFilterOptions();

// Metrics ---------------
dom.buttonRetrieveConnectionMetrics.onclick = retrieveConnectionMetrics;
(document.querySelector('a[data-bs-target="#tabConnectionMetrics"]') as HTMLElement).onclick = retrieveConnectionMetrics;
dom.buttonResetConnectionMetrics.onclick = onResetConnectionMetricsClick;
// dom.inputConnectionLogFilter.onchange = onConnectionLogFilterChange;
dom.tbodyConnectionMetrics.addEventListener('click', onConnectionMetricsTableClick)
}

function onResetConnectionMetricsClick() {
Expand All @@ -70,10 +92,13 @@ function onResetConnectionMetricsClick() {
}

function onConnectionLogTableClick(event) {
connectionLogDetail.setValue(Utils.stringifyPretty(connectionLogs[event.target.parentNode.rowIndex - 1]), -1);
connectionLogDetail.setValue(Utils.stringifyPretty(filteredLogs[event.target.parentNode.rowIndex - 1]), -1);
connectionLogDetail.session.getUndoManager().reset();
}

function onConnectionMetricsTableClick(event) {
}

function onResetConnectionLogsClick() {
Utils.assert(selectedConnectionId, 'Please select a connection', dom.tableValidationConnections);
API.callConnectionsAPI('connectionCommand', retrieveConnectionLogs, selectedConnectionId, null, 'connectivity.commands:resetConnectionLogs');
Expand All @@ -94,7 +119,12 @@ function retrieveConnectionMetrics() {
Object.keys(response.connectionMetrics[direction]).forEach((type) => {
let entry = response.connectionMetrics[direction][type];
Utils.addTableRow(dom.tbodyConnectionMetrics, direction, false, null, type, 'success', entry.success.PT1M, entry.success.PT1H, entry.success.PT24H);
Utils.addTableRow(dom.tbodyConnectionMetrics, direction, false, null, type, 'failure', entry.failure.PT1M, entry.failure.PT1H, entry.failure.PT24H);
let failureRow = Utils.addTableRow(dom.tbodyConnectionMetrics, direction, false, null, type, 'failure', entry.failure.PT1M, entry.failure.PT1H, entry.failure.PT24H);
let numberColumn = failureRow.lastElementChild;
for (let i = 0; i < 3; i++) {
if (numberColumn.innerHTML !== '0') numberColumn.classList.add('text-danger');
numberColumn = numberColumn.previousElementSibling;
}
});
};
});
Expand All @@ -117,21 +147,28 @@ function retrieveConnectionLogs() {
API.callConnectionsAPI('retrieveConnectionLogs', (response) => {
connectionLogs = response.connectionLogs;
adjustEnableButton(response);
fillConnectionLogsTable(response.connectionLogs);
fillConnectionLogsTable();
},
selectedConnectionId);
}

let connectionLogsFilter;

function fillConnectionLogsTable(entries) {
function fillConnectionLogsTable() {
dom.tbodyConnectionLogs.innerHTML = '';
connectionLogDetail.setValue('');

let filter = connectionLogsFilter ? connectionLogsFilter.match : (a => true);
entries.filter(filter).forEach((entry) => {
Utils.addTableRow(dom.tbodyConnectionLogs, Utils.formatDate(entry.timestamp, true), false, null, entry.type, entry.level);
filteredLogs = dom.tableFilterConnectionLogs.filterItems(connectionLogs);

filteredLogs.forEach((entry) => {
Utils.addTableRow(
dom.tbodyConnectionLogs,
Utils.formatDate(entry.timestamp, true), false, null,
entry.type,
entry.level
);
});

Utils.updateCounterBadge(dom.badgeConnectionLogsCount, connectionLogs, filteredLogs);

dom.tbodyConnectionLogs.scrollTop = dom.tbodyConnectionLogs.scrollHeight - dom.tbodyConnectionLogs.clientHeight;
}

Expand All @@ -156,38 +193,24 @@ function onConnectionChange(connection, isNewConnection = true) {
}
}

function JsonFilter() {
let _filters = [];
function onConnectionLogFilterChange(event: Event) {
fillConnectionLogsTable();
}

const match = (object) => {
let result = true;
_filters.forEach((f) => result = result && object[f.key] === f.value);
return result;
};
function createFilterOptions(): [Term?] {
let result: [Term?] = [];

const add = (key, value) => {
_filters.push({key: key, value: value});
};
['consumed', 'mapped', 'dropped', 'enforced', 'acknowledged', 'throttled', 'dispatched', 'filtered', 'published']
.forEach((e) => result.push(new Term(FilterType.PROP_EQ, e, 'type')));
['source', 'target', 'response']
.forEach((e) => result.push(new Term(FilterType.PROP_EQ, e, 'category')));
['success', 'failure']
.forEach((e) => result.push(new Term(FilterType.PROP_EQ, e, 'level')));
['thing']
.forEach((e) => result.push(new Term(FilterType.PROP_EQ, e, 'entityType')));

return {
match,
add,
};
}
// ['correlationId', 'entityId']
// .forEach((value: string) => result.push(value));

const knownFields = ['category', 'type', 'level'];

function onConnectionLogFilterChange(event) {
if (event.target.value && event.target.value !== '') {
connectionLogsFilter = JsonFilter();
event.target.value.split(/(\s+)/).forEach((elem) => {
const keyValue = elem.split(':');
if (keyValue.length === 2 && knownFields.includes(keyValue[0].trim())) {
connectionLogsFilter.add(keyValue[0].trim(), keyValue[1].trim());
}
});
} else {
connectionLogsFilter = null;
}
fillConnectionLogsTable(connectionLogs);
return result;
}
2 changes: 1 addition & 1 deletion ui/modules/things/featureMessages.ts
Expand Up @@ -169,7 +169,7 @@ function clearAllFields() {

function refillTemplates() {
dom.ulMessageTemplates.innerHTML = '';
Utils.addDropDownEntries(dom.ulMessageTemplates, ['Saved message templates'], true);
Utils.addDropDownEntry(dom.ulMessageTemplates, 'Saved message templates', true);
if (theFeatureId && Environments.current().messageTemplates[theFeatureId]) {
Utils.addDropDownEntries(
dom.ulMessageTemplates,
Expand Down
1 change: 1 addition & 0 deletions ui/modules/things/messagesIncoming.html
Expand Up @@ -23,6 +23,7 @@ <h5 data-bs-toggle="collapse" data-bs-target="#collapseMessages">
Reset
</button>
</div>
<table-filter id="tableFilterMessagesIncoming"></table-filter>
<div class="table-wrap">
<table class="table table-striped table-hover table-sm">
<thead>
Expand Down

0 comments on commit b585836

Please sign in to comment.