Regex search tables (#2956)

Support regex searches in table columns

Co-authored-by: charlesh88 <charlesh88@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
This commit is contained in:
Deep Tailor
2021-05-05 17:50:14 -07:00
committed by GitHub
parent 0e80a5b8a0
commit 9920e67c83
9 changed files with 166 additions and 6 deletions

View File

@ -344,6 +344,11 @@ export default {
const layoutItem = selectionItem[0].context.layoutItem; const layoutItem = selectionItem[0].context.layoutItem;
const isChildItem = selectionItem.length > 1; const isChildItem = selectionItem.length > 1;
if (!item && !layoutItem) {
// cases where selection is used for table cells
return;
}
if (!isChildItem) { if (!isChildItem) {
domainObject = item; domainObject = item;
itemStyle = getApplicableStylesForItem(item); itemStyle = getApplicableStylesForItem(item);

View File

@ -104,7 +104,7 @@ export function getConsolidatedStyleValues(multipleItemStyles) {
const properties = Object.keys(styleProps); const properties = Object.keys(styleProps);
properties.forEach((property) => { properties.forEach((property) => {
const values = aggregatedStyleValues[property]; const values = aggregatedStyleValues[property];
if (values.length) { if (values && values.length) {
if (values.every(value => value === values[0])) { if (values.every(value => value === values[0])) {
styleValues[property] = values[0]; styleValues[property] = values[0];
} else { } else {

View File

@ -94,6 +94,7 @@ define([
initialize() { initialize() {
if (this.domainObject.type === 'table') { if (this.domainObject.type === 'table') {
this.filterObserver = this.openmct.objects.observe(this.domainObject, 'configuration.filters', this.updateFilters); this.filterObserver = this.openmct.objects.observe(this.domainObject, 'configuration.filters', this.updateFilters);
this.filters = this.domainObject.configuration.filters;
this.loadComposition(); this.loadComposition();
} else { } else {
this.addTelemetryObject(this.domainObject); this.addTelemetryObject(this.domainObject);
@ -138,7 +139,18 @@ define([
this.emit('object-added', telemetryObject); this.emit('object-added', telemetryObject);
} }
updateFilters() { updateFilters(updatedFilters) {
let deepCopiedFilters = JSON.parse(JSON.stringify(updatedFilters));
if (this.filters && !_.isEqual(this.filters, deepCopiedFilters)) {
this.filters = deepCopiedFilters;
this.clearAndResubscribe();
} else {
this.filters = deepCopiedFilters;
}
}
clearAndResubscribe() {
this.filteredRows.clear(); this.filteredRows.clear();
this.boundedRows.clear(); this.boundedRows.clear();
Object.keys(this.subscriptions).forEach(this.unsubscribe, this); Object.keys(this.subscriptions).forEach(this.unsubscribe, this);

View File

@ -100,6 +100,9 @@ define([
destroy: function (element) { destroy: function (element) {
component.$destroy(); component.$destroy();
component = undefined; component = undefined;
},
_getTable: function () {
return table;
} }
}; };

View File

@ -46,6 +46,7 @@ define(
filter = filter.trim().toLowerCase(); filter = filter.trim().toLowerCase();
let rowsToFilter = this.getRowsToFilter(columnKey, filter); let rowsToFilter = this.getRowsToFilter(columnKey, filter);
if (filter.length === 0) { if (filter.length === 0) {
delete this.columnFilters[columnKey]; delete this.columnFilters[columnKey];
} else { } else {
@ -56,6 +57,16 @@ define(
this.emit('filter'); this.emit('filter');
} }
setColumnRegexFilter(columnKey, filter) {
filter = filter.trim();
let rowsToFilter = this.masterCollection.getRows();
this.columnFilters[columnKey] = new RegExp(filter);
this.rows = rowsToFilter.filter(this.matchesFilters, this);
this.emit('filter');
}
/** /**
* @private * @private
*/ */
@ -71,6 +82,10 @@ define(
* @private * @private
*/ */
isSubsetOfCurrentFilter(columnKey, filter) { isSubsetOfCurrentFilter(columnKey, filter) {
if (this.columnFilters[columnKey] instanceof RegExp) {
return false;
}
return this.columnFilters[columnKey] return this.columnFilters[columnKey]
&& filter.startsWith(this.columnFilters[columnKey]) && filter.startsWith(this.columnFilters[columnKey])
// startsWith check will otherwise fail when filter cleared // startsWith check will otherwise fail when filter cleared
@ -97,7 +112,11 @@ define(
return false; return false;
} }
doesMatchFilters = formattedValue.toLowerCase().indexOf(this.columnFilters[key]) !== -1; if (this.columnFilters[key] instanceof RegExp) {
doesMatchFilters = this.columnFilters[key].test(formattedValue);
} else {
doesMatchFilters = formattedValue.toLowerCase().indexOf(this.columnFilters[key]) !== -1;
}
}); });
return doesMatchFilters; return doesMatchFilters;

View File

@ -188,7 +188,17 @@
class="c-table__search" class="c-table__search"
@input="filterChanged(key)" @input="filterChanged(key)"
@clear="clearFilter(key)" @clear="clearFilter(key)"
/> >
<button
class="c-search__use-regex"
:class="{ 'is-active': enableRegexSearch[key] }"
title="Click to enable regex: enter a string with slashes, like this: /regex_exp/"
@click="toggleRegex(key)"
>
/R/
</button>
</search>
</table-column-header> </table-column-header>
</tr> </tr>
</thead> </thead>
@ -361,6 +371,7 @@ export default {
paused: false, paused: false,
markedRows: [], markedRows: [],
isShowingMarkedRowsOnly: false, isShowingMarkedRowsOnly: false,
enableRegexSearch: {},
hideHeaders: configuration.hideHeaders, hideHeaders: configuration.hideHeaders,
totalNumberOfRows: 0 totalNumberOfRows: 0
}; };
@ -618,7 +629,16 @@ export default {
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft; this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
}, },
filterChanged(columnKey) { filterChanged(columnKey) {
this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]); if (this.enableRegexSearch[columnKey]) {
if (this.isCompleteRegex(this.filters[columnKey])) {
this.table.filteredRows.setColumnRegexFilter(columnKey, this.filters[columnKey].slice(1, -1));
} else {
return;
}
} else {
this.table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
}
this.setHeight(); this.setHeight();
}, },
clearFilter(columnKey) { clearFilter(columnKey) {
@ -956,6 +976,18 @@ export default {
this.$nextTick().then(this.calculateColumnWidths); this.$nextTick().then(this.calculateColumnWidths);
}, },
toggleRegex(key) {
this.$set(this.filters, key, '');
if (this.enableRegexSearch[key] === undefined) {
this.$set(this.enableRegexSearch, key, true);
} else {
this.$set(this.enableRegexSearch, key, !this.enableRegexSearch[key]);
}
},
isCompleteRegex(string) {
return (string.length > 2 && string[0] === '/' && string[string.length - 1] === '/');
},
getViewContext() { getViewContext() {
return { return {
type: 'telemetry-table', type: 'telemetry-table',

View File

@ -113,6 +113,7 @@ describe("the plugin", () => {
let applicableViews; let applicableViews;
let tableViewProvider; let tableViewProvider;
let tableView; let tableView;
let tableInstance;
beforeEach(() => { beforeEach(() => {
testTelemetryObject = { testTelemetryObject = {
@ -179,6 +180,8 @@ describe("the plugin", () => {
tableView = tableViewProvider.view(testTelemetryObject, [testTelemetryObject]); tableView = tableViewProvider.view(testTelemetryObject, [testTelemetryObject]);
tableView.show(child, true); tableView.show(child, true);
tableInstance = tableView._getTable();
return telemetryPromise.then(() => Vue.nextTick()); return telemetryPromise.then(() => Vue.nextTick());
}); });
@ -228,5 +231,41 @@ describe("the plugin", () => {
expect(toColumnText).toEqual(firstColumnText); expect(toColumnText).toEqual(firstColumnText);
}); });
}); });
it("Supports filtering telemetry by regular text search", () => {
tableInstance.filteredRows.setColumnFilter("some-key", "1");
return Vue.nextTick().then(() => {
let filteredRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
expect(filteredRowElements.length).toEqual(1);
tableInstance.filteredRows.setColumnFilter("some-key", "");
return Vue.nextTick().then(() => {
let allRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
expect(allRowElements.length).toEqual(3);
});
});
});
it("Supports filtering using Regex", () => {
tableInstance.filteredRows.setColumnRegexFilter("some-key", "^some-value$");
return Vue.nextTick().then(() => {
let filteredRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
expect(filteredRowElements.length).toEqual(0);
tableInstance.filteredRows.setColumnRegexFilter("some-key", "^some-value");
return Vue.nextTick().then(() => {
let allRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
expect(allRowElements.length).toEqual(3);
});
});
});
}); });
}); });

View File

@ -1,6 +1,11 @@
@mixin visibleRegexButton {
opacity: 1;
padding: 1px 3px;
width: 24px;
}
.c-search { .c-search {
@include wrappedInput(); @include wrappedInput();
padding-top: 2px; padding-top: 2px;
padding-bottom: 2px; padding-bottom: 2px;
@ -9,11 +14,46 @@
content: $glyph-icon-magnify; content: $glyph-icon-magnify;
} }
&__use-regex {
// Button
$c: $colorBodyFg;
background: rgba($c, 0.2);
border: 1px solid rgba($c, 0.3);
color: $c;
border-radius: $controlCr;
font-weight: bold;
letter-spacing: 1px;
font-size: 0.8em;
margin-left: $interiorMarginSm;
min-width: 0;
opacity: 0;
order: 2;
overflow: hidden;
padding: 1px 0;
transform-origin: left;
transition: $transOut;
width: 0;
&.is-active {
$c: $colorBtnActiveBg;
@include visibleRegexButton();
background: rgba($c, 0.3);
border-color: $c;
color: $c;
}
}
&__clear-input { &__clear-input {
display: none; display: none;
order: 99;
padding: 1px 0;
} }
&.is-active { &.is-active {
.c-search__use-regex {
margin-left: 0;
}
.c-search__clear-input { .c-search__clear-input {
display: block; display: block;
} }
@ -21,6 +61,15 @@
input[type='text'], input[type='text'],
input[type='search'] { input[type='search'] {
margin-left: $interiorMargin;
order: 3;
text-align: left; text-align: left;
} }
&:hover {
.c-search__use-regex {
@include visibleRegexButton();
transition: $transIn;
}
}
} }

View File

@ -15,6 +15,7 @@
class="c-search__clear-input icon-x-in-circle" class="c-search__clear-input icon-x-in-circle"
@click="clearInput" @click="clearInput"
></a> ></a>
<slot></slot>
</div> </div>
</template> </template>