diff --git a/src/plugins/condition/components/inspector/StylesView.vue b/src/plugins/condition/components/inspector/StylesView.vue
index 932f51e522..ebd56158c7 100644
--- a/src/plugins/condition/components/inspector/StylesView.vue
+++ b/src/plugins/condition/components/inspector/StylesView.vue
@@ -344,6 +344,11 @@ export default {
const layoutItem = selectionItem[0].context.layoutItem;
const isChildItem = selectionItem.length > 1;
+ if (!item && !layoutItem) {
+ // cases where selection is used for table cells
+ return;
+ }
+
if (!isChildItem) {
domainObject = item;
itemStyle = getApplicableStylesForItem(item);
diff --git a/src/plugins/condition/utils/styleUtils.js b/src/plugins/condition/utils/styleUtils.js
index f67649b901..8581cf3f15 100644
--- a/src/plugins/condition/utils/styleUtils.js
+++ b/src/plugins/condition/utils/styleUtils.js
@@ -104,7 +104,7 @@ export function getConsolidatedStyleValues(multipleItemStyles) {
const properties = Object.keys(styleProps);
properties.forEach((property) => {
const values = aggregatedStyleValues[property];
- if (values.length) {
+ if (values && values.length) {
if (values.every(value => value === values[0])) {
styleValues[property] = values[0];
} else {
diff --git a/src/plugins/telemetryTable/TelemetryTable.js b/src/plugins/telemetryTable/TelemetryTable.js
index e1a7a00ed8..f69a1f1995 100644
--- a/src/plugins/telemetryTable/TelemetryTable.js
+++ b/src/plugins/telemetryTable/TelemetryTable.js
@@ -94,6 +94,7 @@ define([
initialize() {
if (this.domainObject.type === 'table') {
this.filterObserver = this.openmct.objects.observe(this.domainObject, 'configuration.filters', this.updateFilters);
+ this.filters = this.domainObject.configuration.filters;
this.loadComposition();
} else {
this.addTelemetryObject(this.domainObject);
@@ -138,7 +139,18 @@ define([
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.boundedRows.clear();
Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
diff --git a/src/plugins/telemetryTable/TelemetryTableViewProvider.js b/src/plugins/telemetryTable/TelemetryTableViewProvider.js
index f313fcda56..92008397a1 100644
--- a/src/plugins/telemetryTable/TelemetryTableViewProvider.js
+++ b/src/plugins/telemetryTable/TelemetryTableViewProvider.js
@@ -100,6 +100,9 @@ define([
destroy: function (element) {
component.$destroy();
component = undefined;
+ },
+ _getTable: function () {
+ return table;
}
};
diff --git a/src/plugins/telemetryTable/collections/FilteredTableRowCollection.js b/src/plugins/telemetryTable/collections/FilteredTableRowCollection.js
index c99c1f4d07..fda46eefd0 100644
--- a/src/plugins/telemetryTable/collections/FilteredTableRowCollection.js
+++ b/src/plugins/telemetryTable/collections/FilteredTableRowCollection.js
@@ -46,6 +46,7 @@ define(
filter = filter.trim().toLowerCase();
let rowsToFilter = this.getRowsToFilter(columnKey, filter);
+
if (filter.length === 0) {
delete this.columnFilters[columnKey];
} else {
@@ -56,6 +57,16 @@ define(
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
*/
@@ -71,6 +82,10 @@ define(
* @private
*/
isSubsetOfCurrentFilter(columnKey, filter) {
+ if (this.columnFilters[columnKey] instanceof RegExp) {
+ return false;
+ }
+
return this.columnFilters[columnKey]
&& filter.startsWith(this.columnFilters[columnKey])
// startsWith check will otherwise fail when filter cleared
@@ -97,7 +112,11 @@ define(
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;
diff --git a/src/plugins/telemetryTable/components/table.vue b/src/plugins/telemetryTable/components/table.vue
index 8bf94c6f55..8f4be187dc 100644
--- a/src/plugins/telemetryTable/components/table.vue
+++ b/src/plugins/telemetryTable/components/table.vue
@@ -188,7 +188,17 @@
class="c-table__search"
@input="filterChanged(key)"
@clear="clearFilter(key)"
- />
+ >
+
+
+
@@ -361,6 +371,7 @@ export default {
paused: false,
markedRows: [],
isShowingMarkedRowsOnly: false,
+ enableRegexSearch: {},
hideHeaders: configuration.hideHeaders,
totalNumberOfRows: 0
};
@@ -618,7 +629,16 @@ export default {
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
},
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();
},
clearFilter(columnKey) {
@@ -956,6 +976,18 @@ export default {
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() {
return {
type: 'telemetry-table',
diff --git a/src/plugins/telemetryTable/pluginSpec.js b/src/plugins/telemetryTable/pluginSpec.js
index 0c2bdf519c..cf56a55cb2 100644
--- a/src/plugins/telemetryTable/pluginSpec.js
+++ b/src/plugins/telemetryTable/pluginSpec.js
@@ -113,6 +113,7 @@ describe("the plugin", () => {
let applicableViews;
let tableViewProvider;
let tableView;
+ let tableInstance;
beforeEach(() => {
testTelemetryObject = {
@@ -179,6 +180,8 @@ describe("the plugin", () => {
tableView = tableViewProvider.view(testTelemetryObject, [testTelemetryObject]);
tableView.show(child, true);
+ tableInstance = tableView._getTable();
+
return telemetryPromise.then(() => Vue.nextTick());
});
@@ -228,5 +231,41 @@ describe("the plugin", () => {
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);
+ });
+ });
+ });
});
});
diff --git a/src/ui/components/search.scss b/src/ui/components/search.scss
index 9062b56861..fe615c6e8a 100644
--- a/src/ui/components/search.scss
+++ b/src/ui/components/search.scss
@@ -1,6 +1,11 @@
+@mixin visibleRegexButton {
+ opacity: 1;
+ padding: 1px 3px;
+ width: 24px;
+}
+
.c-search {
@include wrappedInput();
-
padding-top: 2px;
padding-bottom: 2px;
@@ -9,11 +14,46 @@
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 {
display: none;
+ order: 99;
+ padding: 1px 0;
}
&.is-active {
+ .c-search__use-regex {
+ margin-left: 0;
+ }
+
.c-search__clear-input {
display: block;
}
@@ -21,6 +61,15 @@
input[type='text'],
input[type='search'] {
+ margin-left: $interiorMargin;
+ order: 3;
text-align: left;
}
+
+ &:hover {
+ .c-search__use-regex {
+ @include visibleRegexButton();
+ transition: $transIn;
+ }
+ }
}
diff --git a/src/ui/components/search.vue b/src/ui/components/search.vue
index 7a230ac5f8..46d069bffa 100644
--- a/src/ui/components/search.vue
+++ b/src/ui/components/search.vue
@@ -15,6 +15,7 @@
class="c-search__clear-input icon-x-in-circle"
@click="clearInput"
>
+