Merge branch 'master' into map-annotations-prototype

This commit is contained in:
Scott Bell 2023-06-02 10:35:58 +02:00 committed by GitHub
commit ec90d4d92a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 197 additions and 82 deletions

View File

@ -70,7 +70,7 @@
"vue-eslint-parser": "9.3.0",
"vue-loader": "15.9.8",
"vue-template-compiler": "2.6.14",
"webpack": "5.84.0",
"webpack": "5.85.0",
"webpack-cli": "5.1.1",
"webpack-dev-server": "4.13.3",
"webpack-merge": "5.8.0"

View File

@ -49,10 +49,16 @@ define(['./components/FiltersView.vue', 'vue'], function (FiltersView, Vue) {
});
},
showTab: function (isEditing) {
const hasPersistedFilters = Boolean(domainObject?.configuration?.filters);
const hasGlobalFilters = Boolean(domainObject?.configuration?.globalFilters);
if (isEditing) {
return true;
}
return hasPersistedFilters || hasGlobalFilters;
const metadata = openmct.telemetry.getMetadata(domainObject);
const metadataWithFilters = metadata
? metadata.valueMetadatas.filter((value) => value.filters)
: [];
return metadataWithFilters.length;
},
priority: function () {
return openmct.priority.DEFAULT;

View File

@ -0,0 +1,53 @@
# Server side filtering in Open MCT
## Introduction
In Open MCT, filters can be constructed to filter out telemetry data on the server side. This is useful for reducing the amount of data that needs to be sent to the client. For example, in [Open MCT for MCWS](https://github.com/NASA-AMMOS/openmct-mcws/blob/e8846d325cc3f659d8ad58d1d24efaafbe2b6bb7/src/constants.js#L115), they can be used to filter realtime data from recorded data. In the [Open MCT YAMCS plugin](https://github.com/akhenry/openmct-yamcs/blob/9c4ed03e23848db3215fdb6a988ba34b445a3989/src/providers/events.js#L44), we can use them to filter incoming event data by severity.
## Installing the filter plugin
You'll need to install the filter plugin first. For example:
```js
openmct.install(openmct.plugins.Filters(['telemetry.plot.overlay', 'table']));
```
will install the filters plugin and have it apply to overlay plots and tables. You can see an example of this in the [Open MCT YAMCS plugin](https://github.com/akhenry/openmct-yamcs/blob/9c4ed03e23848db3215fdb6a988ba34b445a3989/example/index.js#L58).
## Defining a filter
To define a filter, you'll need to add a new `filter` property to the domain object's `telemetry` metadata underneath the `values` array. For example, if you have a domain object with a `telemetry` metadata that looks like this:
```js
{
key: 'fruit',
name: 'Types of fruit',
filters: [{
singleSelectionThreshold: true,
comparator: 'equals',
possibleValues: [
{ name: 'Apple', value: 'apple' },
{ name: 'Banana', value: 'banana' },
{ name: 'Orange', value: 'orange' }
]
}]
}
```
This will define a filter that allows an operator to choose one (due to `singleSelectionThreshold` being `true`) of the three possible values. The `comparator` property defines how the filter will be applied to the telemetry data.
Setting `singleSelectionThreshold` to `false` will render the `possibleValues` as a series of checkboxes. Removing the `possibleValues` property will render the filter as a text box, allowing the operator to enter a value to filter on.
Note that how the filter is interpreted is ultimately decided by the individual telemetry providers.
## Implementing a filter in a telemetry provider
Implementing a filter requires two parts:
- First, one needs to add the filter implementation to the [subscribe](https://github.com/nasa/openmct/blob/5df7971438acb9e8b933edda2aed432b1b8bb27d/src/api/telemetry/TelemetryAPI.js#L366) method in your telemetry provider. The filter will be passed to you in the `options` argument. You can either add the filter to your telemetry subscription request, or filter manually as new messages appears. An example of the latter is [shown in the YAMCS plugin for Open MCT](https://github.com/akhenry/openmct-yamcs/blob/9c4ed03e23848db3215fdb6a988ba34b445a3989/src/providers/events.js#L95).
- Second, one needs to add the filter implementation to the [request](https://github.com/nasa/openmct/blob/5df7971438acb9e8b933edda2aed432b1b8bb27d/src/api/telemetry/TelemetryAPI.js#L318) method in your telemetry provider. The filter again will be passed to you in the `options` argument. You can either add the filter to your telemetry request, or filter manually after the request is made. An example of the former is [shown in the YAMCS plugin for Open MCT](https://github.com/akhenry/openmct-yamcs/blob/9c4ed03e23848db3215fdb6a988ba34b445a3989/src/providers/historical-telemetry-provider.js#L171).
## Using filters
If you installed the plugin to have it apply to `table`, create a Telemetry Table in Open MCT and drag your telemetry object that contains the filter to it. Then click "Edit", and notice the "Filter" tab in the inspector. It allows operator to either select a "Global Filter", or a regular filter. The "Global Filter" will apply for all telemetry objects in the table, while the regular filter will only apply to the telemetry object that it is defined on.

View File

@ -37,14 +37,37 @@
:id="`${filter}filterControl`"
class="c-input--flex"
type="text"
:aria-label="label"
:disabled="useGlobal"
:value="persistedValue(filter)"
@change="updateFilterValue($event, filter)"
@change="updateFilterValueFromString($event, filter)"
/>
</template>
<!-- Dropdown, editing -->
<template v-if="filter.possibleValues && filter.singleSelectionThreshold && isEditing">
<select
name="setSelectionThreshold"
:aria-label="label"
:disabled="useGlobal"
@change="updateFilterValueFromDropdown($event, filter.comparator, $event.target.value)"
>
<option key="NONE" value="NONE" selected="isSelected(filter.comparator, option.value)">
None
</option>
<option
v-for="option in filter.possibleValues"
:key="option.label"
:value="option.value"
:selected="isSelected(filter.comparator, option.value)"
>
{{ option.label }}
</option>
</select>
</template>
<!-- Checkbox list, editing -->
<template v-if="filter.possibleValues && isEditing">
<template v-if="filter.possibleValues && isEditing && !filter.singleSelectionThreshold">
<div
v-for="option in filter.possibleValues"
:key="option.value"
@ -54,9 +77,10 @@
:id="`${option.value}filterControl`"
class="c-checkbox-list__input"
type="checkbox"
:aria-label="label"
:disabled="useGlobal"
:checked="isChecked(filter.comparator, option.value)"
@change="updateFilterValue($event, filter.comparator, option.value)"
:checked="isSelected(filter.comparator, option.value)"
@change="updateFilterValueFromCheckbox($event, filter.comparator, option.value)"
/>
<span class="c-checkbox-list__value">
{{ option.label }}
@ -89,6 +113,10 @@ export default {
type: Object,
required: true
},
label: {
type: String,
required: true
},
useGlobal: Boolean,
persistedFilters: {
type: Object,
@ -112,7 +140,7 @@ export default {
toggleIsEditing(isEditing) {
this.isEditing = isEditing;
},
isChecked(comparator, value) {
isSelected(comparator, value) {
if (this.persistedFilters[comparator] && this.persistedFilters[comparator].includes(value)) {
return true;
} else {
@ -122,11 +150,17 @@ export default {
persistedValue(comparator) {
return this.persistedFilters && this.persistedFilters[comparator];
},
updateFilterValue(event, comparator, value) {
if (value !== undefined) {
this.$emit('filterSelected', this.filterField.key, comparator, value, event.target.checked);
updateFilterValueFromString(event, comparator) {
this.$emit('filterTextValueChanged', this.filterField.key, comparator, event.target.value);
},
updateFilterValueFromCheckbox(event, comparator, value) {
this.$emit('filterSelected', this.filterField.key, comparator, value, event.target.checked);
},
updateFilterValueFromDropdown(event, comparator, value) {
if (value === 'NONE') {
this.$emit('clearFilters', this.filterField.key);
} else {
this.$emit('filterTextValueChanged', this.filterField.key, comparator, event.target.value);
this.$emit('filterSingleSelected', this.filterField.key, comparator, value);
}
},
getFilterLabels(filter) {

View File

@ -63,8 +63,11 @@
:filter-field="metadatum"
:use-global="persistedFilters.useGlobal"
:persisted-filters="updatedFilters[metadatum.key]"
@filterSelected="updateFiltersWithSelectedValue"
label="Specific Filter"
@filterSelected="updateMultipleFiltersWithSelectedValue"
@filterTextValueChanged="updateFiltersWithTextValue"
@filterSingleSelected="updateSingleSelection"
@clearFilters="clearFilters"
/>
</ul>
</div>
@ -140,7 +143,7 @@ export default {
toggleExpanded() {
this.expanded = !this.expanded;
},
updateFiltersWithSelectedValue(key, comparator, valueName, value) {
updateMultipleFiltersWithSelectedValue(key, comparator, valueName, value) {
let filterValue = this.updatedFilters[key];
if (filterValue[comparator]) {
@ -159,6 +162,10 @@ export default {
this.$emit('updateFilters', this.keyString, this.updatedFilters);
},
clearFilters(key) {
this.$set(this.updatedFilters, key, {});
this.$emit('updateFilters', this.keyString, this.updatedFilters);
},
updateFiltersWithTextValue(key, comparator, value) {
if (value.trim() === '') {
this.$set(this.updatedFilters, key, {});
@ -168,6 +175,10 @@ export default {
this.$emit('updateFilters', this.keyString, this.updatedFilters);
},
updateSingleSelection(key, comparator, value) {
this.$set(this.updatedFilters[key], comparator, [value]);
this.$emit('updateFilters', this.keyString, this.updatedFilters);
},
useGlobalFilter(checked) {
this.updatedFilters.useGlobal = checked;
this.$emit('updateFilters', this.keyString, this.updatedFilters, checked);

View File

@ -43,8 +43,11 @@
:key="metadatum.key"
:filter-field="metadatum"
:persisted-filters="updatedFilters[metadatum.key]"
label="Global Filter"
@filterSelected="updateFiltersWithSelectedValue"
@filterTextValueChanged="updateFiltersWithTextValue"
@filterSingleSelected="updateSingleSelection"
@clearFilters="clearFilters"
/>
</ul>
</li>
@ -97,6 +100,10 @@ export default {
toggleExpanded() {
this.expanded = !this.expanded;
},
clearFilters(key) {
this.$set(this.updatedFilters, key, {});
this.$emit('persistGlobalFilters', key, this.updatedFilters);
},
updateFiltersWithSelectedValue(key, comparator, valueName, value) {
let filterValue = this.updatedFilters[key];
@ -116,6 +123,10 @@ export default {
this.$emit('persistGlobalFilters', key, this.updatedFilters);
},
updateSingleSelection(key, comparator, value) {
this.$set(this.updatedFilters[key], comparator, [value]);
this.$emit('persistGlobalFilters', key, this.updatedFilters);
},
updateFiltersWithTextValue(key, comparator, value) {
if (value.trim() === '') {
this.$set(this.updatedFilters, key, {});

View File

@ -20,59 +20,58 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'objectUtils',
'./components/table-configuration.vue',
'./TelemetryTableConfiguration',
'vue'
], function (objectUtils, TableConfigurationComponent, TelemetryTableConfiguration, Vue) {
function TableConfigurationViewProvider(openmct) {
return {
key: 'table-configuration',
name: 'Configuration',
canView: function (selection) {
if (selection.length !== 1 || selection[0].length === 0) {
return false;
}
import Vue from 'vue';
import TableConfigurationComponent from './components/table-configuration.vue';
import TelemetryTableConfiguration from './TelemetryTableConfiguration';
let object = selection[0][0].context.item;
return object && object.type === 'table';
},
view: function (selection) {
let component;
let domainObject = selection[0][0].context.item;
let tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
return {
show: function (element) {
component = new Vue({
el: element,
components: {
TableConfiguration: TableConfigurationComponent.default
},
provide: {
openmct,
tableConfiguration
},
template: '<table-configuration></table-configuration>'
});
},
priority: function () {
return 1;
},
destroy: function () {
if (component) {
component.$destroy();
component = undefined;
}
tableConfiguration = undefined;
}
};
export default function TableConfigurationViewProvider(openmct) {
return {
key: 'table-configuration',
name: 'Configuration',
canView: function (selection) {
if (selection.length !== 1 || selection[0].length === 0) {
return false;
}
};
}
return TableConfigurationViewProvider;
});
let object = selection[0][0].context.item;
return object && object.type === 'table';
},
view: function (selection) {
let component;
let tableConfiguration;
const domainObject = selection[0][0].context.item;
return {
show: function (element) {
tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
component = new Vue({
el: element,
components: {
TableConfiguration: TableConfigurationComponent.default
},
provide: {
openmct,
tableConfiguration
},
template: '<table-configuration></table-configuration>'
});
},
showTab: function (isEditing) {
return isEditing;
},
priority: function () {
return 1;
},
destroy: function () {
if (component) {
component.$destroy();
component = undefined;
}
tableConfiguration = undefined;
}
};
}
};
}

View File

@ -84,25 +84,26 @@ export default {
configuration: this.tableConfiguration.getConfiguration()
};
},
mounted() {
async mounted() {
this.unlisteners = [];
this.openmct.editor.on('isEditing', this.toggleEdit);
let compositionCollection = this.openmct.composition.get(this.tableConfiguration.domainObject);
const compositionCollection = this.openmct.composition.get(
this.tableConfiguration.domainObject
);
compositionCollection.load().then((composition) => {
this.addColumnsForAllObjects(composition);
this.updateHeaders(this.tableConfiguration.getAllHeaders());
const composition = await compositionCollection.load();
this.addColumnsForAllObjects(composition);
this.updateHeaders(this.tableConfiguration.getAllHeaders());
compositionCollection.on('add', this.addObject);
this.unlisteners.push(
compositionCollection.off.bind(compositionCollection, 'add', this.addObject)
);
compositionCollection.on('add', this.addObject);
this.unlisteners.push(
compositionCollection.off.bind(compositionCollection, 'add', this.addObject)
);
compositionCollection.on('remove', this.removeObject);
this.unlisteners.push(
compositionCollection.off.bind(compositionCollection, 'remove', this.removeObject)
);
});
compositionCollection.on('remove', this.removeObject);
this.unlisteners.push(
compositionCollection.off.bind(compositionCollection, 'remove', this.removeObject)
);
},
destroyed() {
this.tableConfiguration.destroy();