Compare commits

...

46 Commits

Author SHA1 Message Date
9601427e9e Fixing bugs with event listeners 2019-03-10 12:58:13 -07:00
acc08247a3 Merge branch 'reorder-api' into filters-inspector-merge 2019-03-10 11:33:45 -07:00
5237630cc5 Remove test spec focus 2019-03-10 11:32:53 -07:00
010a1c2784 Make LAD table editable 2019-03-10 11:31:46 -07:00
e7611e91a9 Re-implemented reordering in Elements 2019-03-10 11:31:38 -07:00
731ad5c4a8 Added 'reorder' function to composition API 2019-03-08 18:37:01 -08:00
a6986d7fda Merge branch 'filters-inspector' into filters-inspector-merge 2019-03-07 13:14:18 -08:00
c3c872537a Conditionally destroy filters listener 2019-03-07 13:14:02 -08:00
5def99dc48 Merge branch 'filters-inspector' into filters-inspector-merge 2019-03-07 13:06:00 -08:00
c4c33f82c6 Tolerate undefined configuration 2019-03-07 13:02:04 -08:00
c9f802229f Merge branch 'filters-inspector' into filters-inspector-merge 2019-03-05 13:08:04 -08:00
3808a29676 Merge branch 'topic-core-refactor' of https://github.com/nasa/openmct into filters-inspector 2019-03-05 12:21:04 -08:00
cd1a98c38f Merge branch 'filters-inspector' of https://github.com/nasa/openmct into filters-inspector 2019-03-05 11:00:49 -08:00
813546f2d8 make filter update pass updated filters to telemetry adapter 2019-03-05 11:00:45 -08:00
29976d42bd Merge branch 'filters-inspector-styling' into filters-inspector 2019-03-05 10:56:52 -08:00
52ce2c547f Significant mods to Filtering
- Styling;
- Added Browse view in Inspector;
- Added .c-checkbox-list class;
- "PLOT SERIES" header changed to "PLOT SERIES OPTIONS" for clarity;
2019-03-05 10:54:51 -08:00
ead131b51c Merge branch 'filters-inspector' into filters-inspector-merge 2019-03-05 08:43:59 -08:00
cf61883ae9 Add filters as 'options' object on subscribe 2019-03-05 08:43:42 -08:00
fd568cac96 Merge branch 'conductor-bug-tcr' into filters-inspector-merge 2019-03-04 17:51:04 -08:00
f8dfc22613 Merged vista fixes into filter branch 2019-03-04 17:03:36 -08:00
35cc2e7329 make requested changes 2019-03-04 11:51:17 -08:00
bd79f097ca working filters on overlay plots 2019-03-01 16:03:27 -08:00
c2b5eaeaae Renamed InspectorView.vue to InspectorViews.vue 2019-03-01 14:25:42 -08:00
3230da0654 Support multiple inspector views 2019-03-01 14:25:42 -08:00
948450eba8 Added telemetry filters to the API 2019-03-01 14:25:42 -08:00
78dde3ce6b VISTA compatibility issues (#2291)
* Build config changes necessary to work with VISTA
* Fixes to TelemetryTableRow to address bug in VISTA
* Fixed sass-fast-loader version to avoid https://github.com/yibn2008/fast-sass-loader/issues/47
* Reverted default theme
2019-03-01 14:25:42 -08:00
b379534cf7 Added test specs for telemetry API reordering change 2019-03-01 14:25:39 -08:00
d7a4247ac5 fix errors when old domainObject does not have configuration property 2019-03-01 13:18:04 -08:00
323d992255 Fixed metadata sorting. Iteratees that return arrays are treated as object paths. 2019-03-01 11:11:56 -08:00
39c56f12cd add filter support to Telemetry Tables 2019-02-28 15:20:07 -08:00
72ebadbafb Do not try to convert undefined to a string 2019-02-28 14:48:04 -08:00
b2b481dcf4 Added filter code to tables 2019-02-28 14:00:47 -08:00
2dcef27a06 update filterValue to filterField for clarity 2019-02-28 13:30:24 -08:00
60c53b87de remove object from persisted filter when object is removed from composition 2019-02-28 13:05:47 -08:00
6293b32574 enable filter inspector view for overlay and stacked plots 2019-02-28 11:57:02 -08:00
8a26ac809a add prop validations for FitlerValue and FilterObject 2019-02-27 13:31:23 -08:00
4635b8f840 working persisted filters from inspector 2019-02-27 12:18:34 -08:00
afe653af49 fix typo 2019-02-26 15:28:17 -08:00
d891319585 working persist checkbox selections 2019-02-26 15:18:34 -08:00
fab641f9be third times the charm 2019-02-25 20:16:37 -08:00
098d114a17 abstract for better readability 2019-02-23 22:44:20 -08:00
51078efe9e first cut of filter inspector plugin 2019-02-23 18:24:20 -08:00
b0b160d71e Renamed InspectorView.vue to InspectorViews.vue 2019-02-22 13:28:09 -08:00
9c4553dd90 Support multiple inspector views 2019-02-22 12:51:38 -08:00
86e6885fb9 Added telemetry filters to the API 2019-02-22 12:50:10 -08:00
c1daac1696 Error in conductor validation 2019-02-15 14:47:11 -08:00
32 changed files with 984 additions and 112 deletions

View File

@ -33,12 +33,20 @@ define([
formatString: '%0.2f',
hints: {
range: 1
}
},
filters: [
{
comparator: 'equals',
possibleValues: [1,2,3,4]
}
]
},
{
key: "cos",
name: "Cosine",
formatString: '%0.2f',
filters: ['equals'],
hints: {
range: 2
}

View File

@ -81,6 +81,7 @@
openmct.install(openmct.plugins.Tabs());
openmct.install(openmct.plugins.FlexibleLayout());
openmct.install(openmct.plugins.LADTable());
openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.stacked', 'telemetry.plot.overlay']));
openmct.start();
</script>
</html>

View File

@ -21,7 +21,11 @@ define([
topicService.and.returnValue(mutationTopic);
publicAPI = {};
publicAPI.objects = jasmine.createSpyObj('ObjectAPI', [
'get'
'get',
'mutate'
]);
publicAPI.objects.eventEmitter = jasmine.createSpyObj('eventemitter', [
'on'
]);
publicAPI.objects.get.and.callFake(function (identifier) {
return Promise.resolve({identifier: identifier});
@ -52,6 +56,14 @@ define([
{
namespace: 'test',
key: 'a'
},
{
namespace: 'test',
key: 'b'
},
{
namespace: 'test',
key: 'c'
}
]
};
@ -68,12 +80,39 @@ define([
composition.on('add', listener);
return composition.load().then(function () {
expect(listener.calls.count()).toBe(1);
expect(listener.calls.count()).toBe(3);
expect(listener).toHaveBeenCalledWith({
identifier: {namespace: 'test', key: 'a'}
});
});
});
describe('supports reordering of composition', function () {
var listener;
beforeEach(function () {
listener = jasmine.createSpy('reorderListener');
composition.on('reorder', listener);
return composition.load();
});
it('', function () {
composition.reorder(1, 0);
let newComposition =
publicAPI.objects.mutate.calls.mostRecent().args[2];
expect(listener).toHaveBeenCalledWith(1, 0);
expect(newComposition[0].key).toEqual('b');
expect(newComposition[1].key).toEqual('a');
});
it('', function () {
composition.reorder(0, 2);
let newComposition =
publicAPI.objects.mutate.calls.mostRecent().args[2];
expect(listener).toHaveBeenCalledWith(0, 2);
expect(newComposition[0].key).toEqual('c');
expect(newComposition[2].key).toEqual('a');
})
});
// TODO: Implement add/removal in new default provider.
xit('synchronizes changes between instances', function () {

View File

@ -56,7 +56,8 @@ define([
this.listeners = {
add: [],
remove: [],
load: []
load: [],
reorder: []
};
this.onProviderAdd = this.onProviderAdd.bind(this);
this.onProviderRemove = this.onProviderRemove.bind(this);
@ -91,6 +92,13 @@ define([
this.onProviderRemove,
this
);
} if (event === 'reorder') {
this.provider.on(
this.domainObject,
'reorder',
this.onProviderReorder,
this
)
}
}
@ -141,6 +149,13 @@ define([
this.onProviderRemove,
this
);
} else if (event === 'reorder') {
this.provider.off(
this.domainObject,
'reorder',
this.onProviderReorder,
this
);
}
}
}
@ -209,6 +224,33 @@ define([
}
};
/**
* Reorder the domain objects in this composition.
*
* A call to [load]{@link module:openmct.CompositionCollection#load}
* must have resolved before using this method.
*
* @param {number} oldIndex
* @param {number} newIndex
* @memberof module:openmct.CompositionCollection#
* @name remove
*/
CompositionCollection.prototype.reorder = function (oldIndex, newIndex, skipMutate) {
if (!skipMutate) {
this.provider.reorder(this.domainObject, oldIndex, newIndex);
} else {
this.emit('reorder', oldIndex, newIndex);
}
};
/**
* Handle reorder from provider.
* @private
*/
CompositionCollection.prototype.onProviderReorder = function (oldIndex, newIndex) {
this.reorder(oldIndex, newIndex, true);
};
/**
* Handle adds from provider.
* @private
@ -232,12 +274,12 @@ define([
* Emit events.
* @private
*/
CompositionCollection.prototype.emit = function (event, payload) {
CompositionCollection.prototype.emit = function (event, ...payload) {
this.listeners[event].forEach(function (l) {
if (l.context) {
l.callback.call(l.context, payload);
l.callback.apply(l.context, payload);
} else {
l.callback(payload);
l.callback(...payload);
}
});
};

View File

@ -126,6 +126,7 @@ define([
objectListeners = this.listeningTo[keyString] = {
add: [],
remove: [],
reorder: [],
composition: [].slice.apply(domainObject.composition)
};
}
@ -160,7 +161,7 @@ define([
});
objectListeners[event].splice(index, 1);
if (!objectListeners.add.length && !objectListeners.remove.length) {
if (!objectListeners.add.length && !objectListeners.remove.length && !objectListeners.reorder.length) {
delete this.listeningTo[keyString];
}
};
@ -199,6 +200,30 @@ define([
// TODO: this needs to be synchronized via mutation
};
DefaultCompositionProvider.prototype.reorder = function (domainObject, oldIndex, newIndex) {
let newComposition = domainObject.composition.slice();
newComposition[newIndex] = domainObject.composition[oldIndex];
newComposition[oldIndex] = domainObject.composition[newIndex];
this.publicAPI.objects.mutate(domainObject, 'composition', newComposition);
let id = objectUtils.makeKeyString(domainObject.identifier);
var listeners = this.listeningTo[id];
if (!listeners) {
return;
}
listeners.reorder.forEach(notify);
function notify(listener) {
if (listener.context) {
listener.callback.call(listener.context, oldIndex, newIndex);
} else {
listener.callback(oldIndex, newIndex);
}
}
};
/**
* Listens on general mutation topic, using injector to fetch to avoid
* circular dependencies.

View File

@ -297,7 +297,7 @@ define([
* @returns {Function} a function which may be called to terminate
* the subscription
*/
TelemetryAPI.prototype.subscribe = function (domainObject, callback) {
TelemetryAPI.prototype.subscribe = function (domainObject, callback, options) {
var provider = this.findSubscriptionProvider(domainObject);
if (!this.subscribeCache) {
@ -316,7 +316,7 @@ define([
subscriber.callbacks.forEach(function (cb) {
cb(value);
});
});
}, options);
} else {
subscriber.unsubscribe = function () {};
}

View File

@ -28,14 +28,22 @@ define([
describe('Telemetry API', function () {
var openmct;
var telemetryAPI;
var mockTypeService;
beforeEach(function () {
openmct = {
time: jasmine.createSpyObj('timeAPI', [
'timeSystem',
'bounds'
]),
$injector: jasmine.createSpyObj('injector', [
'get'
])
};
mockTypeService = jasmine.createSpyObj('typeService', [
'getType'
]);
openmct.$injector.get.and.returnValue(mockTypeService);
openmct.time.timeSystem.and.returnValue({key: 'system'});
openmct.time.bounds.and.returnValue({start: 0, end: 1});
telemetryAPI = new TelemetryAPI(openmct);
@ -296,5 +304,233 @@ define([
);
});
});
describe('metadata', function () {
let mockMetadata = {};
let mockObjectType = {
typeDef: {}
};
beforeEach(function () {
telemetryAPI.addProvider({
key: 'mockMetadataProvider',
supportsMetadata() {
return true;
},
getMetadata() {
return mockMetadata;
}
});
mockTypeService.getType.and.returnValue(mockObjectType);
})
it('respects explicit priority', function () {
mockMetadata.values = [
{
key: "name",
name: "Name",
hints: {
priority: 2
}
},
{
key: "timestamp",
name: "Timestamp",
hints: {
priority: 1
}
},
{
key: "sin",
name: "Sine",
hints: {
priority: 4
}
},
{
key: "cos",
name: "Cosine",
hints: {
priority: 3
}
}
];
let metadata = telemetryAPI.getMetadata({});
let values = metadata.values();
values.forEach((value, index) => {
expect(value.hints.priority).toBe(index + 1);
});
});
it('if no explicit priority, defaults to order defined', function () {
mockMetadata.values = [
{
key: "name",
name: "Name"
},
{
key: "timestamp",
name: "Timestamp"
},
{
key: "sin",
name: "Sine"
},
{
key: "cos",
name: "Cosine"
}
];
let metadata = telemetryAPI.getMetadata({});
let values = metadata.values();
values.forEach((value, index) => {
expect(value.key).toBe(mockMetadata.values[index].key);
});
});
it('respects domain priority', function () {
mockMetadata.values = [
{
key: "name",
name: "Name"
},
{
key: "timestamp-utc",
name: "Timestamp UTC",
hints: {
domain: 2
}
},
{
key: "timestamp-local",
name: "Timestamp Local",
hints: {
domain: 1
}
},
{
key: "sin",
name: "Sine",
hints: {
range: 2
}
},
{
key: "cos",
name: "Cosine",
hints: {
range: 1
}
}
];
let metadata = telemetryAPI.getMetadata({});
let values = metadata.valuesForHints(['domain']);
expect(values[0].key).toBe('timestamp-local');
expect(values[1].key).toBe('timestamp-utc');
});
it('respects range priority', function () {
mockMetadata.values = [
{
key: "name",
name: "Name"
},
{
key: "timestamp-utc",
name: "Timestamp UTC",
hints: {
domain: 2
}
},
{
key: "timestamp-local",
name: "Timestamp Local",
hints: {
domain: 1
}
},
{
key: "sin",
name: "Sine",
hints: {
range: 2
}
},
{
key: "cos",
name: "Cosine",
hints: {
range: 1
}
}
];
let metadata = telemetryAPI.getMetadata({});
let values = metadata.valuesForHints(['range']);
expect(values[0].key).toBe('cos');
expect(values[1].key).toBe('sin');
});
it('respects priority and domain ordering', function () {
mockMetadata.values = [
{
key: "id",
name: "ID",
hints: {
priority: 2
}
},
{
key: "name",
name: "Name",
hints: {
priority: 1
}
},
{
key: "timestamp-utc",
name: "Timestamp UTC",
hints: {
domain: 2,
priority: 1
}
},
{
key: "timestamp-local",
name: "Timestamp Local",
hints: {
domain: 1,
priority: 2
}
},
{
key: "timestamp-pst",
name: "Timestamp PST",
hints: {
domain: 3,
priority: 2
}
},
{
key: "sin",
name: "Sine"
},
{
key: "cos",
name: "Cosine"
}
];
let metadata = telemetryAPI.getMetadata({});
let values = metadata.valuesForHints(['priority', 'domain']);
[
'timestamp-utc',
'timestamp-local',
'timestamp-pst'
].forEach((key, index) => {
expect(values[index].key).toBe(key);
});
});
})
});
});

View File

@ -116,14 +116,18 @@ define([
return hints.every(hasHint, metadata);
}
var matchingMetadata = this.valueMetadatas.filter(hasHints);
var sortedMetadata = _.sortBy(matchingMetadata, function (metadata) {
return hints.map(function (hint) {
let iteratees = hints.map(hint => {
return (metadata) => {
return metadata.hints[hint];
});
}
});
return sortedMetadata;
return _.sortByAll(matchingMetadata, ...iteratees);
};
TelemetryMetadataManager.prototype.getFilterableValues = function () {
return this.valueMetadatas.filter(metadatum => metadatum.filters && metadatum.filters.length > 0);
}
TelemetryMetadataManager.prototype.getDefaultDisplayValue = function () {
let valueMetadata = this.valuesForHints(['range'])[0];

View File

@ -35,6 +35,9 @@ define([
canView: function (domainObject) {
return domainObject.type === 'LadTable';
},
canEdit: function (domainObject) {
return domainObject.type === 'LadTable';
},
view: function (domainObject) {
let component;

View File

@ -0,0 +1,113 @@
<template>
<div class="u-contents c-filter-settings">
<li class="grid-row c-filter-settings__setting"
v-for="(filter, index) in filterField.filters"
:key="index">
<div class="grid-cell label">
{{ filterField.name }} =
</div>
<div class="grid-cell value">
<!-- EDITING -->
<!-- String input, editing -->
<template v-if="!filter.possibleValues && isEditing">
<input class="c-input--flex"
type="text"
:id="`${filter}filterControl`"
placeholder="Enter Value"
:value="persistedValue(filter)"
@blur="updateFilterValue($event, filter)">
</template>
<!-- Checkbox list, editing -->
<template v-if="filter.possibleValues && isEditing">
<div class="c-checkbox-list__row"
v-for="value in filter.possibleValues"
:key="value">
<input class="c-checkbox-list__input"
type="checkbox"
:id="`${value}filterControl`"
@change="onUserSelect($event, filter.comparator, value)"
:checked="isChecked(filter.comparator, value)">
<span class="c-checkbox-list__value">
{{ value }}
</span>
</div>
</template>
<!-- BROWSING -->
<!-- String input, NOT editing -->
<template v-if="!filter.possibleValues && !isEditing">
{{ persistedValue(filter) }}
</template>
<!-- Checkbox list, NOT editing -->
<template v-if="filter.possibleValues && !isEditing">
<span>{{persistedFilters[filter.comparator].join(', ')}}</span>
</template>
</div>
</li>
</div>
</template>
<style lang="scss">
@import "~styles/sass-base";
.c-filter-settings {
&__setting {
.grid-cell.label {
white-space: nowrap;
}
}
}
</style>
<script>
export default {
inject: [
'openmct'
],
props: {
filterField: Object,
persistedFilters: {
type: Object,
default: () => {
return {}
}
}
},
data() {
return {
expanded: false,
isEditing: this.openmct.editor.isEditing()
}
},
methods: {
toggleIsEditing(isEditing) {
this.isEditing = isEditing;
},
onUserSelect(event, comparator, value){
this.$emit('onUserSelect', this.filterField.key, comparator, value, event.target.checked);
},
isChecked(comparator, value) {
if (this.persistedFilters[comparator] && this.persistedFilters[comparator].includes(value)) {
return true;
} else {
return false;
}
},
persistedValue(comparator) {
return this.persistedFilters && this.persistedFilters[comparator];
},
updateFilterValue(event, comparator) {
this.$emit('onTextEnter', this.filterField.key, comparator, event.target.value);
}
},
mounted() {
this.openmct.editor.on('isEditing', this.toggleIsEditing);
},
beforeDestroy() {
this.openmct.editor.off('isEditing', this.toggleIsEditing);
}
}
</script>

View File

@ -0,0 +1,93 @@
<template>
<li>
<div class="c-tree__item menus-to-left"
@click="toggleExpanded">
<span class="c-disclosure-triangle is-enabled flex-elem"
:class="{'c-disclosure-triangle--expanded': expanded}"></span>
<div class="c-tree__item__label">
<div class="t-object-label l-flex-row flex-elem grows">
<div class="t-item-icon flex-elem"
:class="objectCssClass">
</div>
<div class="t-title-label flex-elem grows">{{ filterObject.name }}</div>
</div>
</div>
</div>
<ul class="grid-properties" v-if="expanded">
<filter-field
v-for="field in filterObject.valuesWithFilters"
:key="field.key"
:filterField="field"
:persistedFilters="persistedFilters[field.key]"
@onUserSelect="collectUserSelects"
@onTextEnter="updateTextFilter">
</filter-field>
</ul>
</li>
</template>
<style lang="scss">
</style>
<script>
import FilterField from './FilterField.vue';
export default {
inject: ['openmct'],
components: {
FilterField
},
props: {
filterObject: Object,
persistedFilters: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
expanded: false,
objectCssClass: undefined,
updatedFilters: this.persistedFilters
}
},
methods: {
toggleExpanded() {
this.expanded = !this.expanded;
},
collectUserSelects(key, comparator, valueName, value) {
let filterValue = this.updatedFilters[key];
if (filterValue && filterValue[comparator]) {
if (value === false) {
filterValue[comparator] = filterValue[comparator].filter(v => v !== valueName);
} else {
filterValue[comparator].push(valueName);
}
} else {
if (!this.updatedFilters[key]) {
this.updatedFilters[key] = {};
}
this.updatedFilters[key][comparator] = [value ? valueName : undefined];
}
this.$emit('updateFilters', this.keyString, this.updatedFilters);
},
updateTextFilter(key, comparator, value) {
if (!this.updatedFilters[key]) {
this.updatedFilters[key] = {};
}
this.updatedFilters[key][comparator] = value;
this.$emit('updateFilters', this.keyString, this.updatedFilters);
}
},
mounted() {
let type = this.openmct.types.get(this.filterObject.domainObject.type) || {};
this.keyString = this.openmct.objects.makeKeyString(this.filterObject.domainObject.identifier);
this.objectCssClass = type.definition.cssClass;
}
}
</script>

View File

@ -0,0 +1,85 @@
<template>
<ul class="tree c-tree c-properties__section" v-if="Object.keys(children).length">
<h2 class="c-properties__header">Filters</h2>
<filter-object
v-for="(child, key) in children"
:key="key"
:filterObject="child"
:persistedFilters="persistedFilters[key]"
@updateFilters="persistFilters">
</filter-object>
</ul>
</template>
<style lang="scss">
</style>
<script>
import FilterObject from './FilterObject.vue';
export default {
components: {
FilterObject
},
inject: [
'openmct',
'providedObject'
],
data() {
let persistedFilters = {};
if (this.providedObject.configuration && this.providedObject.configuration.filters) {
persistedFilters = this.providedObject.configuration.filters;
}
return {
persistedFilters,
children: {}
}
},
methods: {
addChildren(child) {
let keyString = this.openmct.objects.makeKeyString(child.identifier),
metadata = this.openmct.telemetry.getMetadata(child),
valuesWithFilters = metadata.valueMetadatas.filter((value) => value.filters),
childObject = {
name: child.name,
domainObject: child,
valuesWithFilters
};
if (childObject.valuesWithFilters.length) {
this.$set(this.children, keyString, childObject);
} else {
return;
}
},
removeChildren(identifier) {
let keyString = this.openmct.objects.makeKeyString(identifier);
this.$delete(this.children, keyString);
this.persistFilters(keyString);
},
persistFilters(keyString, userSelects) {
this.persistedFilters[keyString] = userSelects;
this.openmct.objects.mutate(this.providedObject, 'configuration.filters', this.persistedFilters);
},
updatePersistedFilters(filters) {
this.persistedFilters = filters;
}
},
mounted(){
this.composition = this.openmct.composition.get(this.providedObject);
this.composition.on('add', this.addChildren);
this.composition.on('remove', this.removeChildren);
this.composition.load();
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
},
beforeDestroy() {
this.composition.off('add', this.addChildren);
this.composition.off('remove', this.removeChildren);
this.unobserve();
}
}
</script>

View File

@ -0,0 +1,73 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./components/FiltersView.vue',
'vue'
], function (
FiltersView,
Vue
) {
function FiltersInspectorViewProvider(openmct, supportedObjectTypesArray) {
return {
key: 'filters-inspector',
name: 'Filters Inspector View',
canView: function (selection) {
if (selection.length === 0) {
return false;
}
let object = selection[0].context.item;
return object && supportedObjectTypesArray.some(type => object.type === type);
},
view: function (selection) {
let component;
let providedObject = selection[0].context.item;
return {
show: function (element) {
component = new Vue({
provide: {
openmct,
providedObject
},
components: {
FiltersView: FiltersView.default
},
template: '<filters-view></filters-view>',
el: element
});
},
destroy: function () {
component.$destroy();
component = undefined;
}
}
},
priority: function () {
return 1;
}
}
}
return FiltersInspectorViewProvider;
});

View File

@ -0,0 +1,33 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./filtersInspectorViewProvider'
], function (
FiltersInspectorViewProvider
) {
return function plugin(supportedObjectTypesArray) {
return function install(openmct) {
openmct.inspectorViews.addProvider(new FiltersInspectorViewProvider(openmct, supportedObjectTypesArray));
};
};
});

View File

@ -250,7 +250,8 @@ define([
{"has": "telemetry"}
],
"model": {
"composition": []
"composition": [],
"configuration": {}
},
"properties": [],
"priority": 890

View File

@ -21,7 +21,7 @@
-->
<div ng-controller="PlotOptionsController">
<ul class="tree c-tree">
<h2 title="Plot series display properties in this object">Plot Series</h2>
<h2 title="Plot series display properties in this object">Plot Series Options</h2>
<li ng-repeat="series in config.series.models">
<div class="c-tree__item menus-to-left">
<span class='c-disclosure-triangle is-enabled flex-elem'

View File

@ -21,7 +21,7 @@
-->
<div ng-controller="PlotOptionsController">
<ul class="tree c-tree">
<h2 title="Display properties for this object">Plot Series</h2>
<h2 title="Display properties for this object">Plot Series Options</h2>
<li ng-repeat="series in plotSeries"
ng-controller="PlotSeriesFormController"
form-model="series">

View File

@ -101,6 +101,19 @@ define([
seriesConfig.identifier.namespace === identifier.namespace;
})[0];
},
/**
* Retrieve the persisted filters for a given identifier.
*/
getPersistedFilters: function (identifier) {
var domainObject = this.get('domainObject'),
keystring = this.openmct.objects.makeKeyString(identifier);
if (!domainObject.configuration || !domainObject.configuration.filters) {
return;
}
return domainObject.configuration.filters[keystring];
},
/**
* Update the domain object with the given value.
*/

View File

@ -84,6 +84,7 @@ define([
this.listenTo(this, 'change:xKey', this.onXKeyChange, this);
this.listenTo(this, 'change:yKey', this.onYKeyChange, this);
this.persistedConfig = options.persistedConfig;
this.filters = options.filters;
Model.apply(this, arguments);
this.onXKeyChange(this.get('xKey'));
@ -139,13 +140,16 @@ define([
* @returns {Promise}
*/
fetch: function (options) {
options = _.extend({}, {size: 1000, strategy: 'minmax'}, options || {});
options = _.extend({}, {size: 1000, strategy: 'minmax', filters: this.filters}, options || {});
if (!this.unsubscribe) {
this.unsubscribe = this.openmct
.telemetry
.subscribe(
this.domainObject,
this.add.bind(this)
this.add.bind(this),
{
filters: this.filters
}
);
}
@ -360,6 +364,19 @@ define([
}
}
},
/**
* Updates filters, clears the plot series, unsubscribes and resubscribes
* @public
*/
updateFiltersAndRefresh: function (updatedFilters) {
this.filters = updatedFilters;
this.reset();
if (this.unsubscribe) {
this.unsubscribe();
delete this.unsubscribe;
}
this.fetch();
}
});

View File

@ -66,6 +66,7 @@ define([
},
addTelemetryObject: function (domainObject, index) {
var seriesConfig = this.plot.getPersistedSeriesConfig(domainObject.identifier);
var filters = this.plot.getPersistedFilters(domainObject.identifier);
var plotObject = this.plot.get('domainObject');
if (!seriesConfig) {
@ -92,7 +93,8 @@ define([
collection: this,
openmct: this.openmct,
persistedConfig: this.plot
.getPersistedSeriesConfig(domainObject.identifier)
.getPersistedSeriesConfig(domainObject.identifier),
filters: filters
}));
},
removeTelemetryObject: function (identifier) {

View File

@ -71,6 +71,14 @@ define([
this.config.series.forEach(this.addSeries, this);
this.followTimeConductor();
this.newStyleDomainObject = $scope.domainObject.useCapability('adapter');
this.filterObserver = this.openmct.objects.observe(
this.newStyleDomainObject,
'configuration.filters',
this.updateFiltersAndResubscribe.bind(this)
);
}
eventHelpers.extend(PlotController.prototype);
@ -154,6 +162,9 @@ define([
clearInterval(this.checkForSize);
delete this.checkForSize;
}
if (this.filterObserver) {
this.filterObserver();
}
};
PlotController.prototype.loadMoreData = function (range, purge) {
@ -244,6 +255,12 @@ define([
xRange.max === xDisplayRange.max);
};
PlotController.prototype.updateFiltersAndResubscribe = function (updatedFilters) {
this.config.series.forEach(function (series) {
series.updateFiltersAndRefresh(updatedFilters[series.keyString]);
})
}
/**
* Export view as JPG.
*/

View File

@ -39,7 +39,8 @@ define([
'./folderView/plugin',
'./flexibleLayout/plugin',
'./tabs/plugin',
'./LADTable/plugin'
'./LADTable/plugin',
'./filters/plugin'
], function (
_,
UTCTimeSystem,
@ -59,7 +60,8 @@ define([
FolderView,
FlexibleLayout,
Tabs,
LADTable
LADTable,
Filters
) {
var bundleMap = {
LocalStorage: 'platform/persistence/local',
@ -174,6 +176,7 @@ define([
plugins.Tabs = Tabs;
plugins.FlexibleLayout = FlexibleLayout;
plugins.LADTable = LADTable;
plugins.Filters = Filters;
return plugins;
});

View File

@ -46,7 +46,7 @@ define([
view: function (selection) {
let component;
let domainObject = selection[0].context.item;
const tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
let tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
return {
show: function (element) {
component = new Vue({
@ -64,6 +64,7 @@ define([
destroy: function () {
component.$destroy();
component = undefined;
tableConfiguration = undefined;
}
}
},

View File

@ -53,6 +53,10 @@ define([
this.isTelemetryObject = this.isTelemetryObject.bind(this);
this.refreshData = this.refreshData.bind(this);
this.requestDataFor = this.requestDataFor.bind(this);
this.updateFilters = this.updateFilters.bind(this);
this.buildOptionsFromConfiguration = this.buildOptionsFromConfiguration.bind(this);
this.filterObserver = undefined;
this.createTableRowCollections();
openmct.time.on('bounds', this.refreshData);
@ -60,6 +64,7 @@ define([
initialize() {
if (this.domainObject.type === 'table') {
this.filterObserver = this.openmct.objects.observe(this.domainObject, 'configuration.filters', this.updateFilters);
this.loadComposition();
} else {
this.addTelemetryObject(this.domainObject);
@ -81,6 +86,7 @@ define([
this.tableComposition = this.openmct.composition.get(this.domainObject);
if (this.tableComposition !== undefined) {
this.tableComposition.load().then((composition) => {
composition = composition.filter(this.isTelemetryObject);
this.configuration.addColumnsForAllObjects(composition);
@ -101,6 +107,15 @@ define([
this.emit('object-added', telemetryObject);
}
updateFilters() {
this.filteredRows.clear();
this.boundedRows.clear();
Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
this.telemetryObjects.forEach(this.requestDataFor.bind(this));
this.telemetryObjects.forEach(this.subscribeTo.bind(this));
}
removeTelemetryObject(objectIdentifier) {
this.configuration.removeColumnsForObject(objectIdentifier, true);
let keyString = this.openmct.objects.makeKeyString(objectIdentifier);
@ -113,8 +128,8 @@ define([
requestDataFor(telemetryObject) {
this.incrementOutstandingRequests();
return this.openmct.telemetry.request(telemetryObject)
let requestOptions = this.buildOptionsFromConfiguration(telemetryObject);
return this.openmct.telemetry.request(telemetryObject, requestOptions)
.then(telemetryData => {
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let columnMap = this.getColumnMapForObject(keyString);
@ -165,19 +180,29 @@ define([
}
subscribeTo(telemetryObject) {
let subscribeOptions = this.buildOptionsFromConfiguration(telemetryObject);
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let columnMap = this.getColumnMapForObject(keyString);
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
this.subscriptions[keyString] = this.openmct.telemetry.subscribe(telemetryObject, (datum) => {
this.boundedRows.add(new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
});
}, subscribeOptions);
}
isTelemetryObject(domainObject) {
return domainObject.hasOwnProperty('telemetry');
}
buildOptionsFromConfiguration(telemetryObject) {
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier),
filters = this.domainObject.configuration &&
this.domainObject.configuration.filters &&
this.domainObject.configuration.filters[keyString];
return {filters} || {};
}
unsubscribe(keyString) {
this.subscriptions[keyString]();
delete this.subscriptions[keyString];
@ -188,6 +213,9 @@ define([
this.filteredRows.destroy();
Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
this.openmct.time.off('bounds', this.refreshData);
if (this.filterObserver) {
this.filterObserver();
}
if (this.tableComposition !== undefined) {
this.tableComposition.off('add', this.addTelemetryObject);

View File

@ -49,7 +49,7 @@ define(function () {
getFormattedValue(telemetryDatum) {
let formattedValue = this.formatter.format(telemetryDatum);
if (typeof formattedValue !== 'string') {
if (formattedValue !== undefined && typeof formattedValue !== 'string') {
return formattedValue.toString();
} else {
return formattedValue;

View File

@ -1,19 +1,21 @@
<template>
<div class="c-properties" v-if="isEditing">
<div class="c-properties__header">Table Column Size</div>
<ul class="c-properties__section">
<li class="c-properties__row">
<div class="c-properties__label" title="Show or Hide Column"><label for="AutoSizeControl">Auto-size</label></div>
<div class="c-properties__value"><input type="checkbox" id="AutoSizeControl" :checked="configuration.autosize !== false" @change="toggleAutosize()"></div>
</li>
</ul>
<div class="c-properties__header">Table Column Visibility</div>
<ul class="c-properties__section">
<li class="c-properties__row" v-for="(title, key) in headers">
<div class="c-properties__label" title="Show or Hide Column"><label :for="key + 'ColumnControl'">{{title}}</label></div>
<div class="c-properties__value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div>
</li>
</ul>
<div class="c-properties">
<template v-if="isEditing">
<div class="c-properties__header">Table Column Size</div>
<ul class="c-properties__section">
<li class="c-properties__row">
<div class="c-properties__label" title="Show or Hide Column"><label for="AutoSizeControl">Auto-size</label></div>
<div class="c-properties__value"><input type="checkbox" id="AutoSizeControl" :checked="configuration.autosize !== false" @change="toggleAutosize()"></div>
</li>
</ul>
<div class="c-properties__header">Table Column Visibility</div>
<ul class="c-properties__section">
<li class="c-properties__row" v-for="(title, key) in headers">
<div class="c-properties__label" title="Show or Hide Column"><label :for="key + 'ColumnControl'">{{title}}</label></div>
<div class="c-properties__value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.hiddenColumns[key] !== true" @change="toggleColumn(key)"></div>
</li>
</ul>
</template>
</div>
</template>

View File

@ -239,6 +239,18 @@ select {
padding: 1px 20px 1px $interiorMargin;
}
// CHECKBOX LISTS
// __input followed by __label
.c-checkbox-list {
// Rows
&__row + &__row { margin-top: $interiorMarginSm; }
// input and label in each __row
&__row {
> * + * { margin-left: $interiorMargin; }
}
}
/******************************************************** HYPERLINKS AND HYPERLINK BUTTONS */
.c-hyperlink {
&--link {

View File

@ -87,29 +87,53 @@ export default {
showSelection(selection) {
this.elements = [];
this.elementsCache = [];
this.listeners = [];
this.parentObject = selection[0].context.item;
if (this.mutationUnobserver) {
this.mutationUnobserver();
}
if (this.compositionUnlistener) {
this.compositionUnlistener();
}
if (this.parentObject) {
this.mutationUnobserver = this.openmct.objects.observe(this.parentObject, '*', (updatedModel) => {
this.parentObject = updatedModel;
this.refreshComposition();
});
this.refreshComposition();
this.composition = this.openmct.composition.get(this.parentObject);
if (this.composition) {
this.composition.load();
this.composition.on('add', this.addElement);
this.composition.on('remove', this.removeElement);
this.composition.on('reorder', this.reorderElements);
this.compositionUnlistener = () => {
this.composition.off('add', this.addElement);
this.composition.off('remove', this.removeElement);
this.composition.off('reorder', this.reorderElements);
delete this.compositionUnlistener;
}
}
}
},
refreshComposition() {
let composition = this.openmct.composition.get(this.parentObject);
if (composition){
composition.load().then(this.setElements);
}
addElement(element) {
this.elementsCache.push(JSON.parse(JSON.stringify(element)));
this.applySearch(this.currentSearch);
},
setElements(elements) {
this.elementsCache = elements.map((element)=>JSON.parse(JSON.stringify(element)))
reorderElements(oldIndex, newIndex) {
let tempElement = this.elementsCache[oldIndex];
this.elementsCache[oldIndex] = this.elementsCache[newIndex];
this.elementsCache[newIndex] = tempElement;
this.applySearch(this.currentSearch);
},
removeElement(identifier) {
let index = this.elementsCache.findIndex(cachedElement =>
!this.openmct.objects.areIdsEqual(identifier,
cachedElement.identifier));
this.elementsCache.splice(index, 1);
this.applySearch(this.currentSearch);
},
applySearch(input) {
@ -137,19 +161,7 @@ export default {
event.preventDefault();
},
moveTo(moveToIndex) {
console.log('dropped');
let composition = this.parentObject.composition;
let moveFromId = composition[this.moveFromIndex];
let deleteIndex = this.moveFromIndex;
if (moveToIndex < this.moveFromIndex) {
composition.splice(deleteIndex, 1);
composition.splice(moveToIndex, 0, moveFromId);
} else {
composition.splice(deleteIndex, 1);
composition.splice(moveToIndex, 0, moveFromId);
}
this.openmct.objects.mutate(this.parentObject, 'composition', composition);
this.composition.reorder(this.moveFromIndex, moveToIndex);
},
moveFrom(index){
this.moveFromIndex = index;
@ -157,6 +169,9 @@ export default {
},
destroyed() {
this.openmct.selection.off('change', this.showSelection);
if (this.compositionUnlistener) {
this.compositionUnlistener();
}
}
}
</script>

View File

@ -4,7 +4,7 @@
<pane class="c-inspector__properties">
<properties></properties>
<location></location>
<inspector-view></inspector-view>
<inspector-views></inspector-views>
</pane>
<pane class="c-inspector__elements"
handle="before"
@ -183,6 +183,13 @@
}
/********************************************* LEGACY SUPPORT */
.c-inspector {
// FilterField.vue
.u-contents + .u-contents {
li.grid-row > * {
border-top: 1px solid $colorInspectorSectionHeaderBg;
}
}
li.grid-row + li.grid-row {
> * {
border-top: 1px solid $colorInspectorSectionHeaderBg;
@ -210,7 +217,7 @@
import Elements from './Elements.vue';
import Location from './Location.vue';
import Properties from './Properties.vue';
import InspectorView from './InspectorView.vue';
import InspectorViews from './InspectorViews.vue';
export default {
inject: ['openmct'],
@ -223,7 +230,7 @@
Elements,
Properties,
Location,
InspectorView
InspectorViews
},
data() {
return {

View File

@ -1,37 +0,0 @@
<template>
<div>
</div>
</template>
<style>
</style>
<script>
export default {
inject: ['openmct'],
mounted() {
this.openmct.selection.on('change', this.updateSelection);
this.updateSelection();
},
destroyed() {
this.openmct.selection.off('change', this.updateSelection);
},
methods: {
updateSelection() {
let selection = this.openmct.selection.get();
if (this.selectedView && this.selectedView.destroy) {
this.selectedView.destroy();
delete this.viewContainer;
this.$el.innerHTML = '';
}
this.selectedView = this.openmct.inspectorViews.get(selection);
if (!this.selectedView) {
return;
}
this.viewContainer = document.createElement('div');
this.$el.append(this.viewContainer)
this.selectedView.show(this.viewContainer);
}
}
}
</script>

View File

@ -0,0 +1,40 @@
<template>
<div>
</div>
</template>
<style>
</style>
<script>
export default {
inject: ['openmct'],
mounted() {
this.openmct.selection.on('change', this.updateSelection);
this.updateSelection();
},
destroyed() {
this.openmct.selection.off('change', this.updateSelection);
},
methods: {
updateSelection() {
let selection = this.openmct.selection.get();
if (this.selectedViews) {
this.selectedViews.forEach(selectedView => {
selectedView.destroy();
});
this.$el.innerHTML = '';
}
this.viewContainers = [];
this.selectedViews = this.openmct.inspectorViews.get(selection);
this.selectedViews.forEach(selectedView => {
let viewContainer = document.createElement('div');
this.viewContainers.push(viewContainer);
this.$el.append(viewContainer)
selectedView.show(viewContainer);
});
}
}
}
</script>

View File

@ -42,13 +42,9 @@ define([], function () {
* @private for platform-internal use
*/
InspectorViewRegistry.prototype.get = function (selection) {
var providers = this.getAllProviders().filter(function (provider) {
return this.getAllProviders().filter(function (provider) {
return provider.canView(selection);
});
if (providers && providers.length > 0) {
return providers[0].view(selection);
}
}).map(provider => provider.view(selection));
};
/**