mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 18:50:11 +00:00
Compare commits
30 Commits
eslint_upd
...
vue-table-
Author | SHA1 | Date | |
---|---|---|---|
dbed9b8712 | |||
e9e67e12be | |||
255774cee0 | |||
cb7151cea9 | |||
ed90aa04fc | |||
61ce16d3b0 | |||
edaebe005f | |||
575264d29d | |||
038377410a | |||
73418dee77 | |||
1fbbab29ff | |||
4166811ec6 | |||
df332e6edf | |||
f07dfecf23 | |||
c86fe54ee5 | |||
e2dcb6a7d4 | |||
5113ea9464 | |||
575583d9b4 | |||
7a2c1ea10e | |||
f2443a5c20 | |||
d5f6116226 | |||
c45f857108 | |||
a94610ced2 | |||
4c04aaf32a | |||
60f5700dbf | |||
4685328fc7 | |||
bcac3164a0 | |||
000037229d | |||
9e4b3d8052 | |||
a4629633ef |
@ -60,8 +60,8 @@ define([
|
|||||||
"source": "eventGenerator",
|
"source": "eventGenerator",
|
||||||
"domains": [
|
"domains": [
|
||||||
{
|
{
|
||||||
"key": "time",
|
"key": "utc",
|
||||||
"name": "Time",
|
"name": "Timestamp",
|
||||||
"format": "utc"
|
"format": "utc"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -27,8 +27,14 @@ define([
|
|||||||
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var RED = 0.9,
|
var RED = {
|
||||||
YELLOW = 0.5,
|
sin: 0.9,
|
||||||
|
cos: 0.9
|
||||||
|
},
|
||||||
|
YELLOW = {
|
||||||
|
sin: 0.5,
|
||||||
|
cos: 0.5
|
||||||
|
},
|
||||||
LIMITS = {
|
LIMITS = {
|
||||||
rh: {
|
rh: {
|
||||||
cssClass: "s-limit-upr s-limit-red",
|
cssClass: "s-limit-upr s-limit-red",
|
||||||
@ -67,17 +73,18 @@ define([
|
|||||||
SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) {
|
SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) {
|
||||||
return {
|
return {
|
||||||
evaluate: function (datum, valueMetadata) {
|
evaluate: function (datum, valueMetadata) {
|
||||||
var range = valueMetadata ? valueMetadata.key : 'sin'
|
var range = valueMetadata && valueMetadata.key;
|
||||||
if (datum[range] > RED) {
|
|
||||||
|
if (datum[range] > RED[range]) {
|
||||||
return LIMITS.rh;
|
return LIMITS.rh;
|
||||||
}
|
}
|
||||||
if (datum[range] < -RED) {
|
if (datum[range] < -RED[range]) {
|
||||||
return LIMITS.rl;
|
return LIMITS.rl;
|
||||||
}
|
}
|
||||||
if (datum[range] > YELLOW) {
|
if (datum[range] > YELLOW[range]) {
|
||||||
return LIMITS.yh;
|
return LIMITS.yh;
|
||||||
}
|
}
|
||||||
if (datum[range] < -YELLOW) {
|
if (datum[range] < -YELLOW[range]) {
|
||||||
return LIMITS.yl;
|
return LIMITS.yl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ define([
|
|||||||
var data = [];
|
var data = [];
|
||||||
while (start <= end && data.length < 5000) {
|
while (start <= end && data.length < 5000) {
|
||||||
data.push(pointForTimestamp(start, duration, domainObject.name));
|
data.push(pointForTimestamp(start, duration, domainObject.name));
|
||||||
start += 5000;
|
start += duration;
|
||||||
}
|
}
|
||||||
return Promise.resolve(data);
|
return Promise.resolve(data);
|
||||||
};
|
};
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
<script>
|
<script>
|
||||||
var THIRTY_MINUTES = 30 * 60 * 1000;
|
var THIRTY_MINUTES = 30 * 60 * 1000;
|
||||||
|
|
||||||
require(['openmct'], function (openmct) {
|
require(['openmct', './src/plugins/telemetryTable/plugin'], function (openmct, TelemetryTablePlugin) {
|
||||||
[
|
[
|
||||||
'example/eventGenerator',
|
'example/eventGenerator',
|
||||||
'example/styleguide'
|
'example/styleguide'
|
||||||
@ -70,6 +70,7 @@
|
|||||||
}));
|
}));
|
||||||
openmct.install(openmct.plugins.SummaryWidget());
|
openmct.install(openmct.plugins.SummaryWidget());
|
||||||
openmct.install(openmct.plugins.Notebook());
|
openmct.install(openmct.plugins.Notebook());
|
||||||
|
openmct.install(TelemetryTablePlugin());
|
||||||
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
|
||||||
openmct.time.timeSystem('utc');
|
openmct.time.timeSystem('utc');
|
||||||
openmct.start();
|
openmct.start();
|
||||||
|
68
notes.md
Normal file
68
notes.md
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
* Delete old table
|
||||||
|
* Update new table type and test backward compatibility.
|
||||||
|
* re-evaluate TableConfiguration object. Name doesn't make sense right now, and some duplicated code for configuration handling in components.
|
||||||
|
* Rebase over refactor branch
|
||||||
|
* Move css to new table location
|
||||||
|
* Test (see list of issues below)
|
||||||
|
* Push WIP PR
|
||||||
|
|
||||||
|
* [X] Remove column sizing rows on object removal (should be trivial since tracking by object ID already).
|
||||||
|
* [X] Look at optimizing styles in telemetry-table-row
|
||||||
|
- Right now profiling does not highlight this as a bottleneck?
|
||||||
|
* [X] Add title to table cells
|
||||||
|
* [X] Add elipses for overflow on table cells
|
||||||
|
* [X] On entry, filter boxes need to remove magnifying glass.
|
||||||
|
* [X] auto-scroll
|
||||||
|
* [X] Show / hide columns (ie. table configuration)
|
||||||
|
* [X] Why aren't limits being applied until I scroll or do something?
|
||||||
|
* [X] Handle window resizing
|
||||||
|
* [X] Fix memory leaks
|
||||||
|
* [X] Remove isFromObject and hasColumn from TelemetryTableRow
|
||||||
|
* [X] Remove format caching
|
||||||
|
* [X] Add filtering
|
||||||
|
* If the new filter string starts with the old filter string, filter based on the list of previously filtered results, not the base list.
|
||||||
|
* Add the clear filter button
|
||||||
|
* [X] Cache formatted values for "just in time" formatting. I think cache on row. Opportunity to cache on column to benefit from multiple rows with the same value, but memory management becomes a problem then as cache could grow infinitely if the table is left to run.
|
||||||
|
* [X] Do some more testing with multiple objects. Not working properly right now.
|
||||||
|
* [X] Rows not being removed when object removed from composition
|
||||||
|
* [X] Subscribe to realtime data
|
||||||
|
* [X] Column widths should be done on receipt of FIRST DATA, not on receipt of historical data.
|
||||||
|
* [X] Filter subscription data
|
||||||
|
* [X] Export
|
||||||
|
* [X] Add loading spinner
|
||||||
|
* [X] in 'mounted', should not be necessary to bind to 'this'.
|
||||||
|
* [X] Stop Vue from decorating EVERYTHING (but especially the telemetry collection)
|
||||||
|
* [X] Need minimum width on tables. Provided by calcTableWidthPx in MCTTableController
|
||||||
|
* [X] Limits
|
||||||
|
|
||||||
|
* Benchmark - loading 1 million rows
|
||||||
|
- Old tables: ~90s
|
||||||
|
- New tables: ~11s
|
||||||
|
* 1 million rows in 11 secs vs 90s
|
||||||
|
|
||||||
|
To Test
|
||||||
|
* Multiple instances of tables
|
||||||
|
* Make sure time columns are being correctly merged
|
||||||
|
* Test with MSL data sources
|
||||||
|
* Test with tutorial data sources
|
||||||
|
* Behavior at different widths.
|
||||||
|
* Short tables
|
||||||
|
* Test with bounds / clock / time system changes.
|
||||||
|
* Memory leaks
|
||||||
|
|
||||||
|
Post WIP PR
|
||||||
|
* Fix jitter on auto-scroll
|
||||||
|
* Look at scroll-x again. Sounded like there might be some subtlety missing there (something to do with small columns?).
|
||||||
|
* Split TelemetryTableComponent into more components. It's too large now.
|
||||||
|
* Performance
|
||||||
|
* Don't wrap row on load, do it on scroll.
|
||||||
|
* On batch insert, check bounds once, rather than on each insert.
|
||||||
|
* See if sticky headers can be simplified (eg. can we combine headers table with content table?)
|
||||||
|
* Default sort behavior, and sticking to the bottom for realtime numerical
|
||||||
|
* Look at setting top on tbody, instead of each tr
|
||||||
|
* Replace all "mct-table" classes
|
||||||
|
* Consider making the sizing row a separate component. Encapsulate all sizing logic in there.
|
||||||
|
* consider making the header table a separate component.
|
||||||
|
* Test where no time column present (what will it sort by)
|
||||||
|
|
||||||
|
* [X] Optimization - don't both sorting filtered rows initially, just copy over values from bounded row collection which have already been sorted.
|
@ -133,19 +133,11 @@
|
|||||||
/******************************************************** LOCAL CONTROLS */
|
/******************************************************** LOCAL CONTROLS */
|
||||||
// Controls placed in proximity to or overlaid on components and views
|
// Controls placed in proximity to or overlaid on components and views
|
||||||
|
|
||||||
.local-controls-persist {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.local-controls-hidden {
|
.local-controls-hidden {
|
||||||
// Used within .has-local-controls, hidden by default
|
// Used within .has-local-controls, hidden by default
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.local-controls-flyout {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
body.desktop .has-local-controls {
|
body.desktop .has-local-controls {
|
||||||
// Helper class, provides hover ability to show local controls
|
// Helper class, provides hover ability to show local controls
|
||||||
|
|
||||||
@ -156,7 +148,7 @@ body.desktop .has-local-controls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.local-controls-hidden {
|
.local-controls-hidden {
|
||||||
@include trans-prop-nice($props: opacity, $dur: 1000ms);
|
@include trans-prop-nice($props: opacity, $dur: 500ms);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@ -211,6 +203,7 @@ body.desktop .has-local-controls {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 1em; width: 1em;
|
height: 1em; width: 1em;
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
|
position: relative;
|
||||||
&:before {
|
&:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@include trans-prop-nice(transform, 100ms);
|
@include trans-prop-nice(transform, 100ms);
|
||||||
|
@ -97,6 +97,7 @@ input.c-search__search-input {
|
|||||||
box-shadow: none !important; // !important needed to override default for [input]
|
box-shadow: none !important; // !important needed to override default for [input]
|
||||||
flex: 1 1 99%;
|
flex: 1 1 99%;
|
||||||
min-width: 10px;
|
min-width: 10px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-search__search-menu-holder {
|
.c-search__search-menu-holder {
|
||||||
@ -109,6 +110,10 @@ input.c-search__search-input {
|
|||||||
.holder-search {
|
.holder-search {
|
||||||
$iconWidth: 20px;
|
$iconWidth: 20px;
|
||||||
|
|
||||||
|
.c-search-btn-wrapper {
|
||||||
|
margin-right: $interiorMarginLg; // Fend off rights side from pane splitter control
|
||||||
|
}
|
||||||
|
|
||||||
.results-msg {
|
.results-msg {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
@ -144,6 +144,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.s-status-taking-snapshot,
|
||||||
|
.overlay.snapshot {
|
||||||
|
// Handle overflow-y issues with tables and html2canvas
|
||||||
|
.l-sticky-headers .l-tabular-body { overflow: auto; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/********************************************* MOBILE */
|
/********************************************* MOBILE */
|
||||||
body.mobile {
|
body.mobile {
|
||||||
// Hide the start entry area, and disable ability to edit or delete an entry in mobile context
|
// Hide the start entry area, and disable ability to edit or delete an entry in mobile context
|
||||||
@ -285,24 +292,3 @@ body.phone.portrait {
|
|||||||
.overlay.l-dialog .abs.editor {
|
.overlay.l-dialog .abs.editor {
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
.overlay.l-dialog .outer-holder.annotation-dialog{
|
|
||||||
width: 90%;
|
|
||||||
height: 90%;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
.snap-annotation-wrapper{
|
|
||||||
padding-top: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.t-console {
|
|
||||||
// Temp console-like reporting element
|
|
||||||
max-height: 200px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
@ -34,10 +34,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.s-notebook-entry {
|
.s-notebook-entry {
|
||||||
|
transition: background-color 500ms ease-out;
|
||||||
background-color: rgba($colorBodyFg, 0.1);
|
background-color: rgba($colorBodyFg, 0.1);
|
||||||
border-radius: $basicCr;
|
border-radius: $basicCr;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
transition: background-color 50ms ease-in;
|
||||||
background-color: rgba($colorBodyFg, 0.2);
|
background-color: rgba($colorBodyFg, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- delete entry -->
|
<!-- delete entry -->
|
||||||
<div class="holder flex-elem local-control notebook-entry-delete">
|
<div class="holder flex-elem local-control local-controls-hidden notebook-entry-delete">
|
||||||
<a class="s-icon-button icon-trash" id={{entry.id}} title="Delete Entry" ng-click="deleteEntry($event)"></a>
|
<a class="s-icon-button icon-trash" id={{entry.id}} title="Delete Entry" ng-click="deleteEntry($event)"></a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
mct-table {
|
|
||||||
.mct-sizing-table {
|
.mct-sizing-table {
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
@ -57,10 +57,12 @@ mct-table {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mct-table {
|
||||||
.l-control-bar {
|
.l-control-bar {
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<mct-table
|
<mct-table
|
||||||
headers="headers"
|
headers="headers"
|
||||||
rows="rows"
|
rows="rows"
|
||||||
time-columns="tableController.timeColumns"
|
time-columns="[tableController.table.timeSystemColumnTitle]"
|
||||||
format-cell="formatCell"
|
format-cell="formatCell"
|
||||||
enableFilter="true"
|
enableFilter="true"
|
||||||
enableSort="true"
|
enableSort="true"
|
||||||
|
69
platform/features/table/src/TableColumn.js
Normal file
69
platform/features/table/src/TableColumn.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(function () {
|
||||||
|
function TableColumn(openmct, telemetryObject, metadatum) {
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.telemetryObject = telemetryObject;
|
||||||
|
this.metadatum = metadatum;
|
||||||
|
this.formatter = openmct.telemetry.getValueFormatter(metadatum);
|
||||||
|
|
||||||
|
this.titleValue = this.metadatum.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableColumn.prototype.title = function (title) {
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
this.titleValue = title;
|
||||||
|
}
|
||||||
|
return this.titleValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
TableColumn.prototype.isCurrentTimeSystem = function () {
|
||||||
|
var isCurrentTimeSystem = this.metadatum.hints.hasOwnProperty('domain') &&
|
||||||
|
this.metadatum.key === this.openmct.time.timeSystem().key;
|
||||||
|
|
||||||
|
return isCurrentTimeSystem;
|
||||||
|
};
|
||||||
|
|
||||||
|
TableColumn.prototype.hasValue = function (telemetryObject, telemetryDatum) {
|
||||||
|
var keyStringForDatum = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||||
|
var keyStringForColumn = this.openmct.objects.makeKeyString(this.telemetryObject.identifier);
|
||||||
|
return keyStringForDatum === keyStringForColumn && telemetryDatum.hasOwnProperty(this.metadatum.source);
|
||||||
|
};
|
||||||
|
|
||||||
|
TableColumn.prototype.getValue = function (telemetryDatum, limitEvaluator) {
|
||||||
|
var isValueColumn = !!(this.metadatum.hints.y || this.metadatum.hints.range);
|
||||||
|
var alarm = isValueColumn &&
|
||||||
|
limitEvaluator &&
|
||||||
|
limitEvaluator.evaluate(telemetryDatum, this.metadatum);
|
||||||
|
var value = {
|
||||||
|
text: this.formatter.format(telemetryDatum),
|
||||||
|
value: this.formatter.parse(telemetryDatum)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (alarm) {
|
||||||
|
value.cssClass = alarm.cssClass;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
return TableColumn;
|
||||||
|
});
|
@ -19,10 +19,10 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
/* global Set */
|
||||||
define(
|
define(
|
||||||
[],
|
['./TableColumn'],
|
||||||
function () {
|
function (TableColumn) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class that manages table metadata, state, and contents.
|
* Class that manages table metadata, state, and contents.
|
||||||
@ -32,62 +32,31 @@ define(
|
|||||||
*/
|
*/
|
||||||
function TableConfiguration(domainObject, openmct) {
|
function TableConfiguration(domainObject, openmct) {
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.columns = [];
|
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
|
this.timeSystemColumn = undefined;
|
||||||
|
this.columns = [];
|
||||||
|
this.headers = new Set();
|
||||||
|
this.timeSystemColumnTitle = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build column definitions based on supplied telemetry metadata
|
* Build column definition based on supplied telemetry metadata
|
||||||
|
* @param telemetryObject the telemetry producing object associated with this column
|
||||||
* @param metadata Metadata describing the domains and ranges available
|
* @param metadata Metadata describing the domains and ranges available
|
||||||
* @returns {TableConfiguration} This object
|
* @returns {TableConfiguration} This object
|
||||||
*/
|
*/
|
||||||
TableConfiguration.prototype.populateColumns = function (metadata) {
|
TableConfiguration.prototype.addColumn = function (telemetryObject, metadatum) {
|
||||||
var self = this;
|
var column = new TableColumn(this.openmct, telemetryObject, metadatum);
|
||||||
var telemetryApi = this.openmct.telemetry;
|
|
||||||
|
|
||||||
this.columns = [];
|
if (column.isCurrentTimeSystem()) {
|
||||||
|
if (!this.timeSystemColumnTitle) {
|
||||||
if (metadata) {
|
this.timeSystemColumnTitle = column.title();
|
||||||
|
}
|
||||||
metadata.forEach(function (metadatum) {
|
column.title(this.timeSystemColumnTitle);
|
||||||
var formatter = telemetryApi.getValueFormatter(metadatum);
|
|
||||||
|
|
||||||
self.columns.push({
|
|
||||||
getKey: function () {
|
|
||||||
return metadatum.key;
|
|
||||||
},
|
|
||||||
getTitle: function () {
|
|
||||||
return metadatum.name;
|
|
||||||
},
|
|
||||||
getValue: function (telemetryDatum, limitEvaluator) {
|
|
||||||
var isValueColumn = !!(metadatum.hints.y || metadatum.hints.range);
|
|
||||||
var alarm = isValueColumn &&
|
|
||||||
limitEvaluator &&
|
|
||||||
limitEvaluator.evaluate(telemetryDatum, metadatum);
|
|
||||||
var value = {
|
|
||||||
text: formatter.format(telemetryDatum),
|
|
||||||
value: formatter.parse(telemetryDatum)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (alarm) {
|
|
||||||
value.cssClass = alarm.cssClass;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
this.columns.push(column);
|
||||||
* Get a simple list of column titles
|
this.headers.add(column.title());
|
||||||
* @returns {Array} The titles of the columns
|
|
||||||
*/
|
|
||||||
TableConfiguration.prototype.getHeaders = function () {
|
|
||||||
return this.columns.map(function (column, i) {
|
|
||||||
return column.getTitle() || 'Column ' + (i + 1);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,22 +67,32 @@ define(
|
|||||||
* @returns {Object} Key value pairs where the key is the column
|
* @returns {Object} Key value pairs where the key is the column
|
||||||
* title, and the value is the formatted value from the provided datum.
|
* title, and the value is the formatted value from the provided datum.
|
||||||
*/
|
*/
|
||||||
TableConfiguration.prototype.getRowValues = function (limitEvaluator, datum) {
|
TableConfiguration.prototype.getRowValues = function (telemetryObject, limitEvaluator, datum) {
|
||||||
return this.columns.reduce(function (rowObject, column, i) {
|
return this.columns.reduce(function (rowObject, column) {
|
||||||
var columnTitle = column.getTitle() || 'Column ' + (i + 1),
|
var columnTitle = column.title();
|
||||||
columnValue = column.getValue(datum, limitEvaluator);
|
var columnValue = {
|
||||||
|
text: '',
|
||||||
if (columnValue !== undefined && columnValue.text === undefined) {
|
value: undefined
|
||||||
columnValue.text = '';
|
};
|
||||||
}
|
if (rowObject[columnTitle] === undefined) {
|
||||||
// Don't replace something with nothing.
|
|
||||||
// This occurs when there are multiple columns with the same
|
|
||||||
// column title
|
|
||||||
if (rowObject[columnTitle] === undefined ||
|
|
||||||
rowObject[columnTitle].text === undefined ||
|
|
||||||
rowObject[columnTitle].text.length === 0) {
|
|
||||||
rowObject[columnTitle] = columnValue;
|
rowObject[columnTitle] = columnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (column.hasValue(telemetryObject, datum)) {
|
||||||
|
columnValue = column.getValue(datum, limitEvaluator);
|
||||||
|
|
||||||
|
if (columnValue.text === undefined) {
|
||||||
|
columnValue.text = '';
|
||||||
|
}
|
||||||
|
// Don't replace something with nothing.
|
||||||
|
// This occurs when there are multiple columns with the same
|
||||||
|
// column title
|
||||||
|
if (rowObject[columnTitle].text === undefined ||
|
||||||
|
rowObject[columnTitle].text.length === 0) {
|
||||||
|
rowObject[columnTitle] = columnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return rowObject;
|
return rowObject;
|
||||||
}, {});
|
}, {});
|
||||||
};
|
};
|
||||||
@ -164,7 +143,7 @@ define(
|
|||||||
* specifying whether the column is visible or not. Default to
|
* specifying whether the column is visible or not. Default to
|
||||||
* existing (persisted) configuration if available
|
* existing (persisted) configuration if available
|
||||||
*/
|
*/
|
||||||
this.getHeaders().forEach(function (columnTitle) {
|
this.headers.forEach(function (columnTitle) {
|
||||||
configuration[columnTitle] =
|
configuration[columnTitle] =
|
||||||
typeof defaultConfig[columnTitle] === 'undefined' ? true :
|
typeof defaultConfig[columnTitle] === 'undefined' ? true :
|
||||||
defaultConfig[columnTitle];
|
defaultConfig[columnTitle];
|
||||||
|
@ -93,7 +93,9 @@ define(
|
|||||||
// Calculate the new index of the last item in bounds
|
// Calculate the new index of the last item in bounds
|
||||||
endIndex = _.sortedLastIndex(this.highBuffer, testValue, this.sortField);
|
endIndex = _.sortedLastIndex(this.highBuffer, testValue, this.sortField);
|
||||||
added = this.highBuffer.splice(0, endIndex);
|
added = this.highBuffer.splice(0, endIndex);
|
||||||
this.telemetry = this.telemetry.concat(added);
|
added.forEach(function (datum) {
|
||||||
|
this.telemetry.push(datum);
|
||||||
|
}.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (discarded && discarded.length > 0) {
|
if (discarded && discarded.length > 0) {
|
||||||
@ -132,6 +134,7 @@ define(
|
|||||||
// bounds events, so no bounds checking necessary
|
// bounds events, so no bounds checking necessary
|
||||||
if (this.sortField === undefined) {
|
if (this.sortField === undefined) {
|
||||||
this.telemetry.push(item);
|
this.telemetry.push(item);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +156,6 @@ define(
|
|||||||
|
|
||||||
// If out of bounds low, disregard data
|
// If out of bounds low, disregard data
|
||||||
if (!boundsLow) {
|
if (!boundsLow) {
|
||||||
|
|
||||||
// Going to check for duplicates. Bound the search problem to
|
// Going to check for duplicates. Bound the search problem to
|
||||||
// items around the given time. Use sortedIndex because it
|
// items around the given time. Use sortedIndex because it
|
||||||
// employs a binary search which is O(log n). Can use binary search
|
// employs a binary search which is O(log n). Can use binary search
|
||||||
|
@ -118,23 +118,18 @@ define(
|
|||||||
* to sort by. By default will just match on key.
|
* to sort by. By default will just match on key.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {TimeSystem} timeSystem
|
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.sortByTimeSystem = function (timeSystem) {
|
TelemetryTableController.prototype.sortByTimeSystem = function () {
|
||||||
var scope = this.$scope;
|
var scope = this.$scope;
|
||||||
var sortColumn;
|
var sortColumn;
|
||||||
scope.defaultSort = undefined;
|
scope.defaultSort = undefined;
|
||||||
|
|
||||||
if (timeSystem !== undefined) {
|
sortColumn = this.table.columns.filter(function (column) {
|
||||||
this.table.columns.forEach(function (column) {
|
return column.isCurrentTimeSystem();
|
||||||
if (column.getKey() === timeSystem.key) {
|
})[0];
|
||||||
sortColumn = column;
|
if (sortColumn) {
|
||||||
}
|
scope.defaultSort = sortColumn.title();
|
||||||
});
|
this.telemetry.sort(sortColumn.title() + '.value');
|
||||||
if (sortColumn) {
|
|
||||||
scope.defaultSort = sortColumn.getTitle();
|
|
||||||
this.telemetry.sort(sortColumn.getTitle() + '.value');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -172,9 +167,6 @@ define(
|
|||||||
* @param rows
|
* @param rows
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.addRowsToTable = function (rows) {
|
TelemetryTableController.prototype.addRowsToTable = function (rows) {
|
||||||
rows.forEach(function (row) {
|
|
||||||
this.$scope.rows.push(row);
|
|
||||||
}, this);
|
|
||||||
this.$scope.$broadcast('add:rows', rows);
|
this.$scope.$broadcast('add:rows', rows);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -237,35 +229,21 @@ define(
|
|||||||
TelemetryTableController.prototype.loadColumns = function (objects) {
|
TelemetryTableController.prototype.loadColumns = function (objects) {
|
||||||
var telemetryApi = this.openmct.telemetry;
|
var telemetryApi = this.openmct.telemetry;
|
||||||
|
|
||||||
|
this.table = new TableConfiguration(this.$scope.domainObject,
|
||||||
|
this.openmct);
|
||||||
|
|
||||||
this.$scope.headers = [];
|
this.$scope.headers = [];
|
||||||
|
|
||||||
if (objects.length > 0) {
|
if (objects.length > 0) {
|
||||||
var allMetadata = objects.map(telemetryApi.getMetadata.bind(telemetryApi));
|
objects.forEach(function (object) {
|
||||||
var allValueMetadata = _.flatten(allMetadata.map(
|
var metadataValues = telemetryApi.getMetadata(object).values();
|
||||||
function getMetadataValues(metadata) {
|
metadataValues.forEach(function (metadatum) {
|
||||||
return metadata.values();
|
this.table.addColumn(object, metadatum);
|
||||||
}
|
}.bind(this));
|
||||||
));
|
}.bind(this));
|
||||||
|
|
||||||
this.table.populateColumns(allValueMetadata);
|
|
||||||
|
|
||||||
var domainColumns = telemetryApi.commonValuesForHints(allMetadata, ['domain']);
|
|
||||||
this.timeColumns = domainColumns.map(function (metadatum) {
|
|
||||||
return metadatum.name;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.filterColumns();
|
this.filterColumns();
|
||||||
|
this.sortByTimeSystem();
|
||||||
// Default to no sort on underlying telemetry collection. Sorting
|
|
||||||
// is necessary to do bounds filtering, but this is only possible
|
|
||||||
// if data matches selected time system
|
|
||||||
this.telemetry.sort(undefined);
|
|
||||||
|
|
||||||
var timeSystem = this.openmct.time.timeSystem();
|
|
||||||
if (timeSystem !== undefined) {
|
|
||||||
this.sortByTimeSystem(timeSystem);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return objects;
|
return objects;
|
||||||
@ -302,7 +280,7 @@ define(
|
|||||||
/*
|
/*
|
||||||
* Process a batch of historical data
|
* Process a batch of historical data
|
||||||
*/
|
*/
|
||||||
function processData(historicalData, index, limitEvaluator) {
|
function processData(object, historicalData, index, limitEvaluator) {
|
||||||
if (index >= historicalData.length) {
|
if (index >= historicalData.length) {
|
||||||
processedObjects++;
|
processedObjects++;
|
||||||
|
|
||||||
@ -311,14 +289,13 @@ define(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rowData = rowData.concat(historicalData.slice(index, index + self.batchSize)
|
rowData = rowData.concat(historicalData.slice(index, index + self.batchSize)
|
||||||
.map(self.table.getRowValues.bind(self.table, limitEvaluator)));
|
.map(self.table.getRowValues.bind(self.table, object, limitEvaluator)));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Use timeout to yield process to other UI activities. On
|
Use timeout to yield process to other UI activities. On
|
||||||
return, process next batch
|
return, process next batch
|
||||||
*/
|
*/
|
||||||
self.timeoutHandle = self.$timeout(function () {
|
self.timeoutHandle = self.$timeout(function () {
|
||||||
processData(historicalData, index + self.batchSize, limitEvaluator);
|
processData(object, historicalData, index + self.batchSize, limitEvaluator);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -327,7 +304,7 @@ define(
|
|||||||
// Only process the most recent request
|
// Only process the most recent request
|
||||||
if (requestTime === self.lastRequestTime) {
|
if (requestTime === self.lastRequestTime) {
|
||||||
var limitEvaluator = openmct.telemetry.limitEvaluator(object);
|
var limitEvaluator = openmct.telemetry.limitEvaluator(object);
|
||||||
processData(historicalData, 0, limitEvaluator);
|
processData(object, historicalData, 0, limitEvaluator);
|
||||||
} else {
|
} else {
|
||||||
resolve(rowData);
|
resolve(rowData);
|
||||||
}
|
}
|
||||||
@ -367,7 +344,6 @@ define(
|
|||||||
var telemetryCollection = this.telemetry;
|
var telemetryCollection = this.telemetry;
|
||||||
//Set table max length to avoid unbounded growth.
|
//Set table max length to avoid unbounded growth.
|
||||||
var limitEvaluator;
|
var limitEvaluator;
|
||||||
var added = false;
|
|
||||||
var table = this.table;
|
var table = this.table;
|
||||||
|
|
||||||
this.subscriptions.forEach(function (subscription) {
|
this.subscriptions.forEach(function (subscription) {
|
||||||
@ -377,7 +353,7 @@ define(
|
|||||||
|
|
||||||
function newData(domainObject, datum) {
|
function newData(domainObject, datum) {
|
||||||
limitEvaluator = telemetryApi.limitEvaluator(domainObject);
|
limitEvaluator = telemetryApi.limitEvaluator(domainObject);
|
||||||
added = telemetryCollection.add([table.getRowValues(limitEvaluator, datum)]);
|
telemetryCollection.add([table.getRowValues(domainObject, limitEvaluator, datum)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
objects.forEach(function (object) {
|
objects.forEach(function (object) {
|
||||||
|
@ -27,31 +27,52 @@ define(
|
|||||||
function (Table) {
|
function (Table) {
|
||||||
|
|
||||||
describe("A table", function () {
|
describe("A table", function () {
|
||||||
var mockDomainObject,
|
var mockTableObject,
|
||||||
|
mockTelemetryObject,
|
||||||
mockAPI,
|
mockAPI,
|
||||||
mockTelemetryAPI,
|
mockTelemetryAPI,
|
||||||
table,
|
table,
|
||||||
|
mockTimeAPI,
|
||||||
|
mockObjectsAPI,
|
||||||
mockModel;
|
mockModel;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockDomainObject = jasmine.createSpyObj('domainObject',
|
mockTableObject = jasmine.createSpyObj('domainObject',
|
||||||
['getModel', 'useCapability', 'getCapability', 'hasCapability']
|
['getModel', 'useCapability', 'getCapability', 'hasCapability']
|
||||||
);
|
);
|
||||||
mockModel = {};
|
mockModel = {};
|
||||||
mockDomainObject.getModel.and.returnValue(mockModel);
|
mockTableObject.getModel.and.returnValue(mockModel);
|
||||||
mockDomainObject.getCapability.and.callFake(function (name) {
|
mockTableObject.getCapability.and.callFake(function (name) {
|
||||||
return name === 'editor' && {
|
return name === 'editor' && {
|
||||||
isEditContextRoot: function () {
|
isEditContextRoot: function () {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
mockTelemetryObject = {
|
||||||
|
identifier: {
|
||||||
|
namespace: 'mock',
|
||||||
|
key: 'domainObject'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
mockTelemetryAPI = jasmine.createSpyObj('telemetryAPI', [
|
mockTelemetryAPI = jasmine.createSpyObj('telemetryAPI', [
|
||||||
'getValueFormatter'
|
'getValueFormatter'
|
||||||
]);
|
]);
|
||||||
|
mockTimeAPI = jasmine.createSpyObj('timeAPI', [
|
||||||
|
'timeSystem'
|
||||||
|
]);
|
||||||
|
mockObjectsAPI = jasmine.createSpyObj('objectsAPI', [
|
||||||
|
'makeKeyString'
|
||||||
|
]);
|
||||||
|
mockObjectsAPI.makeKeyString.and.callFake(function (identifier) {
|
||||||
|
return [identifier.namespace, identifier.key].join(':');
|
||||||
|
});
|
||||||
|
|
||||||
mockAPI = {
|
mockAPI = {
|
||||||
telemetry: mockTelemetryAPI
|
telemetry: mockTelemetryAPI,
|
||||||
|
time: mockTimeAPI,
|
||||||
|
objects: mockObjectsAPI
|
||||||
};
|
};
|
||||||
mockTelemetryAPI.getValueFormatter.and.callFake(function (metadata) {
|
mockTelemetryAPI.getValueFormatter.and.callFake(function (metadata) {
|
||||||
var formatter = jasmine.createSpyObj(
|
var formatter = jasmine.createSpyObj(
|
||||||
@ -69,7 +90,7 @@ define(
|
|||||||
return formatter;
|
return formatter;
|
||||||
});
|
});
|
||||||
|
|
||||||
table = new Table(mockDomainObject, mockAPI);
|
table = new Table(mockTableObject, mockAPI);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Building columns from telemetry metadata", function () {
|
describe("Building columns from telemetry metadata", function () {
|
||||||
@ -77,51 +98,57 @@ define(
|
|||||||
{
|
{
|
||||||
name: 'Range 1',
|
name: 'Range 1',
|
||||||
key: 'range1',
|
key: 'range1',
|
||||||
|
source: 'range1',
|
||||||
hints: {
|
hints: {
|
||||||
y: 1
|
range: 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Range 2',
|
name: 'Range 2',
|
||||||
key: 'range2',
|
key: 'range2',
|
||||||
|
source: 'range2',
|
||||||
hints: {
|
hints: {
|
||||||
y: 2
|
range: 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Domain 1',
|
name: 'Domain 1',
|
||||||
key: 'domain1',
|
key: 'domain1',
|
||||||
|
source: 'domain1',
|
||||||
format: 'utc',
|
format: 'utc',
|
||||||
hints: {
|
hints: {
|
||||||
x: 1
|
domain: 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Domain 2',
|
name: 'Domain 2',
|
||||||
key: 'domain2',
|
key: 'domain2',
|
||||||
|
source: 'domain2',
|
||||||
format: 'utc',
|
format: 'utc',
|
||||||
hints: {
|
hints: {
|
||||||
x: 2
|
domain: 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
table.populateColumns(metadata);
|
mockTimeAPI.timeSystem.and.returnValue({
|
||||||
|
key: 'domain1'
|
||||||
|
});
|
||||||
|
metadata.forEach(function (metadatum) {
|
||||||
|
table.addColumn(mockTelemetryObject, metadatum);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("populates columns", function () {
|
it("populates columns", function () {
|
||||||
expect(table.columns.length).toBe(4);
|
expect(table.columns.length).toBe(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Produces headers for each column based on title", function () {
|
it("Produces headers for each column based on metadata name", function () {
|
||||||
var headers,
|
expect(table.headers.size).toBe(4);
|
||||||
firstColumn = table.columns[0];
|
Array.from(table.headers.values).forEach(function (header, i) {
|
||||||
|
expect(header).toEqual(metadata[i].name);
|
||||||
spyOn(firstColumn, 'getTitle');
|
});
|
||||||
headers = table.getHeaders();
|
|
||||||
expect(headers.length).toBe(4);
|
|
||||||
expect(firstColumn.getTitle).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Provides a default configuration with all columns" +
|
it("Provides a default configuration with all columns" +
|
||||||
@ -169,11 +196,10 @@ define(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
rowValues = table.getRowValues(limitEvaluator, datum);
|
rowValues = table.getRowValues(mockTelemetryObject, limitEvaluator, datum);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Returns a value for every column", function () {
|
it("Returns a value for every column", function () {
|
||||||
expect(rowValues['Range 1'].text).toBeDefined();
|
|
||||||
expect(rowValues['Range 1'].text).toEqual(10);
|
expect(rowValues['Range 1'].text).toEqual(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -78,7 +78,8 @@ define(
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
mockObjectAPI = jasmine.createSpyObj("objectAPI", [
|
mockObjectAPI = jasmine.createSpyObj("objectAPI", [
|
||||||
"observe"
|
"observe",
|
||||||
|
"makeKeyString"
|
||||||
]);
|
]);
|
||||||
unobserve = jasmine.createSpy("unobserve");
|
unobserve = jasmine.createSpy("unobserve");
|
||||||
mockObjectAPI.observe.and.returnValue(unobserve);
|
mockObjectAPI.observe.and.returnValue(unobserve);
|
||||||
@ -184,8 +185,7 @@ define(
|
|||||||
var mockComposition,
|
var mockComposition,
|
||||||
mockTelemetryObject,
|
mockTelemetryObject,
|
||||||
mockChildren,
|
mockChildren,
|
||||||
unsubscribe,
|
unsubscribe;
|
||||||
done;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockComposition = jasmine.createSpyObj("composition", [
|
mockComposition = jasmine.createSpyObj("composition", [
|
||||||
@ -207,8 +207,6 @@ define(
|
|||||||
mockTelemetryAPI.isTelemetryObject.and.callFake(function (obj) {
|
mockTelemetryAPI.isTelemetryObject.and.callFake(function (obj) {
|
||||||
return obj.identifier.key === mockTelemetryObject.identifier.key;
|
return obj.identifier.key === mockTelemetryObject.identifier.key;
|
||||||
});
|
});
|
||||||
|
|
||||||
done = false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fetches historical data for the time period specified by the conductor bounds', function () {
|
it('fetches historical data for the time period specified by the conductor bounds', function () {
|
||||||
@ -292,40 +290,37 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('populates table columns', function () {
|
describe('populates table columns', function () {
|
||||||
var domainMetadata;
|
|
||||||
var allMetadata;
|
var allMetadata;
|
||||||
var mockTimeSystem;
|
var mockTimeSystem1;
|
||||||
|
var mockTimeSystem2;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
domainMetadata = [{
|
|
||||||
key: "column1",
|
|
||||||
name: "Column 1",
|
|
||||||
hints: {}
|
|
||||||
}];
|
|
||||||
|
|
||||||
allMetadata = [{
|
allMetadata = [{
|
||||||
key: "column1",
|
key: "column1",
|
||||||
name: "Column 1",
|
name: "Column 1",
|
||||||
hints: {}
|
hints: {
|
||||||
|
domain: 1
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
key: "column2",
|
key: "column2",
|
||||||
name: "Column 2",
|
name: "Column 2",
|
||||||
hints: {}
|
hints: {
|
||||||
|
domain: 2
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
key: "column3",
|
key: "column3",
|
||||||
name: "Column 3",
|
name: "Column 3",
|
||||||
hints: {}
|
hints: {}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
mockTimeSystem = {
|
mockTimeSystem1 = {
|
||||||
key: "column1"
|
key: "column1"
|
||||||
};
|
};
|
||||||
|
mockTimeSystem2 = {
|
||||||
|
key: "column2"
|
||||||
|
};
|
||||||
|
|
||||||
mockTelemetryAPI.commonValuesForHints.and.callFake(function (metadata, hints) {
|
mockConductor.timeSystem.and.returnValue(mockTimeSystem1);
|
||||||
if (_.eq(hints, ["domain"])) {
|
|
||||||
return domainMetadata;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mockTelemetryAPI.getMetadata.and.returnValue({
|
mockTelemetryAPI.getMetadata.and.returnValue({
|
||||||
values: function () {
|
values: function () {
|
||||||
@ -345,9 +340,12 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('and sorts by column matching time system', function () {
|
it('and sorts by column matching time system', function () {
|
||||||
expect(mockScope.defaultSort).not.toEqual("Column 1");
|
|
||||||
controller.sortByTimeSystem(mockTimeSystem);
|
|
||||||
expect(mockScope.defaultSort).toEqual("Column 1");
|
expect(mockScope.defaultSort).toEqual("Column 1");
|
||||||
|
|
||||||
|
mockConductor.timeSystem.and.returnValue(mockTimeSystem2);
|
||||||
|
controller.sortByTimeSystem();
|
||||||
|
|
||||||
|
expect(mockScope.defaultSort).toEqual("Column 2");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('batches processing of rows for performance when receiving historical telemetry', function () {
|
it('batches processing of rows for performance when receiving historical telemetry', function () {
|
||||||
@ -403,25 +401,16 @@ define(
|
|||||||
|
|
||||||
describe('when telemetry is added', function () {
|
describe('when telemetry is added', function () {
|
||||||
var testRows;
|
var testRows;
|
||||||
var expectedRows;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
testRows = [{ a: 0 }, { a: 1 }, { a: 2 }];
|
testRows = [{ a: 0 }, { a: 1 }, { a: 2 }];
|
||||||
mockScope.rows = [{ a: -1 }];
|
|
||||||
expectedRows = mockScope.rows.concat(testRows);
|
|
||||||
|
|
||||||
spyOn(controller.telemetry, "on").and.callThrough();
|
|
||||||
controller.registerChangeListeners();
|
controller.registerChangeListeners();
|
||||||
|
controller.telemetry.add(testRows);
|
||||||
controller.telemetry.on.calls.all().forEach(function (call) {
|
|
||||||
if (call.args[0] === 'added') {
|
|
||||||
call.args[1](testRows);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds it to rows in scope", function () {
|
it("Adds the rows to the MCTTable directive", function () {
|
||||||
expect(mockScope.rows).toEqual(expectedRows);
|
expect(mockScope.$broadcast).toHaveBeenCalledWith("add:rows", testRows);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
39
src/exporters/CSVExporter.js
Normal file
39
src/exporters/CSVExporter.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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([
|
||||||
|
'csv',
|
||||||
|
'saveAs'
|
||||||
|
], function (CSV, saveAs) {
|
||||||
|
class CSVExporter {
|
||||||
|
export(rows, options) {
|
||||||
|
let headers = (options && options.headers) ||
|
||||||
|
(Object.keys((rows[0] || {})).sort());
|
||||||
|
let filename = (options && options.filename) || "export.csv";
|
||||||
|
let csvText = new CSV(rows, { header: headers }).encode();
|
||||||
|
let blob = new Blob([csvText], { type: "text/csv" });
|
||||||
|
saveAs(blob, filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CSVExporter;
|
||||||
|
});
|
@ -113,6 +113,9 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
LocalTimeFormat.prototype.parse = function (text) {
|
LocalTimeFormat.prototype.parse = function (text) {
|
||||||
|
if (typeof text === 'number') {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
return moment(text, DATE_FORMATS).valueOf();
|
return moment(text, DATE_FORMATS).valueOf();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<span class="t-configuration"> </span>
|
<span class="t-configuration"> </span>
|
||||||
<span class="t-value-inputs"> </span>
|
<span class="t-value-inputs"> </span>
|
||||||
</span>
|
</span>
|
||||||
<span class="flex-elem local-control l-condition-action-buttons-wrapper">
|
<span class="flex-elem local-control local-controls-hidden l-condition-action-buttons-wrapper">
|
||||||
<a class="s-icon-button icon-duplicate t-duplicate" title="Duplicate this condition"></a>
|
<a class="s-icon-button icon-duplicate t-duplicate" title="Duplicate this condition"></a>
|
||||||
<a class="s-icon-button icon-trash t-delete" title="Delete this condition"></a>
|
<a class="s-icon-button icon-trash t-delete" title="Delete this condition"></a>
|
||||||
</span>
|
</span>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<div class="widget-rule-header">
|
<div class="widget-rule-header">
|
||||||
<span class="flex-elem l-widget-thumb-wrapper">
|
<span class="flex-elem l-widget-thumb-wrapper">
|
||||||
<span class="grippy-holder">
|
<span class="grippy-holder">
|
||||||
<span class="t-grippy grippy local-control"></span>
|
<span class="t-grippy grippy local-control local-controls-hidden"></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="view-control expanded"></span>
|
<span class="view-control expanded"></span>
|
||||||
<span class="t-widget-thumb widget-thumb">
|
<span class="t-widget-thumb widget-thumb">
|
||||||
@ -12,7 +12,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span class="flex-elem rule-title">Default Title</span>
|
<span class="flex-elem rule-title">Default Title</span>
|
||||||
<span class="flex-elem rule-description grows">Rule description goes here</span>
|
<span class="flex-elem rule-description grows">Rule description goes here</span>
|
||||||
<span class="flex-elem local-control l-rule-action-buttons-wrapper">
|
<span class="flex-elem local-control local-controls-hidden l-rule-action-buttons-wrapper">
|
||||||
<a class="s-icon-button icon-duplicate t-duplicate" title="Duplicate this rule"></a>
|
<a class="s-icon-button icon-duplicate t-duplicate" title="Duplicate this rule"></a>
|
||||||
<a class="s-icon-button icon-trash t-delete" title="Delete this rule"></a>
|
<a class="s-icon-button icon-trash t-delete" title="Delete this rule"></a>
|
||||||
</span>
|
</span>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<span class="equal-to hidden"> equal to </span>
|
<span class="equal-to hidden"> equal to </span>
|
||||||
<span class="t-value-inputs"></span>
|
<span class="t-value-inputs"></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="flex-elem local-control l-widget-test-data-item-action-buttons-wrapper">
|
<span class="flex-elem local-control local-controls-hidden l-widget-test-data-item-action-buttons-wrapper">
|
||||||
<a class="s-icon-button icon-duplicate t-duplicate" title="Duplicate this test value"></a>
|
<a class="s-icon-button icon-duplicate t-duplicate" title="Duplicate this test value"></a>
|
||||||
<a class="s-icon-button icon-trash t-delete" title="Delete this test value"></a>
|
<a class="s-icon-button icon-trash t-delete" title="Delete this test value"></a>
|
||||||
</span>
|
</span>
|
||||||
|
@ -188,7 +188,7 @@ define([
|
|||||||
if (!this.config.values[index]) {
|
if (!this.config.values[index]) {
|
||||||
this.config.values[index] = (inputType === 'number' ? 0 : '');
|
this.config.values[index] = (inputType === 'number' ? 0 : '');
|
||||||
}
|
}
|
||||||
newInput = $('<input class="sm" type = "' + inputType + '" value = "' + this.config.values[index] + '"> </input>');
|
newInput = $('<input type = "' + inputType + '" value = "' + this.config.values[index] + '"> </input>');
|
||||||
this.valueInputs.push(newInput.get(0));
|
this.valueInputs.push(newInput.get(0));
|
||||||
inputArea.append(newInput);
|
inputArea.append(newInput);
|
||||||
index += 1;
|
index += 1;
|
||||||
|
102
src/plugins/telemetryTable/TableConfigurationComponent.js
Normal file
102
src/plugins/telemetryTable/TableConfigurationComponent.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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([
|
||||||
|
'lodash',
|
||||||
|
'vue',
|
||||||
|
'text!./table-configuration.html',
|
||||||
|
'./TelemetryTableConfiguration'
|
||||||
|
],function (
|
||||||
|
_,
|
||||||
|
Vue,
|
||||||
|
TableConfigurationTemplate,
|
||||||
|
TelemetryTableConfiguration
|
||||||
|
) {
|
||||||
|
return function TableConfigurationComponent(domainObject, openmct) {
|
||||||
|
const tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
|
||||||
|
let unlisteners = [];
|
||||||
|
unlisteners.push(openmct.objects.observe(domainObject, '*', (newDomainObject) => {
|
||||||
|
domainObject = newDomainObject;
|
||||||
|
}));
|
||||||
|
|
||||||
|
function defaultConfiguration(domainObject) {
|
||||||
|
let configuration = domainObject.configuration;
|
||||||
|
configuration.table = configuration.table || {
|
||||||
|
columns: {}
|
||||||
|
};
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Vue({
|
||||||
|
template: TableConfigurationTemplate,
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
headers: {},
|
||||||
|
configuration: defaultConfiguration(domainObject)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateHeaders: function (headers) {
|
||||||
|
this.headers = headers;
|
||||||
|
},
|
||||||
|
toggleColumn: function (key) {
|
||||||
|
let isVisible = this.configuration.table.columns[key];
|
||||||
|
|
||||||
|
if (isVisible === undefined) {
|
||||||
|
isVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.configuration.table.columns[key] = !isVisible;
|
||||||
|
openmct.objects.mutate(domainObject, "configuration", this.configuration);
|
||||||
|
},
|
||||||
|
addObject: function (domainObject) {
|
||||||
|
tableConfiguration.addColumnsForObject(domainObject, true);
|
||||||
|
this.updateHeaders(tableConfiguration.getHeaders());
|
||||||
|
},
|
||||||
|
removeObject: function (objectIdentifier) {
|
||||||
|
tableConfiguration.removeColumnsForObject(objectIdentifier, true);
|
||||||
|
this.updateHeaders(tableConfiguration.getHeaders());
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
let compositionCollection = openmct.composition.get(domainObject);
|
||||||
|
|
||||||
|
compositionCollection.load()
|
||||||
|
.then((composition) => {
|
||||||
|
tableConfiguration.addColumnsForAllObjects(composition);
|
||||||
|
this.updateHeaders(tableConfiguration.getHeaders());
|
||||||
|
|
||||||
|
compositionCollection.on('add', this.addObject);
|
||||||
|
unlisteners.push(compositionCollection.off.bind(compositionCollection, 'add', this.addObject));
|
||||||
|
|
||||||
|
compositionCollection.on('remove', this.removeObject);
|
||||||
|
unlisteners.push(compositionCollection.off.bind(compositionCollection, 'remove', this.removeObject));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroyed: function () {
|
||||||
|
tableConfiguration.destroy();
|
||||||
|
unlisteners.forEach((unlisten) => unlisten());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
85
src/plugins/telemetryTable/TableConfigurationViewProvider.js
Normal file
85
src/plugins/telemetryTable/TableConfigurationViewProvider.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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([
|
||||||
|
'../../api/objects/object-utils',
|
||||||
|
'./TableConfigurationComponent'
|
||||||
|
], function (
|
||||||
|
objectUtils,
|
||||||
|
TableConfigurationComponent
|
||||||
|
) {
|
||||||
|
function TableConfigurationViewProvider(openmct) {
|
||||||
|
let instantiateService;
|
||||||
|
|
||||||
|
function isBeingEdited(object) {
|
||||||
|
let oldStyleObject = getOldStyleObject(object);
|
||||||
|
|
||||||
|
return oldStyleObject.hasCapability('editor') &&
|
||||||
|
oldStyleObject.getCapability('editor').isEditContextRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOldStyleObject(object) {
|
||||||
|
let oldFormatModel = objectUtils.toOldFormat(object);
|
||||||
|
let oldFormatId = objectUtils.makeKeyString(object.identifier);
|
||||||
|
|
||||||
|
return instantiate(oldFormatModel, oldFormatId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function instantiate(model, id) {
|
||||||
|
if (!instantiateService) {
|
||||||
|
instantiateService = openmct.$injector.get('instantiate');
|
||||||
|
}
|
||||||
|
return instantiateService(model, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: 'table-configuration',
|
||||||
|
name: 'Telemetry Table Configuration',
|
||||||
|
canView: function (selection) {
|
||||||
|
let object = selection[0].context.item;
|
||||||
|
|
||||||
|
return selection.length > 0 &&
|
||||||
|
object.type === 'vue-table' &&
|
||||||
|
isBeingEdited(object);
|
||||||
|
},
|
||||||
|
view: function (selection) {
|
||||||
|
let component;
|
||||||
|
let domainObject = selection[0].context.item;
|
||||||
|
return {
|
||||||
|
show: function (element) {
|
||||||
|
component = TableConfigurationComponent(domainObject, openmct);
|
||||||
|
element.appendChild(component.$mount().$el);
|
||||||
|
},
|
||||||
|
destroy: function (element) {
|
||||||
|
component.$destroy();
|
||||||
|
element.removeChild(component.$el);
|
||||||
|
component = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
priority: function () {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TableConfigurationViewProvider;
|
||||||
|
});
|
145
src/plugins/telemetryTable/TelemetryTable.js
Normal file
145
src/plugins/telemetryTable/TelemetryTable.js
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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([
|
||||||
|
'EventEmitter',
|
||||||
|
'lodash',
|
||||||
|
'./collections/BoundedTableRowCollection',
|
||||||
|
'./collections/FilteredTableRowCollection',
|
||||||
|
'./TelemetryTableRow',
|
||||||
|
'./TelemetryTableConfiguration'
|
||||||
|
], function (
|
||||||
|
EventEmitter,
|
||||||
|
_,
|
||||||
|
BoundedTableRowCollection,
|
||||||
|
FilteredTableRowCollection,
|
||||||
|
TelemetryTableRow,
|
||||||
|
TelemetryTableConfiguration
|
||||||
|
) {
|
||||||
|
class TelemetryTable extends EventEmitter {
|
||||||
|
constructor(domainObject, rowCount, openmct) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.domainObject = domainObject;
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.rowCount = rowCount;
|
||||||
|
this.subscriptions = {};
|
||||||
|
this.tableComposition = undefined;
|
||||||
|
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
|
||||||
|
|
||||||
|
this.addTelemetryObject = this.addTelemetryObject.bind(this);
|
||||||
|
this.removeTelemetryObject = this.removeTelemetryObject.bind(this);
|
||||||
|
|
||||||
|
this.createTableRowCollections();
|
||||||
|
this.loadComposition();
|
||||||
|
}
|
||||||
|
|
||||||
|
createTableRowCollections() {
|
||||||
|
this.boundedRows = new BoundedTableRowCollection(this.openmct);
|
||||||
|
|
||||||
|
//By default, sort by current time system, ascending.
|
||||||
|
this.filteredRows = new FilteredTableRowCollection(this.boundedRows);
|
||||||
|
this.filteredRows.sortBy({
|
||||||
|
key: this.openmct.time.timeSystem().key,
|
||||||
|
direction: 'asc'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadComposition() {
|
||||||
|
this.tableComposition = this.openmct.composition.get(this.domainObject);
|
||||||
|
this.tableComposition.load().then((composition)=>{
|
||||||
|
this.configuration.addColumnsForAllObjects(composition);
|
||||||
|
composition.forEach(this.addTelemetryObject);
|
||||||
|
|
||||||
|
this.tableComposition.on('add', this.addTelemetryObject);
|
||||||
|
this.tableComposition.on('remove', this.removeTelemetryObject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addTelemetryObject(telemetryObject) {
|
||||||
|
this.configuration.addColumnsForObject(telemetryObject, true);
|
||||||
|
this.requestDataFor(telemetryObject);
|
||||||
|
this.subscribeTo(telemetryObject);
|
||||||
|
|
||||||
|
this.emit('object-added', telemetryObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTelemetryObject(objectIdentifier) {
|
||||||
|
this.configuration.removeColumnsForObject(objectIdentifier, true);
|
||||||
|
let keyString = this.openmct.objects.makeKeyString(objectIdentifier);
|
||||||
|
this.boundedRows.removeAllRowsForObject(keyString);
|
||||||
|
this.unsubscribe(keyString);
|
||||||
|
|
||||||
|
this.emit('object-removed', objectIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestDataFor(telemetryObject) {
|
||||||
|
this.emit('loading-historical-data', true);
|
||||||
|
this.openmct.telemetry.request(telemetryObject)
|
||||||
|
.then(telemetryData => {
|
||||||
|
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||||
|
let columnMap = this.getColumnMapForObject(keyString);
|
||||||
|
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
|
||||||
|
|
||||||
|
let telemetryRows = telemetryData.map(datum => new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator));
|
||||||
|
this.boundedRows.add(telemetryRows);
|
||||||
|
console.log('loaded ' + telemetryRows.length + ' rows');
|
||||||
|
this.emit('loading-historical-data', false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getColumnMapForObject(objectKeyString) {
|
||||||
|
let columns = this.configuration.getColumns();
|
||||||
|
|
||||||
|
return columns[objectKeyString].reduce((map, column) => {
|
||||||
|
map[column.getKey()] = column;
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeTo(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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe(keyString) {
|
||||||
|
this.subscriptions[keyString]();
|
||||||
|
delete this.subscriptions[keyString];
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.boundedRows.destroy();
|
||||||
|
this.filteredRows.destroy();
|
||||||
|
Object.keys(this.subscriptions).forEach(this.unsubscribe, this);
|
||||||
|
|
||||||
|
this.tableComposition.off('add', this.addTelemetryObject);
|
||||||
|
this.tableComposition.off('remove', this.removeTelemetryObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TelemetryTable;
|
||||||
|
});
|
57
src/plugins/telemetryTable/TelemetryTableColumn.js
Normal file
57
src/plugins/telemetryTable/TelemetryTableColumn.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(function () {
|
||||||
|
class TelemetryTableColumn {
|
||||||
|
constructor (openmct, metadatum) {
|
||||||
|
this.metadatum = metadatum;
|
||||||
|
this.formatter = openmct.telemetry.getValueFormatter(metadatum);
|
||||||
|
this.titleValue = this.metadatum.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
getKey() {
|
||||||
|
return this.metadatum.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTitle() {
|
||||||
|
return this.metadatum.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMetadatum() {
|
||||||
|
return this.metadatum;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasValueForDatum(telemetryDatum) {
|
||||||
|
return telemetryDatum.hasOwnProperty(this.metadatum.source);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRawValue(telemetryDatum) {
|
||||||
|
return telemetryDatum[this.metadatum.source];
|
||||||
|
}
|
||||||
|
|
||||||
|
getFormattedValue(telemetryDatum) {
|
||||||
|
return this.formatter.format(telemetryDatum);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return TelemetryTableColumn;
|
||||||
|
});
|
309
src/plugins/telemetryTable/TelemetryTableComponent.js
Normal file
309
src/plugins/telemetryTable/TelemetryTableComponent.js
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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([
|
||||||
|
'lodash',
|
||||||
|
'vue',
|
||||||
|
'text!./telemetry-table.html',
|
||||||
|
'./TelemetryTable',
|
||||||
|
'./TelemetryTableRowComponent',
|
||||||
|
'../../exporters/CSVExporter'
|
||||||
|
],function (
|
||||||
|
_,
|
||||||
|
Vue,
|
||||||
|
TelemetryTableTemplate,
|
||||||
|
TelemetryTable,
|
||||||
|
TelemetryTableRowComponent,
|
||||||
|
CSVExporter
|
||||||
|
) {
|
||||||
|
const VISIBLE_ROW_COUNT = 100;
|
||||||
|
const ROW_HEIGHT = 17;
|
||||||
|
const RESIZE_POLL_INTERVAL = 200;
|
||||||
|
const AUTO_SCROLL_TRIGGER_HEIGHT = 20;
|
||||||
|
|
||||||
|
return function TelemetryTableComponent(domainObject, openmct) {
|
||||||
|
const csvExporter = new CSVExporter();
|
||||||
|
const table = new TelemetryTable(domainObject, VISIBLE_ROW_COUNT, openmct);
|
||||||
|
let processingScroll = false;
|
||||||
|
let observationUnlistener;
|
||||||
|
|
||||||
|
function defaultConfiguration(domainObject) {
|
||||||
|
let configuration = domainObject.configuration;
|
||||||
|
configuration.table = configuration.table || {
|
||||||
|
columns: {}
|
||||||
|
};
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Vue({
|
||||||
|
template: TelemetryTableTemplate,
|
||||||
|
components: {
|
||||||
|
'telemetry-table-row': TelemetryTableRowComponent
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
headers: {},
|
||||||
|
configuration: defaultConfiguration(domainObject),
|
||||||
|
headersCount: 0,
|
||||||
|
visibleRows: [],
|
||||||
|
columnWidths: [],
|
||||||
|
sizingRows: {},
|
||||||
|
rowHeight: ROW_HEIGHT,
|
||||||
|
scrollOffset: 0,
|
||||||
|
totalHeight: 0,
|
||||||
|
totalWidth: 0,
|
||||||
|
rowOffset: 0,
|
||||||
|
autoScroll: true,
|
||||||
|
sortOptions: {},
|
||||||
|
filters: {},
|
||||||
|
loading: false,
|
||||||
|
scrollable: undefined,
|
||||||
|
tableEl: undefined,
|
||||||
|
headersHolderEl: undefined,
|
||||||
|
calcTableWidth: '100%'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateVisibleRows: function () {
|
||||||
|
let start = 0;
|
||||||
|
let end = VISIBLE_ROW_COUNT;
|
||||||
|
let filteredRows = table.filteredRows.getRows();
|
||||||
|
let filteredRowsLength = filteredRows.length;
|
||||||
|
|
||||||
|
this.totalHeight = this.rowHeight * filteredRowsLength - 1;
|
||||||
|
|
||||||
|
if (filteredRowsLength < VISIBLE_ROW_COUNT) {
|
||||||
|
end = filteredRowsLength;
|
||||||
|
} else {
|
||||||
|
let firstVisible = this.calculateFirstVisibleRow();
|
||||||
|
let lastVisible = this.calculateLastVisibleRow();
|
||||||
|
let totalVisible = lastVisible - firstVisible;
|
||||||
|
|
||||||
|
let numberOffscreen = VISIBLE_ROW_COUNT - totalVisible;
|
||||||
|
start = firstVisible - Math.floor(numberOffscreen / 2);
|
||||||
|
end = lastVisible + Math.ceil(numberOffscreen / 2);
|
||||||
|
|
||||||
|
if (start < 0) {
|
||||||
|
start = 0;
|
||||||
|
end = Math.min(VISIBLE_ROW_COUNT, filteredRowsLength);
|
||||||
|
} else if (end >= filteredRowsLength) {
|
||||||
|
end = filteredRowsLength;
|
||||||
|
start = end - VISIBLE_ROW_COUNT + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.rowOffset = start;
|
||||||
|
this.visibleRows = filteredRows.slice(start, end);
|
||||||
|
},
|
||||||
|
calculateFirstVisibleRow: function () {
|
||||||
|
return Math.floor(this.scrollable.scrollTop / this.rowHeight);
|
||||||
|
},
|
||||||
|
calculateLastVisibleRow: function () {
|
||||||
|
let bottomScroll = this.scrollable.scrollTop + this.scrollable.offsetHeight;
|
||||||
|
return Math.floor(bottomScroll / this.rowHeight);
|
||||||
|
},
|
||||||
|
updateHeaders: function () {
|
||||||
|
let headers = table.configuration.getHeaders();
|
||||||
|
|
||||||
|
Object.keys(headers).forEach((headerKey) => {
|
||||||
|
if (this.configuration.table.columns[headerKey] === false) {
|
||||||
|
delete headers[headerKey];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.headers = headers;
|
||||||
|
this.headersCount = Object.values(headers).length;
|
||||||
|
Vue.nextTick().then(this.calculateColumnWidths);
|
||||||
|
},
|
||||||
|
setSizingTableWidth: function () {
|
||||||
|
let scrollW = this.scrollable.offsetWidth - this.scrollable.clientWidth;
|
||||||
|
|
||||||
|
if (scrollW && scrollW > 0) {
|
||||||
|
this.calcTableWidth = 'calc(100% - ' + scrollW + 'px)';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
calculateColumnWidths: function () {
|
||||||
|
let columnWidths = [];
|
||||||
|
let totalWidth = 0;
|
||||||
|
let sizingRowEl = this.sizingTable.children[0];
|
||||||
|
let sizingCells = Array.from(sizingRowEl.children);
|
||||||
|
|
||||||
|
sizingCells.forEach((cell) => {
|
||||||
|
let columnWidth = cell.offsetWidth;
|
||||||
|
columnWidths.push(columnWidth + 'px');
|
||||||
|
totalWidth += columnWidth;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.columnWidths = columnWidths;
|
||||||
|
this.totalWidth = totalWidth;
|
||||||
|
},
|
||||||
|
sortBy: function (columnKey) {
|
||||||
|
// If sorting by the same column, flip the sort direction.
|
||||||
|
if (this.sortOptions.key === columnKey) {
|
||||||
|
if (this.sortOptions.direction === 'asc') {
|
||||||
|
this.sortOptions.direction = 'desc';
|
||||||
|
} else {
|
||||||
|
this.sortOptions.direction = 'asc';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.sortOptions = {
|
||||||
|
key: columnKey,
|
||||||
|
direction: 'asc'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.filteredRows.sortBy(this.sortOptions);
|
||||||
|
},
|
||||||
|
scroll: function() {
|
||||||
|
if (!processingScroll) {
|
||||||
|
processingScroll = true;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.updateVisibleRows();
|
||||||
|
this.synchronizeScrollX();
|
||||||
|
|
||||||
|
if (this.shouldSnapToBottom()) {
|
||||||
|
// If user scrolls away from bottom, disable auto-scroll.
|
||||||
|
// Auto-scroll will be re-enabled if user scrolls to bottom again.
|
||||||
|
this.autoScroll = true;
|
||||||
|
} else {
|
||||||
|
this.autoScroll = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
processingScroll = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shouldSnapToBottom: function () {
|
||||||
|
return this.scrollable.scrollTop >= (this.scrollable.scrollHeight - this.scrollable.offsetHeight - AUTO_SCROLL_TRIGGER_HEIGHT);
|
||||||
|
},
|
||||||
|
scrollToBottom: function () {
|
||||||
|
this.scrollable.scrollTop = this.totalHeight;
|
||||||
|
},
|
||||||
|
synchronizeScrollX: function () {
|
||||||
|
this.headersHolderEl.scrollLeft = this.scrollable.scrollLeft;
|
||||||
|
},
|
||||||
|
filterChanged: function (columnKey) {
|
||||||
|
table.filteredRows.setColumnFilter(columnKey, this.filters[columnKey]);
|
||||||
|
},
|
||||||
|
clearFilter: function (columnKey) {
|
||||||
|
this.filters[columnKey] = '';
|
||||||
|
table.filteredRows.setColumnFilter(columnKey, '');
|
||||||
|
},
|
||||||
|
rowsAdded: function (rows) {
|
||||||
|
let sizingRow;
|
||||||
|
if (Array.isArray(rows)) {
|
||||||
|
sizingRow = rows[0];
|
||||||
|
} else {
|
||||||
|
sizingRow = rows;
|
||||||
|
}
|
||||||
|
if (!this.sizingRows[sizingRow.objectKeyString]) {
|
||||||
|
this.sizingRows[sizingRow.objectKeyString] = sizingRow;
|
||||||
|
Vue.nextTick().then(this.calculateColumnWidths);
|
||||||
|
}
|
||||||
|
this.updateVisibleRows();
|
||||||
|
|
||||||
|
if (this.autoScroll) {
|
||||||
|
this.scrollToBottom();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
exportAsCSV: function () {
|
||||||
|
const justTheData = table.filteredRows.getRows()
|
||||||
|
.map(row => row.getFormattedDatum());
|
||||||
|
const headers = Object.keys(this.headers);
|
||||||
|
csvExporter.export(justTheData, {
|
||||||
|
filename: table.domainObject.name + '.csv',
|
||||||
|
headers: headers
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadingHistoricalData: function (loading) {
|
||||||
|
this.loading = loading;
|
||||||
|
},
|
||||||
|
calculateTableSize: function () {
|
||||||
|
this.setSizingTableWidth();
|
||||||
|
Vue.nextTick().then(this.calculateColumnWidths);
|
||||||
|
},
|
||||||
|
pollForResize: function () {
|
||||||
|
let el = this.$el;
|
||||||
|
let width = el.clientWidth;
|
||||||
|
let height = el.clientHeight;
|
||||||
|
|
||||||
|
this.resizePollHandle = setInterval(() => {
|
||||||
|
if (el.clientWidth !== width || el.clientHeight !== height) {
|
||||||
|
this.calculateTableSize();
|
||||||
|
width = el.clientWidth;
|
||||||
|
height = el.clientHeight;
|
||||||
|
}
|
||||||
|
}, RESIZE_POLL_INTERVAL);
|
||||||
|
},
|
||||||
|
updateConfiguration: function (configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.updateHeaders();
|
||||||
|
},
|
||||||
|
addObject: function () {
|
||||||
|
this.updateHeaders();
|
||||||
|
},
|
||||||
|
removeObject: function (objectIdentifier) {
|
||||||
|
let objectKeyString = openmct.objects.makeKeyString(objectIdentifier);
|
||||||
|
delete this.sizingRows[objectKeyString];
|
||||||
|
this.updateHeaders();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function () {
|
||||||
|
this.filterChanged = _.debounce(this.filterChanged, 500);
|
||||||
|
},
|
||||||
|
mounted: function () {
|
||||||
|
table.on('object-added', this.addObject);
|
||||||
|
table.on('object-removed', this.removeObject);
|
||||||
|
table.on('loading-historical-data', this.loadingHistoricalData);
|
||||||
|
|
||||||
|
table.filteredRows.on('add', this.rowsAdded);
|
||||||
|
table.filteredRows.on('remove', this.updateVisibleRows);
|
||||||
|
table.filteredRows.on('sort', this.updateVisibleRows);
|
||||||
|
table.filteredRows.on('filter', this.updateVisibleRows);
|
||||||
|
|
||||||
|
//Default sort
|
||||||
|
this.sortOptions = table.filteredRows.sortBy();
|
||||||
|
this.scrollable = this.$el.querySelector('.t-scrolling');
|
||||||
|
this.sizingTable = this.$el.querySelector('.js-sizing-table');
|
||||||
|
this.headersHolderEl = this.$el.querySelector('.mct-table-headers-w');
|
||||||
|
|
||||||
|
observationUnlistener = openmct.objects.observe(domainObject, 'configuration', this.updateConfiguration);
|
||||||
|
|
||||||
|
this.calculateTableSize();
|
||||||
|
this.pollForResize();
|
||||||
|
},
|
||||||
|
destroyed: function () {
|
||||||
|
table.off('object-added', this.addObject);
|
||||||
|
table.off('object-removed', this.removeObject);
|
||||||
|
table.off('loading-historical-data', this.loadingHistoricalData);
|
||||||
|
|
||||||
|
table.filteredRows.off('add', this.updateVisibleRows);
|
||||||
|
table.filteredRows.off('remove', this.updateVisibleRows);
|
||||||
|
table.filteredRows.off('sort', this.updateVisibleRows);
|
||||||
|
table.filteredRows.off('filter', this.updateVisibleRows);
|
||||||
|
clearInterval(this.resizePollHandle);
|
||||||
|
|
||||||
|
observationUnlistener();
|
||||||
|
|
||||||
|
table.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
94
src/plugins/telemetryTable/TelemetryTableConfiguration.js
Normal file
94
src/plugins/telemetryTable/TelemetryTableConfiguration.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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([
|
||||||
|
'EventEmitter',
|
||||||
|
'./TelemetryTableColumn',
|
||||||
|
], function (EventEmitter, TelemetryTableColumn) {
|
||||||
|
|
||||||
|
class TelemetryTableConfiguration extends EventEmitter{
|
||||||
|
constructor(domainObject, openmct) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.domainObject = domainObject;
|
||||||
|
this.openmct = openmct;
|
||||||
|
this.columns = {};
|
||||||
|
|
||||||
|
this.addColumnsForObject = this.addColumnsForObject.bind(this);
|
||||||
|
this.removeColumnsForObject = this.removeColumnsForObject.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
addColumnsForAllObjects(objects) {
|
||||||
|
objects.forEach(composee => this.addColumnsForObject(composee, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
addColumnsForObject(telemetryObject) {
|
||||||
|
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
|
||||||
|
let objectKeyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||||
|
this.columns[objectKeyString] = [];
|
||||||
|
|
||||||
|
metadataValues.forEach(metadatum => {
|
||||||
|
let column = new TelemetryTableColumn(this.openmct, metadatum);
|
||||||
|
this.columns[objectKeyString].push(column);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeColumnsForObject(objectIdentifier) {
|
||||||
|
let objectKeyString = this.openmct.objects.makeKeyString(objectIdentifier);
|
||||||
|
let columnsToRemove = this.columns[objectKeyString];
|
||||||
|
|
||||||
|
delete this.columns[objectKeyString];
|
||||||
|
columnsToRemove.forEach((column) => {
|
||||||
|
//There may be more than one column with the same key (eg. time system columns)
|
||||||
|
if (!this.hasColumnWithKey(column.key)) {
|
||||||
|
// If there are no more columns with this key, delete any configuration, and trigger
|
||||||
|
// a column refresh.
|
||||||
|
delete this.domainObject.configuration.table.columns[column.getKey()];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hasColumnWithKey(columnKey) {
|
||||||
|
return _.flatten(Object.values(this.columns))
|
||||||
|
.findIndex(column => column.getKey() === columnKey) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
getColumns() {
|
||||||
|
return this.columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHeaders() {
|
||||||
|
let flattenedColumns = _.flatten(Object.values(this.columns));
|
||||||
|
let headers = _.uniq(flattenedColumns, false, column => column.getKey())
|
||||||
|
.reduce(fromColumnsToHeadersMap, {});
|
||||||
|
|
||||||
|
function fromColumnsToHeadersMap(headersMap, column){
|
||||||
|
headersMap[column.getKey()] = column.getTitle();
|
||||||
|
return headersMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TelemetryTableConfiguration;
|
||||||
|
});
|
82
src/plugins/telemetryTable/TelemetryTableRow.js
Normal file
82
src/plugins/telemetryTable/TelemetryTableRow.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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([], function () {
|
||||||
|
class TelemetryTableRow {
|
||||||
|
constructor(datum, columns, objectKeyString, limitEvaluator) {
|
||||||
|
this.columns = columns;
|
||||||
|
|
||||||
|
this.datum = createNormalizedDatum(datum, columns);
|
||||||
|
this.limitEvaluator = limitEvaluator;
|
||||||
|
this.objectKeyString = objectKeyString;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFormattedDatum() {
|
||||||
|
return Object.values(this.columns)
|
||||||
|
.reduce((formattedDatum, column) => {
|
||||||
|
formattedDatum[column.getKey()] = this.getFormattedValue(column.getKey());
|
||||||
|
return formattedDatum;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
getFormattedValue(key) {
|
||||||
|
let column = this.columns[key];
|
||||||
|
return column.getFormattedValue(this.datum[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRowLimitClass() {
|
||||||
|
if (!this.rowLimitClass) {
|
||||||
|
let limitEvaluation = this.limitEvaluator.evaluate(this.datum);
|
||||||
|
this.rowLimitClass = limitEvaluation && limitEvaluation.cssClass;
|
||||||
|
}
|
||||||
|
return this.rowLimitClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCellLimitClasses() {
|
||||||
|
if (!this.cellLimitClasses) {
|
||||||
|
this.cellLimitClasses = Object.values(this.columns).reduce((alarmStateMap, column) => {
|
||||||
|
let limitEvaluation = this.limitEvaluator.evaluate(this.datum, column.getMetadatum());
|
||||||
|
alarmStateMap[column.getKey()] = limitEvaluation && limitEvaluation.cssClass;
|
||||||
|
|
||||||
|
return alarmStateMap;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
return this.cellLimitClasses;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize the structure of datums to assist sorting and merging of columns.
|
||||||
|
* Maps all sources to keys.
|
||||||
|
* @private
|
||||||
|
* @param {*} telemetryDatum
|
||||||
|
* @param {*} metadataValues
|
||||||
|
*/
|
||||||
|
function createNormalizedDatum(datum, columns) {
|
||||||
|
return Object.values(columns).reduce((normalizedDatum, column) => {
|
||||||
|
normalizedDatum[column.getKey()] = column.getRawValue(datum);
|
||||||
|
return normalizedDatum;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
return TelemetryTableRow;
|
||||||
|
});
|
90
src/plugins/telemetryTable/TelemetryTableRowComponent.js
Normal file
90
src/plugins/telemetryTable/TelemetryTableRowComponent.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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([
|
||||||
|
'text!./telemetry-table-row.html',
|
||||||
|
],function (
|
||||||
|
TelemetryTableRowTemplate
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
template: TelemetryTableRowTemplate,
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
rowTop: (this.rowOffset + this.rowIndex) * this.rowHeight + 'px',
|
||||||
|
formattedRow: this.row.getFormattedDatum(),
|
||||||
|
rowLimitClass: this.row.getRowLimitClass(),
|
||||||
|
cellLimitClasses: this.row.getCellLimitClasses()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
headers: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
columnWidths: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
rowIndex: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
rowOffset: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
rowHeight: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
configuration: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
calculateRowTop: function (rowOffset) {
|
||||||
|
this.rowTop = (rowOffset + this.rowIndex) * this.rowHeight + 'px';
|
||||||
|
},
|
||||||
|
formatRow: function (row) {
|
||||||
|
this.formattedRow = row.getFormattedDatum();
|
||||||
|
this.rowLimitClass = row.getRowLimitClass();
|
||||||
|
this.cellLimitClasses = row.getCellLimitClasses();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
rowOffset: 'calculateRowTop',
|
||||||
|
row: {
|
||||||
|
handler: 'formatRow',
|
||||||
|
deep: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
37
src/plugins/telemetryTable/TelemetryTableType.js
Normal file
37
src/plugins/telemetryTable/TelemetryTableType.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(function () {
|
||||||
|
function TelemetryTableType() {
|
||||||
|
return {
|
||||||
|
name: 'Vue Telemetry Table',
|
||||||
|
description: 'Display telemetry values for the current time bounds in tabular form. Supports filtering and sorting.',
|
||||||
|
creatable: true,
|
||||||
|
cssClass: 'icon-tabular-realtime',
|
||||||
|
initialize: function (domainObject) {
|
||||||
|
domainObject.composition = [];
|
||||||
|
domainObject.configuration = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TelemetryTableType;
|
||||||
|
});
|
52
src/plugins/telemetryTable/TelemetryTableViewProvider.js
Normal file
52
src/plugins/telemetryTable/TelemetryTableViewProvider.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(['./TelemetryTableComponent'], function (TelemetryTableComponent) {
|
||||||
|
function TelemetryTableViewProvider(openmct) {
|
||||||
|
return {
|
||||||
|
key: 'vue-table',
|
||||||
|
name: 'Telemetry Table',
|
||||||
|
editable: true,
|
||||||
|
canView: function (domainObject) {
|
||||||
|
return domainObject.type === 'vue-table';
|
||||||
|
},
|
||||||
|
view: function (domainObject) {
|
||||||
|
let component;
|
||||||
|
return {
|
||||||
|
show: function (element) {
|
||||||
|
component = TelemetryTableComponent(domainObject, openmct);
|
||||||
|
element.appendChild(component.$mount().$el);
|
||||||
|
},
|
||||||
|
destroy: function (element) {
|
||||||
|
component.$destroy();
|
||||||
|
element.removeChild(component.$el);
|
||||||
|
component = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
priority: function () {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TelemetryTableViewProvider;
|
||||||
|
});
|
@ -0,0 +1,139 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(
|
||||||
|
[
|
||||||
|
'lodash',
|
||||||
|
'./SortedTableRowCollection'
|
||||||
|
],
|
||||||
|
function (
|
||||||
|
_,
|
||||||
|
SortedTableRowCollection
|
||||||
|
) {
|
||||||
|
|
||||||
|
class BoundedTableRowCollection extends SortedTableRowCollection {
|
||||||
|
constructor (openmct) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.futureBuffer = new SortedTableRowCollection();
|
||||||
|
this.openmct = openmct;
|
||||||
|
|
||||||
|
this.sortByTimeSystem = this.sortByTimeSystem.bind(this)
|
||||||
|
this.bounds = this.bounds.bind(this)
|
||||||
|
|
||||||
|
this.sortByTimeSystem(openmct.time.timeSystem());
|
||||||
|
openmct.time.on('timeSystem', this.sortByTimeSystem);
|
||||||
|
|
||||||
|
this.lastBounds = openmct.time.bounds();
|
||||||
|
openmct.time.on('bounds', this.bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
addOne (item) {
|
||||||
|
// Insert into either in-bounds array, or the future buffer.
|
||||||
|
// Data in the future buffer will be re-evaluated for possible
|
||||||
|
// insertion on next bounds change
|
||||||
|
let beforeStartOfBounds = item.datum[this.sortOptions.key] < this.lastBounds.start;
|
||||||
|
let afterEndOfBounds = item.datum[this.sortOptions.key] > this.lastBounds.end;
|
||||||
|
|
||||||
|
if (!afterEndOfBounds && !beforeStartOfBounds) {
|
||||||
|
return super.addOne(item);
|
||||||
|
} else if (afterEndOfBounds) {
|
||||||
|
this.futureBuffer.addOne(item);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sortByTimeSystem(timeSystem) {
|
||||||
|
this.sortBy({key: timeSystem.key, direction: 'asc'});
|
||||||
|
this.futureBuffer.sortBy({key: timeSystem.key, direction: 'asc'});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is optimized for ticking - it assumes that start and end
|
||||||
|
* bounds will only increase and as such this cannot be used for decreasing
|
||||||
|
* bounds changes.
|
||||||
|
*
|
||||||
|
* An implication of this is that data will not be discarded that exceeds
|
||||||
|
* the given end bounds. For arbitrary bounds changes, it's assumed that
|
||||||
|
* a telemetry requery is performed anyway, and the collection is cleared
|
||||||
|
* and repopulated.
|
||||||
|
*
|
||||||
|
* @fires TelemetryCollection#added
|
||||||
|
* @fires TelemetryCollection#discarded
|
||||||
|
* @param bounds
|
||||||
|
*/
|
||||||
|
bounds (bounds) {
|
||||||
|
let startChanged = this.lastBounds.start !== bounds.start;
|
||||||
|
let endChanged = this.lastBounds.end !== bounds.end;
|
||||||
|
|
||||||
|
let startIndex = 0;
|
||||||
|
let endIndex = 0;
|
||||||
|
|
||||||
|
let discarded = [];
|
||||||
|
let added = [];
|
||||||
|
let testValue = {
|
||||||
|
datum: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.lastBounds = bounds;
|
||||||
|
|
||||||
|
if (startChanged) {
|
||||||
|
testValue.datum[this.sortOptions.key] = bounds.start;
|
||||||
|
// Calculate the new index of the first item within the bounds
|
||||||
|
startIndex = this.sortedIndex(this.rows, testValue);
|
||||||
|
discarded = this.rows.splice(0, startIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endChanged) {
|
||||||
|
testValue.datum[this.sortOptions.key] = bounds.end;
|
||||||
|
// Calculate the new index of the last item in bounds
|
||||||
|
endIndex = this.sortedLastIndex(this.futureBuffer.rows, testValue);
|
||||||
|
added = this.futureBuffer.rows.splice(0, endIndex);
|
||||||
|
added.forEach((datum) => this.rows.push(datum));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discarded && discarded.length > 0) {
|
||||||
|
/**
|
||||||
|
* A `discarded` event is emitted when telemetry data fall out of
|
||||||
|
* bounds due to a bounds change event
|
||||||
|
* @type {object[]} discarded the telemetry data
|
||||||
|
* discarded as a result of the bounds change
|
||||||
|
*/
|
||||||
|
this.emit('remove', discarded);
|
||||||
|
}
|
||||||
|
if (added && added.length > 0) {
|
||||||
|
/**
|
||||||
|
* An `added` event is emitted when a bounds change results in
|
||||||
|
* received telemetry falling within the new bounds.
|
||||||
|
* @type {object[]} added the telemetry data that is now within bounds
|
||||||
|
*/
|
||||||
|
this.emit('add', added);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.openmct.time.off('timeSystem', this.sortByTimeSystem);
|
||||||
|
this.openmct.time.off('bounds', this.bounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return BoundedTableRowCollection;
|
||||||
|
});
|
@ -0,0 +1,112 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(
|
||||||
|
[
|
||||||
|
'./SortedTableRowCollection'
|
||||||
|
],
|
||||||
|
function (
|
||||||
|
SortedTableRowCollection
|
||||||
|
) {
|
||||||
|
class FilteredTableRowCollection extends SortedTableRowCollection {
|
||||||
|
constructor(masterCollection) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.masterCollection = masterCollection;
|
||||||
|
this.columnFilters = {};
|
||||||
|
|
||||||
|
//Synchronize with master collection
|
||||||
|
this.masterCollection.on('add', this.add);
|
||||||
|
this.masterCollection.on('remove', this.remove);
|
||||||
|
|
||||||
|
//Default to master collection's sort options
|
||||||
|
this.sortOptions = masterCollection.sortBy();
|
||||||
|
}
|
||||||
|
|
||||||
|
setColumnFilter(columnKey, filter) {
|
||||||
|
filter = filter.trim().toLowerCase();
|
||||||
|
|
||||||
|
let rowsToFilter = this.getRowsToFilter(columnKey, filter);
|
||||||
|
if (filter.length === 0) {
|
||||||
|
delete this.columnFilters[columnKey];
|
||||||
|
} else {
|
||||||
|
this.columnFilters[columnKey] = filter;
|
||||||
|
}
|
||||||
|
this.rows = rowsToFilter.filter(this.matchesFilters, this);
|
||||||
|
this.emit('filter');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
getRowsToFilter(columnKey, filter) {
|
||||||
|
if (this.isSubsetOfCurrentFilter(columnKey, filter)) {
|
||||||
|
return this.getRows();
|
||||||
|
} else {
|
||||||
|
return this.masterCollection.getRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
isSubsetOfCurrentFilter(columnKey, filter) {
|
||||||
|
return this.columnFilters[columnKey] &&
|
||||||
|
filter.startsWith(this.columnFilters[columnKey]) &&
|
||||||
|
// startsWith check will otherwise fail when filter cleared
|
||||||
|
// because anyString.startsWith('') === true
|
||||||
|
filter !== '';
|
||||||
|
}
|
||||||
|
|
||||||
|
addOne(row) {
|
||||||
|
return this.matchesFilters(row) && super.addOne(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
matchesFilters(row) {
|
||||||
|
let doesMatchFilters = true;
|
||||||
|
for (const key in this.columnFilters) {
|
||||||
|
if (!this.rowHasColumn(row, key)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
let formattedValue = row.getFormattedValue(key).toLowerCase();
|
||||||
|
doesMatchFilters = doesMatchFilters &&
|
||||||
|
formattedValue.indexOf(this.columnFilters[key]) !== -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return doesMatchFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
rowHasColumn(row, key) {
|
||||||
|
return row.columns.hasOwnProperty(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.masterCollection.off('add', this.add);
|
||||||
|
this.masterCollection.off('remove', this.remove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FilteredTableRowCollection;
|
||||||
|
});
|
@ -0,0 +1,212 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(
|
||||||
|
[
|
||||||
|
'lodash',
|
||||||
|
'EventEmitter'
|
||||||
|
],
|
||||||
|
function (
|
||||||
|
_,
|
||||||
|
EventEmitter
|
||||||
|
) {
|
||||||
|
const LESS_THAN = -1;
|
||||||
|
const EQUAL = 0;
|
||||||
|
const GREATER_THAN = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
class SortedTableRowCollection extends EventEmitter {
|
||||||
|
constructor () {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.dupeCheck = false;
|
||||||
|
this.rows = [];
|
||||||
|
|
||||||
|
this.add = this.add.bind(this);
|
||||||
|
this.remove = this.remove.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a datum or array of data to this telemetry collection
|
||||||
|
* @fires TelemetryCollection#added
|
||||||
|
* @param {object | object[]} rows
|
||||||
|
*/
|
||||||
|
add(rows) {
|
||||||
|
if (Array.isArray(rows)) {
|
||||||
|
this.dupeCheck = false;
|
||||||
|
|
||||||
|
let rowsAdded = rows.filter(this.addOne, this);
|
||||||
|
if (rowsAdded.length > 0) {
|
||||||
|
this.emit('add', rowsAdded);
|
||||||
|
}
|
||||||
|
this.dupeCheck = true;
|
||||||
|
} else {
|
||||||
|
let wasAdded = this.addOne(rows);
|
||||||
|
if (wasAdded) {
|
||||||
|
this.emit('add', rows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
addOne(row) {
|
||||||
|
if (this.sortOptions === undefined) {
|
||||||
|
throw 'Please specify sort options';
|
||||||
|
}
|
||||||
|
|
||||||
|
let isDuplicate = false;
|
||||||
|
|
||||||
|
// Going to check for duplicates. Bound the search problem to
|
||||||
|
// items around the given time. Use sortedIndex because it
|
||||||
|
// employs a binary search which is O(log n). Can use binary search
|
||||||
|
// based on time stamp because the array is guaranteed ordered due
|
||||||
|
// to sorted insertion.
|
||||||
|
let startIx = this.sortedIndex(this.rows, row);
|
||||||
|
let endIx = undefined;
|
||||||
|
|
||||||
|
if (this.dupeCheck && startIx !== this.rows.length) {
|
||||||
|
endIx = _.sortedLastIndex(this.rows, row);
|
||||||
|
|
||||||
|
// Create an array of potential dupes, based on having the
|
||||||
|
// same time stamp
|
||||||
|
let potentialDupes = this.rows.slice(startIx, endIx + 1);
|
||||||
|
// Search potential dupes for exact dupe
|
||||||
|
isDuplicate = _.findIndex(potentialDupes, _.isEqual.bind(undefined, row)) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDuplicate) {
|
||||||
|
this.rows.splice(endIx || startIx, 0, row);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sortedLastIndex(rows, testRow) {
|
||||||
|
return this.sortedIndex(rows, testRow, _.sortedLastIndex);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Finds the correct insertion point for the given row.
|
||||||
|
* Leverages lodash's `sortedIndex` function which implements a binary search.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
sortedIndex(rows, testRow, lodashFunction) {
|
||||||
|
const sortOptionsKey = this.sortOptions.key;
|
||||||
|
lodashFunction = lodashFunction || _.sortedIndex;
|
||||||
|
|
||||||
|
if (this.sortOptions.direction === 'asc') {
|
||||||
|
return lodashFunction(rows, testRow, (thisRow) => {
|
||||||
|
return thisRow.datum[sortOptionsKey];
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const testRowValue = testRow.datum[this.sortOptions.key];
|
||||||
|
// Use a custom comparison function to support descending sort.
|
||||||
|
return lodashFunction(rows, testRow, (thisRow) => {
|
||||||
|
const thisRowValue = thisRow.datum[sortOptionsKey];
|
||||||
|
if (testRowValue === thisRowValue) {
|
||||||
|
return EQUAL;
|
||||||
|
} else if (testRowValue < thisRowValue) {
|
||||||
|
return LESS_THAN;
|
||||||
|
} else {
|
||||||
|
return GREATER_THAN;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts the telemetry collection based on the provided sort field
|
||||||
|
* specifier. Subsequent inserts are sorted to maintain specified sport
|
||||||
|
* order.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // First build some mock telemetry for the purpose of an example
|
||||||
|
* let now = Date.now();
|
||||||
|
* let telemetry = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(function (value) {
|
||||||
|
* return {
|
||||||
|
* // define an object property to demonstrate nested paths
|
||||||
|
* timestamp: {
|
||||||
|
* ms: now - value * 1000,
|
||||||
|
* text:
|
||||||
|
* },
|
||||||
|
* value: value
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* let collection = new TelemetryCollection();
|
||||||
|
*
|
||||||
|
* collection.add(telemetry);
|
||||||
|
*
|
||||||
|
* // Sort by telemetry value
|
||||||
|
* collection.sortBy({
|
||||||
|
* key: 'value', direction: 'asc'
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Sort by ms since epoch
|
||||||
|
* collection.sort({
|
||||||
|
* key: 'timestamp.ms',
|
||||||
|
* direction: 'asc'
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Sort by 'text' attribute, descending
|
||||||
|
* collection.sort("timestamp.text");
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {object} sortOptions An object specifying a sort key, and direction.
|
||||||
|
*/
|
||||||
|
sortBy(sortOptions) {
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
this.sortOptions = sortOptions;
|
||||||
|
this.rows = _.sortByOrder(this.rows, 'datum.' + sortOptions.key, sortOptions.direction);
|
||||||
|
this.emit('sort');
|
||||||
|
}
|
||||||
|
// Return duplicate to avoid direct modification of underlying object
|
||||||
|
return Object.assign({}, this.sortOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAllRowsForObject(objectKeyString) {
|
||||||
|
let removed = [];
|
||||||
|
this.rows = this.rows.filter(row => {
|
||||||
|
if (row.objectKeyString === objectKeyString) {
|
||||||
|
removed.push(row);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
this.emit('remove', removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(removedRows) {
|
||||||
|
this.rows = this.rows.filter(row => {
|
||||||
|
return removedRows.indexOf(row) === -1;
|
||||||
|
});
|
||||||
|
this.emit('remove', removedRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRows () {
|
||||||
|
return this.rows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SortedTableRowCollection;
|
||||||
|
});
|
39
src/plugins/telemetryTable/plugin.js
Normal file
39
src/plugins/telemetryTable/plugin.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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([
|
||||||
|
'./TelemetryTableViewProvider',
|
||||||
|
'./TableConfigurationViewProvider',
|
||||||
|
'./TelemetryTableType'
|
||||||
|
], function (
|
||||||
|
TelemetryTableViewProvider,
|
||||||
|
TableConfigurationViewProvider,
|
||||||
|
TelemetryTableType
|
||||||
|
) {
|
||||||
|
return function plugin() {
|
||||||
|
return function install(openmct) {
|
||||||
|
openmct.objectViews.addProvider(new TelemetryTableViewProvider(openmct));
|
||||||
|
openmct.inspectorViews.addProvider(new TableConfigurationViewProvider(openmct));
|
||||||
|
openmct.types.addType('vue-table', TelemetryTableType());
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
11
src/plugins/telemetryTable/table-configuration.html
Normal file
11
src/plugins/telemetryTable/table-configuration.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<div class="grid-properties">
|
||||||
|
<!--form class="form" -->
|
||||||
|
<ul class="l-inspector-part">
|
||||||
|
<h2>Table Columns</h2>
|
||||||
|
<li class="grid-row" v-for="(title, key) in headers">
|
||||||
|
<div class="grid-cell label" title="Show or Hide Column"><label :for="key + 'ColumnControl'">{{title}}</label></div>
|
||||||
|
<div class="grid-cell value"><input type="checkbox" :id="key + 'ColumnControl'" :checked="configuration.table.columns[key] !== false" @change="toggleColumn(key)"></div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<!--/form -->
|
||||||
|
</div>
|
6
src/plugins/telemetryTable/telemetry-table-row.html
Normal file
6
src/plugins/telemetryTable/telemetry-table-row.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<tr :style="{ top: rowTop }" :class="rowLimitClass">
|
||||||
|
<td v-for="(title, key, headerIndex) in headers"
|
||||||
|
:style="{ width: columnWidths[headerIndex], 'max-width': columnWidths[headerIndex]}"
|
||||||
|
:title="formattedRow[key]"
|
||||||
|
:class="cellLimitClasses[key]">{{formattedRow[key]}}</td>
|
||||||
|
</tr>
|
60
src/plugins/telemetryTable/telemetry-table.html
Normal file
60
src/plugins/telemetryTable/telemetry-table.html
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<div class="tabular-holder l-sticky-headers has-control-bar" :class="{'loading': loading}">
|
||||||
|
<div class="l-control-bar">
|
||||||
|
<a class="s-button t-export icon-download labeled"
|
||||||
|
v-on:click="exportAsCSV()"
|
||||||
|
title="Export This View's Data">
|
||||||
|
Export As CSV
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<!-- Headers table -->
|
||||||
|
<div class="mct-table-headers-w">
|
||||||
|
<table class="mct-table l-tabular-headers filterable" :style="{ 'max-width': totalWidth + 'px'}">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th v-for="(title, key, headerIndex) in headers"
|
||||||
|
v-on:click="sortBy(key)"
|
||||||
|
:class="['sortable', sortOptions.key === key ? 'sort' : '', sortOptions.direction].join(' ')"
|
||||||
|
:style="{ width: columnWidths[headerIndex], 'max-width': columnWidths[headerIndex]}">{{title}}</th>
|
||||||
|
</tr>
|
||||||
|
<tr class="s-filters">
|
||||||
|
<th v-for="(title, key, headerIndex) in headers"
|
||||||
|
:style="{
|
||||||
|
width: columnWidths[headerIndex],
|
||||||
|
'max-width': columnWidths[headerIndex],
|
||||||
|
}">
|
||||||
|
<div class="holder l-filter flex-elem grows" :class="{active: filters[key]}">
|
||||||
|
<input type="text" v-model="filters[key]" v-on:input="filterChanged(key)" />
|
||||||
|
<a class="clear-icon clear-input icon-x-in-circle" :class="{show: filters[key]}" @click="clearFilter(key)"></a>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- Content table -->
|
||||||
|
<div v-on:scroll="scroll()" class="l-tabular-body t-scrolling vscroll--persist">
|
||||||
|
<table class="mct-table js-telemetry-table" :style="{ height: totalHeight + 'px', 'max-width': totalWidth + 'px'}">
|
||||||
|
<tbody>
|
||||||
|
<telemetry-table-row v-for="(row, rowIndex) in visibleRows"
|
||||||
|
:headers="headers"
|
||||||
|
:columnWidths="columnWidths"
|
||||||
|
:rowIndex="rowIndex"
|
||||||
|
:rowOffset="rowOffset"
|
||||||
|
:rowHeight="rowHeight"
|
||||||
|
:row="row"
|
||||||
|
>
|
||||||
|
</telemetry-table-row>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- Sizing table -->
|
||||||
|
<table class="mct-sizing-table t-sizing-table js-sizing-table" :style="{width: calcTableWidth}">
|
||||||
|
<tr>
|
||||||
|
<th v-for="(title, key, headerIndex) in headers">{{title}}</th>
|
||||||
|
</tr>
|
||||||
|
<telemetry-table-row v-for="(sizingRowData, objectKeyString) in sizingRows"
|
||||||
|
:headers="headers"
|
||||||
|
:row="sizingRowData">
|
||||||
|
</telemetry-table-row>
|
||||||
|
</table>
|
||||||
|
</div>
|
Reference in New Issue
Block a user