Merge branch 'master' into open671

Conflicts:
	main.js
	platform/commonUI/edit/src/policies/EditableMovePolicy.js
	platform/commonUI/general/src/directives/MCTTree.js
	platform/commonUI/general/src/ui/ToggleView.js
	platform/core/src/actions/ActionCapability.js
	platform/core/test/models/CachingModelDecoratorSpec.js
	platform/core/test/services/InstantiateSpec.js
	platform/features/events/bundle.js
	platform/features/events/src/DomainColumn.js
	platform/features/events/src/EventListController.js
	platform/features/events/src/EventListPopulator.js
	platform/features/events/src/RangeColumn.js
	platform/features/events/src/directives/MCTDataTable.js
	platform/features/events/src/policies/MessagesViewPolicy.js
	platform/features/events/test/DomainColumnSpec.js
	platform/features/events/test/EventListControllerSpec.js
	platform/features/events/test/EventListPopulatorSpec.js
	platform/features/events/test/RangeColumnSpec.js
	platform/features/events/test/policies/MessagesViewPolicySpec.js
	platform/features/rtevents/bundle.js
	platform/features/rtevents/src/DomainColumn.js
	platform/features/rtevents/src/RTEventListController.js
	platform/features/rtevents/src/RangeColumn.js
	platform/features/rtevents/src/directives/MCTRTDataTable.js
	platform/features/rtevents/src/policies/RTMessagesViewPolicy.js
	platform/features/rtevents/test/DomainColumnSpec.js
	platform/features/rtevents/test/RTEventListControllerSpec.js
	platform/features/rtevents/test/RangeColumnSpec.js
	platform/features/rtevents/test/policies/RTMessagesViewPolicySpec.js
	platform/features/rtscrolling/bundle.js
	platform/features/rtscrolling/src/DomainColumn.js
	platform/features/rtscrolling/src/NameColumn.js
	platform/features/rtscrolling/src/RTScrollingListController.js
	platform/features/rtscrolling/src/RangeColumn.js
	platform/features/scrolling/src/DomainColumn.js
	platform/features/scrolling/src/RangeColumn.js
	platform/features/scrolling/src/ScrollingListController.js
	platform/features/scrolling/src/ScrollingListPopulator.js
	platform/features/scrolling/test/DomainColumnSpec.js
	platform/features/scrolling/test/RangeColumnSpec.js
	platform/features/scrolling/test/ScrollingListControllerSpec.js
	platform/features/scrolling/test/ScrollingListPopulatorSpec.js
	platform/features/table/src/directives/MCTTable.js
	platform/features/table/test/controllers/TelemetryTableControllerSpec.js
	platform/representation/src/gestures/DropGesture.js
	platform/telemetry/src/TelemetryFormatter.js
	test-main.js
This commit is contained in:
Victor Woeltjen
2016-04-08 16:05:04 -07:00
179 changed files with 6732 additions and 19586 deletions

View File

@@ -155,7 +155,9 @@ define([
{
"key": "clock",
"name": "Clock",
"glyph": "C",
"glyph": "\u0043",
"description": "A UTC-based clock that supports a variety of display formats. Clocks can be added to Display Layouts.",
"priority": 101,
"features": [
"creation"
],
@@ -180,7 +182,8 @@ define([
"value": "hh:mm:ss",
"name": "hh:mm:ss"
}
]
],
"cssclass": "l-inline"
},
{
"control": "select",
@@ -193,7 +196,8 @@ define([
"value": "clock24",
"name": "24hr"
}
]
],
"cssclass": "l-inline"
}
]
}
@@ -208,7 +212,9 @@ define([
{
"key": "timer",
"name": "Timer",
"glyph": "õ",
"glyph": "\u00f5",
"description": "A timer that counts up or down to a datetime. Timers can be started, stopped and reset whenever needed, and support a variety of display formats. Each Timer displays the same value to all users. Timers can be added to Display Layouts.",
"priority": 100,
"features": [
"creation"
],
@@ -221,6 +227,7 @@ define([
{
"key": "timerFormat",
"control": "select",
"name": "Display Format",
"options": [
{
"value": "long",

View File

@@ -1,81 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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([
"./src/EventListController",
"./src/directives/MCTDataTable",
"./src/policies/MessagesViewPolicy",
"text!./res/templates/messages.html",
'legacyRegistry'
], function (
EventListController,
MCTDataTable,
MessagesViewPolicy,
messagesTemplate,
legacyRegistry
) {
legacyRegistry.register("platform/features/events", {
"name": "Event Messages",
"description": "List of time-ordered event messages",
"extensions": {
"views": [
{
"key": "messages",
"name": "Messages",
"glyph": "5",
"description": "Scrolling list of messages.",
"template": messagesTemplate,
"needs": [
"telemetry"
],
"delegation": true
}
],
"controllers": [
{
"key": "EventListController",
"implementation": EventListController,
"depends": [
"$scope",
"telemetryFormatter"
]
}
],
"directives": [
{
"key": "mctDataTable",
"implementation": MCTDataTable,
"depends": [
"$window"
]
}
],
"policies": [
{
"category": "view",
"implementation": MessagesViewPolicy
}
]
}
});
});

View File

@@ -1,37 +0,0 @@
<!--
Open MCT Web, Copyright (c) 2014-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web 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 Web 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.
-->
<table class="tabular">
<thead>
<tr>
<th ng-repeat="header in headers">
{{header}}
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="cell in row">
{{cell}}
</td>
</tr>
</tbody>
</table>

View File

@@ -1,29 +0,0 @@
<!--
Open MCT Web, Copyright (c) 2014-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web 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 Web 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.
-->
<div class="w1" ng-controller="TelemetryController as telemetry">
<div class="w2"
ng-controller="EventListController">
<mct-data-table headers="headers" rows="rows" ascending-scroll="true"></mct-data-table>
</div>
</div>

View File

@@ -1,61 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
/**
* A column which will report telemetry domain values
* (typically, timestamps.) Used by the ScrollingListController.
*
* @memberof platform/features/events
* @constructor
* @implements {platform/features/events.EventsColumn}
* @param domainMetadata an object with the machine- and human-
* readable names for this domain (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function DomainColumn(domainMetadata, telemetryFormatter) {
this.domainMetadata = domainMetadata;
this.telemetryFormatter = telemetryFormatter;
}
DomainColumn.prototype.getTitle = function () {
return this.domainMetadata.name;
};
DomainColumn.prototype.getValue = function (domainObject, data, index) {
var domainKey = this.domainMetadata.key;
return this.telemetryFormatter.formatDomainValue(
data.getDomainValue(index, domainKey)
);
};
return DomainColumn;
}
);

View File

@@ -1,162 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*
* Module defining EventListController.
* Created by chacskaylo on 06/18/2015.
* Modified by shale on 06/23/2015.
*/
/**
* This bundle implements the "Events" view of string telemetry.
* @namespace platform/features/events
*/
define(
["./DomainColumn", "./RangeColumn", "./EventListPopulator"],
function (DomainColumn, RangeColumn, EventListPopulator) {
var ROW_COUNT = 100;
/**
* The EventListController is responsible for populating
* the contents of the event list view.
* @memberof platform/features/events
* @constructor
*/
function EventListController($scope, formatter) {
var populator;
// Get a set of populated, ready-to-display rows for the
// latest data values.
function getRows(telemetry) {
var datas = telemetry.getResponse(),
objects = telemetry.getTelemetryObjects();
return populator.getRows(datas, objects, ROW_COUNT);
}
// Update the contents
function updateRows() {
var telemetry = $scope.telemetry;
$scope.rows = telemetry ? getRows(telemetry) : [];
}
// Set up columns based on telemetry metadata. This will
// include one column for each domain and range type, as
// well as a column for the domain object name.
function setupColumns(telemetry) {
var domainKeys = {},
rangeKeys = {},
columns = [],
metadata;
// Add a domain to the set of columns, if a domain
// with the same key has not yet been inclued.
function addDomain(domain) {
var key = domain.key;
if (key && !domainKeys[key]) {
domainKeys[key] = true;
columns.push(new DomainColumn(domain, formatter));
}
}
// Add a range col to the set of columns, if a range
// with the same key has not yet been included.
function addRange(range) {
var key = range.key;
if (key && !rangeKeys[key]) {
rangeKeys[key] = true;
columns.push(new RangeColumn(range, formatter));
}
}
// We cannot proceed if the telemetry controller
// is not available; clear all rows/columns.
if (!telemetry) {
columns = [];
$scope.rows = [];
$scope.headers = [];
return;
}
// Add domain, range, event msg columns
metadata = telemetry.getMetadata();
(metadata || []).forEach(function (metadata) {
(metadata.domains || []).forEach(addDomain);
});
(metadata || []).forEach(function (metadata) {
(metadata.ranges || []).forEach(addRange);
});
// Add default domain and range columns if none
// were described in metadata.
if (Object.keys(domainKeys).length < 1) {
columns.push(new DomainColumn({name: "Time"}, formatter));
}
if (Object.keys(rangeKeys).length < 1) {
columns.push(new RangeColumn({name: "Message"}, formatter));
}
// We have all columns now; use them to initializer
// the populator, which will use them to generate
// actual rows and headers.
populator = new EventListPopulator(columns);
// Initialize headers
$scope.headers = populator.getHeaders();
// Fill in the contents of the rows.
updateRows();
}
$scope.$on("telemetryUpdate", updateRows);
$scope.$watch("telemetry", setupColumns);
}
return EventListController;
/**
* A description of how to display a certain column of data in an
* Events view.
* @interface platform/features/events.EventColumn
* @private
*/
/**
* Get the title to display in this column's header.
* @returns {string} the title to display
* @method platform/features/events.EventColumn#getTitle
*/
/**
* Get the text to display inside a row under this
* column.
* @param {DomainObject} domainObject the domain object associated
* with this row
* @param {TelemetrySeries} series the telemetry data associated
* with this row
* @param {number} index the index of the telemetry datum associated
* with this row
* @returns {string} the text to display
* @method platform/features/events.EventColumn#getValue
*/
}
);

View File

@@ -1,163 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 () {
/**
* The EventListPopulator is responsible for filling in the
* values which should appear within columns of a event list
* view, based on received telemetry data.
* @constructor
* @memberof platform/features/events
* @param {Column[]} columns the columns to be populated
*/
function EventListPopulator(columns) {
this.columns = columns;
}
/*
* Look up the most recent values from a set of data objects.
* Returns an array of objects in the order in which data
* should be displayed; each element is an object with
* two properties:
*
* * objectIndex: The index of the domain object associated
* with the data point to be displayed in that
* row.
* * pointIndex: The index of the data point itself, within
* its data set.
*
* @param {Array<Telemetry>} datas an array of the most recent
* data objects; expected to be in the same order
* as the domain objects provided at constructor
* @param {number} count the number of rows to provide
*/
function getLatestDataValues(datas, count) {
var latest = [],
candidate,
candidateTime,
used = datas.map(function () { return 0; });
// This algorithm is O(nk) for n rows and k telemetry elements;
// one O(k) linear search for a max is made for each of n rows.
// This could be done in O(n lg k + k lg k), using a priority
// queue (where priority is max-finding) containing k initial
// values. For n rows, pop the max from the queue and replenish
// the queue with a value from the data at the same
// objectIndex, if available.
// But k is small, so this might not give an observable
// improvement in performance.
// Find the most recent unused data point (this will be used
// in a loop to find and the N most recent data points)
function findCandidate(data, i) {
var nextTime,
pointCount = data.getPointCount(),
pointIndex = pointCount - used[i] - 1;
if (data && pointIndex >= 0) {
nextTime = data.getDomainValue(pointIndex);
if (nextTime > candidateTime) {
candidateTime = nextTime;
candidate = {
objectIndex: i,
pointIndex: pointIndex
};
}
}
}
// Assemble a list of the most recent data points
while (latest.length < count) {
// Reset variables pre-search
candidateTime = Number.NEGATIVE_INFINITY;
candidate = undefined;
// Linear search for most recent
datas.forEach(findCandidate);
if (candidate) {
// Record this data point - it is the most recent
latest.push(candidate);
// Track the data points used so we can look farther back
// in the data set on the next iteration
used[candidate.objectIndex] = used[candidate.objectIndex] + 1;
} else {
// Ran out of candidates; not enough data points
// available to fill all rows.
break;
}
}
return latest;
}
/**
* Get the text which should appear in headers for the
* provided columns.
* @memberof platform/features/events.EventListPopulator
* @returns {string[]} column headers
*/
EventListPopulator.prototype.getHeaders = function () {
return this.columns.map(function (column) {
return column.getTitle();
});
};
/**
* Get the contents of rows for the event list view.
* @param {TelemetrySeries[]} datas the data sets
* @param {DomainObject[]} objects the domain objects which
* provided the data sets; these should match
* index-to-index with the `datas` argument
* @param {number} count the number of rows to populate
* @memberof platform/features/events.EventListPopulator
* @returns {string[][]} an array of rows, each of which
* is an array of values which should appear
* in that row
*/
EventListPopulator.prototype.getRows = function (datas, objects, count) {
var values = getLatestDataValues(datas, count),
columns = this.columns;
// Each value will become a row, which will contain
// some value in each column (rendering by the
// column object itself)
// Additionally, we want to display the rows in reverse
// order. (i.e. from the top to the bottom of the page)
return values.map(function (value) {
return columns.map(function (column) {
return column.getValue(
objects[value.objectIndex],
datas[value.objectIndex],
value.pointIndex
);
});
}).reverse();
};
return EventListPopulator;
}
);

View File

@@ -1,60 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
/**
* A column which will report telemetry range values
* (typically, measurements.) Used by the ScrollingListController.
*
* @memberof platform/features/events
* @constructor
* @implements {platform/features/events.EventsColumn}
* @param rangeMetadata an object with the machine- and human-
* readable names for this range (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function RangeColumn(rangeMetadata, telemetryFormatter) {
this.rangeMetadata = rangeMetadata;
this.telemetryFormatter = telemetryFormatter;
}
RangeColumn.prototype.getTitle = function () {
return this.rangeMetadata.name;
};
RangeColumn.prototype.getValue = function (domainObject, data, index) {
var rangeKey = this.rangeMetadata.key;
return this.telemetryFormatter.formatRangeValue(
data.getRangeValue(index, rangeKey)
);
};
return RangeColumn;
}
);

View File

@@ -1,72 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* Module defining MCTDataTable. Created by shale on 06/22/2015.
*/
define(
['text!../../res/templates/mct-data-table.html'],
function (dataTableTemplate) {
function MCTDataTable($window) {
return {
restrict: "E",
template: dataTableTemplate,
scope: {
headers: "=",
rows: "=",
ascendingScroll: "="
},
link: function ($scope, $element) {
var currentHeight,
previousHeight,
scrollParent;
// If the scroll is set to ascending, we want to
// check when elements are added to the table, and move the scroll
// bar accordingly.
// (When viewing at the bottom of the page, the scroll bar will
// stay at the bottom despite additions to the table)
if ($scope.ascendingScroll) {
$scope.$watch("rows", function () {
// Wait until the page as been repainted (otherwise the
// height will always be zero)
$window.requestAnimationFrame(function () {
previousHeight = currentHeight;
// The height of the table body
currentHeight = $element[0].firstElementChild.firstElementChild.nextElementSibling.clientHeight;
// One of the parents is a div that has vscroll
scrollParent = $element[0].parentElement.parentElement.parentElement.parentElement.parentElement;
// Move the scrollbar down the amount that the height has changed
scrollParent.scrollTop = scrollParent.scrollTop + (currentHeight - previousHeight);
});
});
}
}
};
}
return MCTDataTable;
}
);

View File

@@ -1,65 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* Module defining MessagesViewPolicy. Created by shale on 06/24/2015.
*/
define(
[],
function () {
/**
* Policy controlling when the Messages view should be avaliable.
* @memberof platform/features/events
* @constructor
* @implements {Policy.<View, DomainObject>}
*/
function MessagesViewPolicy() {}
function hasStringTelemetry(domainObject) {
var telemetry = domainObject &&
domainObject.getCapability('telemetry'),
metadata = telemetry ? telemetry.getMetadata() : {},
ranges = metadata.ranges || [];
return ranges.some(function (range) {
return range.format === 'string';
});
}
MessagesViewPolicy.prototype.allow = function (view, domainObject) {
// This policy only applies for the Messages view
if (view.key === 'messages') {
// The Messages view is allowed only if the domain
// object has string telemetry
if (!hasStringTelemetry(domainObject)) {
return false;
}
}
// Like all policies, allow by default.
return true;
};
return MessagesViewPolicy;
}
);

View File

@@ -1,82 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015.
*/
define(
["../src/DomainColumn"],
function (DomainColumn) {
var TEST_DOMAIN_VALUE = "some formatted domain value";
describe("An event list domain column", function () {
var mockDataSet,
testMetadata,
mockFormatter,
column;
beforeEach(function () {
mockDataSet = jasmine.createSpyObj(
"data",
[ "getDomainValue" ]
);
mockFormatter = jasmine.createSpyObj(
"formatter",
[ "formatDomainValue", "formatRangeValue" ]
);
testMetadata = {
key: "testKey",
name: "Test Name"
};
mockFormatter.formatDomainValue.andReturn(TEST_DOMAIN_VALUE);
column = new DomainColumn(testMetadata, mockFormatter);
});
it("reports a column header from domain metadata", function () {
expect(column.getTitle()).toEqual("Test Name");
});
it("looks up data from a data set", function () {
column.getValue(undefined, mockDataSet, 42);
expect(mockDataSet.getDomainValue)
.toHaveBeenCalledWith(42, "testKey");
});
it("formats domain values as time", function () {
mockDataSet.getDomainValue.andReturn(402513731000);
// Should have just given the value the formatter gave
expect(column.getValue(undefined, mockDataSet, 42))
.toEqual(TEST_DOMAIN_VALUE);
// Make sure that service interactions were as expected
expect(mockFormatter.formatDomainValue)
.toHaveBeenCalledWith(402513731000);
expect(mockFormatter.formatRangeValue)
.not.toHaveBeenCalled();
});
});
}
);

View File

@@ -1,108 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* EventSpec. Created by shale on 06/24/2015.
*/
define(
["../src/EventListController"],
function (EventListController) {
describe("The event list controller", function () {
var mockScope,
mockTelemetry,
testMetadata,
controller;
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
[ "$on", "$watch" ]
);
mockTelemetry = jasmine.createSpyObj(
"telemetryController",
[ "getResponse", "getMetadata", "getTelemetryObjects" ]
);
testMetadata = [
{
domains: [
{ key: "d0", name: "D0" },
{ key: "d1", name: "D1" }
],
ranges: [
{ key: "r0", name: "R0" },
{ key: "r1", name: "R1" }
]
},
{
domains: [
{ key: "d0", name: "D0" },
{ key: "d2", name: "D2" }
],
ranges: [
{ key: "r0", name: "R0" }
]
}
];
mockTelemetry.getMetadata.andReturn(testMetadata);
mockTelemetry.getResponse.andReturn([]);
mockTelemetry.getTelemetryObjects.andReturn([]);
mockScope.telemetry = mockTelemetry;
controller = new EventListController(mockScope);
});
it("listens for telemetry data updates", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
"telemetryUpdate",
jasmine.any(Function)
);
});
it("watches for telemetry controller changes", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
"telemetry",
jasmine.any(Function)
);
});
it("provides a column for each unique domain and range", function () {
// Should have five columns based on metadata above,
// (d0, d1, d2, r0, r1)
mockScope.$watch.mostRecentCall.args[1](mockTelemetry);
expect(mockScope.headers).toEqual(["D0", "D1", "D2", "R0", "R1"]);
});
it("does not throw if telemetry controller is undefined", function () {
// Just a general robustness check
mockScope.telemetry = undefined;
expect(mockScope.$watch.mostRecentCall.args[1])
.not.toThrow();
});
it("provides default columns if domain/range metadata is unavailable", function () {
mockTelemetry.getMetadata.andReturn([]);
mockScope.$watch.mostRecentCall.args[1](mockTelemetry);
expect(mockScope.headers).toEqual(["Time", "Message"]);
});
});
}
);

View File

@@ -1,101 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* EventSpec. Created by shale on 06/24/2015.
*/
define(
["../src/EventListPopulator"],
function (EventListPopulator) {
describe("The event list populator", function () {
var mockColumns,
mockDatas,
mockDomainObjects,
populator;
function makeMockColumn(name, index) {
var mockColumn = jasmine.createSpyObj(
"column" + index,
[ "getTitle", "getValue" ]
);
mockColumn.getTitle.andReturn(name);
mockColumn.getValue.andCallFake(function (obj, data, i) {
return data.getDomainValue(i);
});
return mockColumn;
}
function makeMockData(bias, index) {
var mockData = jasmine.createSpyObj(
"data" + index,
[ "getDomainValue", "getPointCount" ]
);
mockData.getPointCount.andReturn(1000);
mockData.getDomainValue.andCallFake(function (i) {
return i + bias;
});
return mockData;
}
function makeMockDomainObject(name, index) {
var mockDomainObject = jasmine.createSpyObj(
"domainObject" + index,
[ "getId", "getModel" ]
);
return mockDomainObject;
}
beforeEach(function () {
mockColumns = ["A", "B", "C", "D"].map(makeMockColumn);
mockDatas = [ 10, 0, 3 ].map(makeMockData);
mockDomainObjects = ["A", "B", "C"].map(makeMockDomainObject);
populator = new EventListPopulator(mockColumns);
});
it("returns column headers", function () {
expect(populator.getHeaders()).toEqual(["A", "B", "C", "D"]);
});
it("provides rows on request, with all columns in each row", function () {
var rows = populator.getRows(mockDatas, mockDomainObjects, 84);
expect(rows.length).toEqual(84);
rows.forEach(function (row) {
expect(row.length).toEqual(4); // number of columns
});
});
it("returns rows in most-recent-last order", function () {
var rows = populator.getRows(mockDatas, mockDomainObjects, 84),
previous = Number.NEGATIVE_INFINITY;
// Should always be most-recent-last
rows.forEach(function (row) {
expect(row[0]).not.toBeLessThan(previous);
previous = row[0];
});
});
});
}
);

View File

@@ -1,79 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* EventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/23/2015.
*/
define(
["../src/RangeColumn"],
function (RangeColumn) {
var TEST_RANGE_VALUE = "some formatted range value";
describe("An event list range column", function () {
var mockDataSet,
testMetadata,
mockFormatter,
column;
beforeEach(function () {
mockDataSet = jasmine.createSpyObj(
"data",
[ "getRangeValue" ]
);
mockFormatter = jasmine.createSpyObj(
"formatter",
[ "formatDomainValue", "formatRangeValue" ]
);
testMetadata = {
key: "testKey",
name: "Test Name"
};
mockFormatter.formatRangeValue.andReturn(TEST_RANGE_VALUE);
column = new RangeColumn(testMetadata, mockFormatter);
});
it("reports a column header from range metadata", function () {
expect(column.getTitle()).toEqual("Test Name");
});
it("looks up data from a data set", function () {
column.getValue(undefined, mockDataSet, 42);
expect(mockDataSet.getRangeValue)
.toHaveBeenCalledWith(42, "testKey");
});
it("formats range values as time", function () {
mockDataSet.getRangeValue.andReturn(123.45678);
expect(column.getValue(undefined, mockDataSet, 42))
.toEqual(TEST_RANGE_VALUE);
// Make sure that service interactions were as expected
expect(mockFormatter.formatRangeValue)
.toHaveBeenCalledWith(123.45678);
expect(mockFormatter.formatDomainValue)
.not.toHaveBeenCalled();
});
});
}
);

View File

@@ -1,78 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* EventSpec. Created by shale on 06/24/2015.
*/
define(
["../../src/policies/MessagesViewPolicy"],
function (MessagesViewPolicy) {
describe("The messages view policy", function () {
var mockDomainObject,
mockTelemetry,
testType,
testView,
testMetadata,
policy;
beforeEach(function () {
testView = { key: "string" };
testMetadata = {};
mockDomainObject = jasmine.createSpyObj(
'domainObject',
['getModel', 'getCapability']
);
mockTelemetry = jasmine.createSpyObj(
'telemetry',
['getMetadata']
);
mockDomainObject.getModel.andCallFake(function () {
return {type: testType};
});
mockDomainObject.getCapability.andCallFake(function (c) {
return c === 'telemetry' ? mockTelemetry : undefined;
});
mockTelemetry.getMetadata.andReturn(testMetadata);
policy = new MessagesViewPolicy();
});
it("disallows the message view for objects without string telemetry", function () {
testMetadata.ranges = [ { format: 'notString' } ];
expect(policy.allow({ key: 'messages' }, mockDomainObject)).toBeFalsy();
});
it("allows the message view for objects with string telemetry", function () {
testMetadata.ranges = [ { format: 'string' } ];
expect(policy.allow({ key: 'messages' }, mockDomainObject)).toBeTruthy();
});
it("returns true when the current view is not the Messages view", function () {
expect(policy.allow({ key: 'notMessages' }, mockDomainObject)).toBeTruthy();
});
});
}
);

View File

@@ -56,7 +56,7 @@ define([
{
"key": "layout",
"name": "Display Layout",
"glyph": "L",
"glyph": "\u004c",
"type": "layout",
"template": layoutTemplate,
"editable": true,
@@ -80,28 +80,28 @@ define([
"items": [
{
"method": "add",
"glyph": "+",
"glyph": "\u002b",
"control": "menu-button",
"text": "Add",
"options": [
{
"name": "Box",
"glyph": "à",
"glyph": "\u00e0",
"key": "fixed.box"
},
{
"name": "Line",
"glyph": "â",
"glyph": "\u00e2",
"key": "fixed.line"
},
{
"name": "Text",
"glyph": "ä",
"glyph": "\u00e4",
"key": "fixed.text"
},
{
"name": "Image",
"glyph": "ã",
"glyph": "\u00e3",
"key": "fixed.image"
}
]
@@ -117,22 +117,22 @@ define([
"options": [
{
"name": "Move to Top",
"glyph": "^",
"glyph": "\u00eb",
"key": "top"
},
{
"name": "Move Up",
"glyph": "^",
"glyph": "\u005e",
"key": "up"
},
{
"name": "Move Down",
"glyph": "v",
"glyph": "\u0076",
"key": "down"
},
{
"name": "Move to Bottom",
"glyph": "v",
"glyph": "\u00ee",
"key": "bottom"
}
]
@@ -261,8 +261,9 @@ define([
{
"key": "layout",
"name": "Display Layout",
"glyph": "L",
"description": "A layout in which multiple telemetry panels may be displayed.",
"glyph": "\u004c",
"description": "Assemble other objects and components together into a reusable screen layout. Working in a simple canvas workspace, simply drag in the objects you want, position and size them. Save your design and view or edit it at any time.",
"priority": 900,
"features": "creation",
"model": {
"composition": []
@@ -276,12 +277,12 @@ define([
{
"name": "Horizontal grid (px)",
"control": "textfield",
"cssclass": "l-small l-numeric"
"cssclass": "l-input-sm l-numeric"
},
{
"name": "Vertical grid (px)",
"control": "textfield",
"cssclass": "l-small l-numeric"
"cssclass": "l-input-sm l-numeric"
}
],
"key": "layoutGrid",
@@ -294,6 +295,7 @@ define([
"name": "Telemetry Panel",
"glyph": "t",
"description": "A panel for collecting telemetry elements.",
"priority": 899,
"delegates": [
"telemetry"
],

View File

@@ -36,8 +36,9 @@ define([
{
"key": "example.page",
"name": "Web Page",
"glyph": "ê",
"description": "A component to display a web page or image with a valid URL. Can be added to a Display Layout.",
"glyph": "\u00ea",
"description": "Embed a web page or web-based image in a resizeable window component. Can be added to Display Layouts. Note that the URL being embedded must allow iframing.",
"priority": 50,
"features": [
"creation"
],
@@ -47,7 +48,8 @@ define([
"name": "URL",
"control": "textfield",
"pattern": "^(ftp|https?)\\:\\/\\/\\w+(\\.\\w+)*(\\:\\d+)?(\\/\\S*)*$",
"required": true
"required": true,
"cssclass": "l-input-lg"
}
]
}

View File

@@ -19,58 +19,52 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<style>
.l-inspect .l-inspector-part .no-margin .form {
margin-left: 0;
}
.reduced-min-width .form .form-row > .label {
min-width: 80px;
}
</style>
<div ng-controller="PlotOptionsController" class="flex-elem grows l-inspector-part">
<em class="t-inspector-part-header" title="Display properties for this object">Display</em>
<em class="t-inspector-part-header" title="Display properties for this object">Plot Options</em>
<mct-form
ng-model="configuration.plot.xAxis"
structure="xAxisForm"
name="xAxisFormState"
class="flex-elem l-flex-row no-validate no-margin reduced-min-width">
class="flex-elem l-flex-row no-validate no-margin">
</mct-form>
<mct-form
ng-model="configuration.plot.yAxis"
structure="yAxisForm"
name="yAxisFormState"
class="flex-elem l-flex-row no-validate no-margin reduced-min-width">
class="flex-elem l-flex-row no-validate no-margin">
</mct-form>
<div class="section-header ng-binding ng-scope">
Plot Series
</div>
<ul class="first flex-elem grows vscroll">
<ul class="tree">
<li ng-repeat="child in children">
<span ng-controller="ToggleController as toggle">
<span ng-controller="TreeNodeController as treeNode">
<span class="tree-item menus-to-left">
<span
class='ui-symbol view-control flex-elem has-children'
ng-class="{ expanded: toggle.isActive() }"
ng-click="toggle.toggle(); treeNode.trackExpansion()">
<div class="form">
<div class="section-header ng-binding ng-scope">
Plot Series
</div>
<ul class="first flex-elem grows vscroll">
<ul class="tree">
<li ng-repeat="child in children">
<span ng-controller="ToggleController as toggle">
<span ng-controller="TreeNodeController as treeNode">
<span class="tree-item menus-to-left">
<span
class='ui-symbol view-control flex-elem has-children'
ng-class="{ expanded: toggle.isActive() }"
ng-click="toggle.toggle(); treeNode.trackExpansion()">
</span>
<mct-representation
class="rep-object-label"
key="'label'"
mct-object="child">
</mct-representation>
</span>
<mct-representation
class="rep-object-label"
key="'label'"
mct-object="child">
</mct-representation>
</span>
<mct-form
ng-class="{hidden: !toggle.isActive()}"
ng-model="configuration.plot.series[$index]"
structure="plotSeriesForm"
name="plotOptionsState"
class="flex-elem l-flex-row no-validate">
</mct-form>
</span>
<mct-form
ng-class="{hidden: !toggle.isActive()}"
ng-model="configuration.plot.series[$index]"
structure="plotSeriesForm"
name="plotOptionsState"
class="flex-elem l-flex-row l-controls-first no-validate">
</mct-form>
</span>
</li>
</li>
</ul>
</ul>
</ul>
</div>
</div>

View File

@@ -46,9 +46,9 @@ define(
'control': 'select',
'key': 'key',
'options': [
{'name':'scet', 'value': 'scet'},
{'name':'sclk', 'value': 'sclk'},
{'name':'lst', 'value': 'lst'}
{'name':'SCET', 'value': 'scet'},
{'name':'SCLK', 'value': 'sclk'},
{'name':'LST', 'value': 'lst'}
]
}
]
@@ -62,6 +62,16 @@ define(
// itself.
'name': 'y-axis',
'rows': [
{
'name': 'Range',
'control': 'select',
'key': 'key',
'options': [
{'name':'EU', 'value': 'eu'},
{'name':'DN', 'value': 'dn'},
{'name':'Status', 'value': 'status'}
]
},
{
'name': 'Autoscale',
'control': 'checkbox',
@@ -71,23 +81,15 @@ define(
'name': 'Min',
'control': 'textfield',
'key': 'min',
'pattern': '[0-9]'
'pattern': '[0-9]',
'inputsize' : 'sm'
},
{
'name': 'Max',
'control': 'textfield',
'key': 'max',
'pattern': '[0-9]'
},
{
'name': 'Range',
'control': 'select',
'key': 'key',
'options': [
{'name':'eu', 'value': 'eu'},
{'name':'dn', 'value': 'dn'},
{'name':'status', 'value': 'status'}
]
'pattern': '[0-9]',
'inputsize' : 'sm'
}
]
}]
@@ -108,7 +110,8 @@ define(
{
'name': 'Markers',
'control': 'checkbox',
'key': 'markers'
'key': 'markers',
'layout': 'control-first'
}
]
},
@@ -118,19 +121,22 @@ define(
'name': 'No Line',
'control': 'radio',
'key': 'lineType',
'value': 'noLine'
'value': 'noLine',
'layout': 'control-first'
},
{
'name': 'Step Line',
'control': 'radio',
'key': 'lineType',
'value': 'stepLine'
'value': 'stepLine',
'layout': 'control-first'
},
{
'name': 'Linear Line',
'control': 'radio',
'key': 'lineType',
'value': 'linearLine'
'value': 'linearLine',
'layout': 'control-first'
}
]
}

View File

@@ -24,6 +24,8 @@ define(
[],
function () {
var DIGITS = 3;
/**
* Wraps a `TelemetryFormatter` to provide formats for domain and
* range values; provides a single place to track domain/range
@@ -61,6 +63,10 @@ define(
};
PlotTelemetryFormatter.prototype.formatRangeValue = function (value) {
if (typeof value === 'number') {
return value.toFixed(DIGITS);
}
return this.telemetryFormatter
.formatRangeValue(value, this.rangeFormat);
};

View File

@@ -53,14 +53,17 @@ define(
.toHaveBeenCalledWith(12321, domainFormat);
});
it("includes format in formatRangeValue calls", function () {
it("includes format in formatRangeValue calls for strings", function () {
mockFormatter.formatRangeValue.andReturn("formatted!");
expect(formatter.formatRangeValue(12321))
expect(formatter.formatRangeValue('foo'))
.toEqual("formatted!");
expect(mockFormatter.formatRangeValue)
.toHaveBeenCalledWith(12321, rangeFormat);
.toHaveBeenCalledWith('foo', rangeFormat);
});
it("formats numeric values with three fixed digits", function () {
expect(formatter.formatRangeValue(10)).toEqual("10.000");
});
});
});

View File

@@ -1,82 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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([
"./src/RTEventListController",
"./src/directives/MCTRTDataTable",
"./src/policies/RTMessagesViewPolicy",
"text!./res/templates/rtmessages.html",
'legacyRegistry'
], function (
RTEventListController,
MCTRTDataTable,
RTMessagesViewPolicy,
rtmessagesTemplate,
legacyRegistry
) {
legacyRegistry.register("platform/features/rtevents", {
"name": "Event Messages",
"description": "List of time-ordered event messages",
"extensions": {
"views": [
{
"key": "rtmessages",
"name": "RT Messages",
"glyph": "5",
"description": "Scrolling list of real time messages.",
"template": rtmessagesTemplate,
"needs": [
"telemetry"
],
"delegation": true
}
],
"controllers": [
{
"key": "RTEventListController",
"implementation": RTEventListController,
"depends": [
"$scope",
"telemetryHandler",
"telemetryFormatter"
]
}
],
"directives": [
{
"key": "mctRtDataTable",
"implementation": MCTRTDataTable,
"depends": [
"$window"
]
}
],
"policies": [
{
"category": "view",
"implementation": RTMessagesViewPolicy
}
]
}
});
});

View File

@@ -1,37 +0,0 @@
<!--
Open MCT Web, Copyright (c) 2014-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web 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 Web 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.
-->
<table class="tabular">
<thead>
<tr>
<th ng-repeat="header in headers">
{{header}}
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="cell in row">
{{cell}}
</td>
</tr>
</tbody>
</table>

View File

@@ -1,29 +0,0 @@
<!--
Open MCT Web, Copyright (c) 2014-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web 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 Web 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.
-->
<div class="w1">
<div class="w2"
ng-controller="RTEventListController as rtevent">
<mct-rt-data-table headers="rtevent.headers()" rows="rtevent.rows()" ascending-scroll="true"></mct-rt-data-table>
</div>
</div>

View File

@@ -1,73 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
/**
* A column which will report telemetry domain values
* (typically, timestamps.) Used by the ScrollingListController.
*
* @memberof platform/features/rtevents
* @constructor
* @param domainMetadata an object with the machine- and human-
* readable names for this domain (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function DomainColumn(telemetryFormatter) {
return {
/**
* Get the title to display in this column's header.
* @returns {string} the title to display
* @memberof platform/features/rtevents.DomainColumn#
*/
getTitle: function () {
// At the moment there does not appear to be a way to get the
// column's title through metadata for real time telemetry
return "Time";
},
/**
* Get the text to display inside a row under this
* column.
* @returns {string} the text to display
* @memberof platform/features/rtevents.DomainColumn#
*/
getValue: function (domainObject, handle) {
return {
text: telemetryFormatter.formatDomainValue(
handle.getDomainValue(domainObject)
)
};
}
};
}
return DomainColumn;
}
);

View File

@@ -1,134 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* Module defining RTEventListController.
* Created by shale on 06/25/2014. Based on RT Scrolling lists.
*/
define(
["./DomainColumn", "./RangeColumn"],
function (DomainColumn, RangeColumn) {
var ROW_COUNT = 100;
/**
* The RTEventListController is responsible for populating
* the contents of the messages view.
* @memberof platform/features/rtevents
* @constructor
*/
function RTEventListController($scope, telemetryHandler, telemetryFormatter) {
var handle,
lastUpdated = {},
lastIds = [],
columns = [],
headers = [],
rows = [];
function getTelemetryObjects() {
//console.log("handle.getTelemetryObjects() ", handle.getTelemetryObjects());
return handle ? handle.getTelemetryObjects() : [];
}
function idsChanged(telemetryObjects) {
function mismatch(id, index) {
return id !== telemetryObjects[index].getId();
}
return lastIds.length !== telemetryObjects.length ||
lastIds.some(mismatch);
}
function setupColumns() {
columns = [];
columns.push(new DomainColumn(telemetryFormatter));
columns.push(new RangeColumn());
headers = columns.map(function (column) {
return column.getTitle();
});
}
function updateObjects(telemetryObjects) {
if (idsChanged(telemetryObjects)) {
setupColumns(telemetryObjects);
lastIds = telemetryObjects.map(function (telemetryObject) {
return telemetryObject.getId();
});
}
}
function addRow(telemetryObject) {
var id = telemetryObject.getId(),
domainValue = handle.getDomainValue(telemetryObject);
if (lastUpdated[id] !== domainValue &&
domainValue !== undefined) {
// Instead of unshift (scrolling), use push (messages)
rows.push(columns.map(function (column) {
return column.getValue(telemetryObject, handle).text;
}));
// Remove first rows when adding past the max rows limit
rows.splice(0, rows.length - ROW_COUNT);
lastUpdated[id] = domainValue;
}
}
function updateValues() {
getTelemetryObjects().forEach(addRow);
}
function releaseSubscription() {
if (handle) {
handle.unsubscribe();
}
}
function makeSubscription(domainObject) {
releaseSubscription();
rows = [];
handle = telemetryHandler.handle(
domainObject,
updateValues,
true
);
}
$scope.$on("$destroy", releaseSubscription);
$scope.$watch("domainObject", makeSubscription);
$scope.$watch(getTelemetryObjects, updateObjects);
return {
rows: function () {
return rows;
},
headers: function () {
return headers;
}
};
}
return RTEventListController;
}
);

View File

@@ -1,70 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* Module defining DomainColumn.
* Created by vwoeltje on 11/18/14. Modified by shale on 06/25/2015.
*/
define(
[],
function () {
/**
* A column which will report telemetry range values
* (typically, measurements.) Used by the RTEventListController.
*
* @memberof platform/features/rtevents
* @constructor
* @param rangeMetadata an object with the machine- and human-
* readable names for this range (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function RangeColumn() {
return {
/**
* Get the title to display in this column's header.
* @returns {string} the title to display
* @memberof platform/features/rtevents.RangeColumn#
*/
getTitle: function () {
return "Message";
},
/**
* Get the text to display inside a row under this
* column.
* @returns {string} the text to display
* @memberof platform/features/rtevents.RangeColumn#
*/
getValue: function (domainObject, handle) {
return {
text: handle.getRangeValue(domainObject)
};
}
};
}
return RangeColumn;
}
);

View File

@@ -1,72 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* Module defining MCTRTDataTable. Created by shale on 06/25/2015.
*/
define(
['text!../../res/templates/mct-rt-data-table.html'],
function (dataTableTemplate) {
function MCTRTDataTable($window) {
return {
restrict: "E",
template: dataTableTemplate,
scope: {
headers: "=",
rows: "=",
ascendingScroll: "="
},
link: function ($scope, $element) {
var currentHeight,
previousHeight,
scrollParent;
// If the scroll is set to ascending, we want to
// check when elements are added to the table, and move the scroll
// bar accordingly.
// (When viewing at the bottom of the page, the scroll bar will
// stay at the bottom despite additions to the table)
if ($scope.ascendingScroll) {
$scope.$watchCollection("rows", function () {
// Wait until the page as been repainted (otherwise the
// height will always be zero)
$window.requestAnimationFrame(function () {
previousHeight = currentHeight;
// The height of the table body
currentHeight = $element[0].firstElementChild.firstElementChild.nextElementSibling.clientHeight;
// One of the parents is a div that has vscroll
scrollParent = $element[0].parentElement.parentElement.parentElement.parentElement.parentElement;
// Move the scrollbar down the amount that the height has changed
scrollParent.scrollTop = scrollParent.scrollTop + (currentHeight - previousHeight);
});
});
}
}
};
}
return MCTRTDataTable;
}
);

View File

@@ -1,74 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* Module defining MessagesViewPolicy. Created by shale on 06/24/2015.
*/
define(
[],
function () {
/**
* Policy controlling when the real time Messages view should be avaliable.
* @memberof platform/features/rtevents
* @constructor
*/
function RTMessagesViewPolicy() {
function hasStringTelemetry(domainObject) {
var telemetry = domainObject &&
domainObject.getCapability('telemetry'),
metadata = telemetry ? telemetry.getMetadata() : {},
ranges = metadata.ranges || [];
return ranges.some(function (range) {
return range.format === 'string';
});
}
return {
/**
* Check whether or not a given action is allowed by this
* policy.
* @param {Action} action the action
* @param domainObject the domain object which will be viewed
* @returns {boolean} true if not disallowed
* @memberof platform/features/rtevents.RTMessagesViewPolicy#
*/
allow: function (view, domainObject) {
// This policy only applies for the RT Messages view
if (view.key === 'rtmessages') {
// The Messages view is allowed only if the domain
// object has string telemetry
if (!hasStringTelemetry(domainObject)) {
return false;
}
}
// Like all policies, allow by default.
return true;
}
};
}
return RTMessagesViewPolicy;
}
);

View File

@@ -1,86 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* RTEventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/25/2015.
*/
define(
["../src/DomainColumn"],
function (DomainColumn) {
var TEST_DOMAIN_VALUE = "some formatted domain value";
describe("A real time event list domain column", function () {
var mockDomainObject,
mockTelemetryHandler,
mockHandle,
mockFormatter,
column;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
"domainObject",
["getModel", "getCapability"]
);
mockTelemetryHandler = jasmine.createSpyObj(
"telemetryHandler",
["handle"]
);
mockHandle = jasmine.createSpyObj(
"handle",
["getDomainValue", "getRangeValue"]
);
mockFormatter = jasmine.createSpyObj(
"formatter",
["formatDomainValue", "formatRangeValue"]
);
mockFormatter.formatDomainValue.andReturn(TEST_DOMAIN_VALUE);
column = new DomainColumn(mockFormatter);
});
it("reports the domain column header as 'Time'", function () {
expect(column.getTitle()).toEqual("Time");
});
it("retrives data from a telemetry provider", function () {
column.getValue(mockDomainObject, mockHandle);
expect(mockHandle.getDomainValue).toHaveBeenCalled();
});
it("formats domain values as time", function () {
mockHandle.getDomainValue.andReturn(402513731000);
// Should have just given the value the formatter gave
expect(column.getValue(mockDomainObject, mockHandle).text)
.toEqual(TEST_DOMAIN_VALUE);
// Make sure that service interactions were as expected
expect(mockFormatter.formatDomainValue)
.toHaveBeenCalledWith(402513731000);
expect(mockFormatter.formatRangeValue)
.not.toHaveBeenCalled();
});
});
}
);

View File

@@ -1,128 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* RTEventSpec. Created by shale on 06/25/2015.
*/
define(
["../src/RTEventListController"],
function (RTEventListController) {
describe("The real time event list controller", function () {
var mockDomainObject,
mockScope,
mockTelemetryHandler,
mockHandle,
mockTelemetryFormatter,
controller;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[ "getId", "getModel", "getCapability" ]
);
mockScope = jasmine.createSpyObj(
"$scope",
[ "$on", "$watch" ]
);
mockTelemetryHandler = jasmine.createSpyObj(
"telemetryHandler",
["handle"]
);
mockHandle = jasmine.createSpyObj(
"handle",
["getDomainValue", "getRangeValue", "getTelemetryObjects", "unsubscribe"]
);
mockTelemetryFormatter = jasmine.createSpyObj(
"formatter",
["formatDomainValue", "formatRangeValue"]
);
controller = new RTEventListController(mockScope, mockTelemetryHandler, mockTelemetryFormatter);
mockHandle.getDomainValue.andReturn("domain value");
mockHandle.getRangeValue.andReturn("range value");
mockTelemetryHandler.handle.andReturn(mockHandle);
mockHandle.getTelemetryObjects.andReturn([mockDomainObject]);
// Subscribe to the RT telemetry
// second argument of: $scope.$watch("domainObject", makeSubscription);
mockScope.$watch.calls.forEach(function (c) {
// There are two possible calls of $watch, so we need to filter
// through the calls to get the correct kind
if (c.args[0] === 'domainObject') {
c.args[1]();
}
});
// callback, passed into telemetry handler
mockTelemetryHandler.handle.mostRecentCall.args[1]();
// Update the telemetry objects
// second argument of: $scope.$watch(getTelemetryObjects, updateObjects);
mockScope.$watch.calls.forEach(function (c) {
// There are two possible calls of $watch, so we need to filter
// through the calls to get the correct kind
if (c.args[0] !== 'domainObject') {
c.args[1]([mockDomainObject]);
}
});
});
it("provides a domain and a range column", function () {
// Should have two columns with these headers
expect(controller.headers()).toEqual(["Time", "Message"]);
});
it("listens for telemetry data updates", function () {
// Of the two possible $watch calls, this corresponds to
// $scope.$watch(getTelemetryObjects, updateObjects);
expect(mockScope.$watch).toHaveBeenCalledWith(
jasmine.any(Function),
jasmine.any(Function)
);
});
it("makes telemetry subscriptions", function () {
// Of the two possible $watch calls, this corresponds to
// $scope.$watch("domainObject", makeSubscription);
expect(mockScope.$watch).toHaveBeenCalledWith(
"domainObject",
jasmine.any(Function)
);
});
it("releases telemetry subscriptions on destruction", function () {
// Call the second argument of
// $scope.$on("$destroy", releaseSubscription);
mockScope.$on.mostRecentCall.args[1]();
expect(mockScope.$on).toHaveBeenCalledWith(
"$destroy",
jasmine.any(Function)
);
});
});
}
);

View File

@@ -1,82 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* RTEventSpec. Created by vwoeltje on 11/6/14. Modified by shale on 06/25/2015.
*/
define(
["../src/RangeColumn"],
function (RangeColumn) {
var TEST_RANGE_VALUE = "some formatted range value";
describe("A real time event list range column", function () {
var mockDomainObject,
mockTelemetryHandler,
mockHandle,
mockFormatter,
column;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
"domainObject",
["getModel", "getCapability"]
);
mockTelemetryHandler = jasmine.createSpyObj(
"telemetryHandler",
["handle"]
);
mockHandle = jasmine.createSpyObj(
"handle",
["getDomainValue", "getRangeValue"]
);
mockFormatter = jasmine.createSpyObj(
"formatter",
["formatDomainValue", "formatRangeValue"]
);
mockFormatter.formatRangeValue.andReturn(TEST_RANGE_VALUE);
column = new RangeColumn();
});
it("reports a range column header as 'Message'", function () {
expect(column.getTitle()).toEqual("Message");
});
it("retrives data from a telemetry provider", function () {
column.getValue(mockDomainObject, mockHandle);
expect(mockHandle.getRangeValue).toHaveBeenCalled();
});
it("does not format range values", function () {
mockHandle.getRangeValue.andReturn(123.45678);
// Does not format range value as time
expect(column.getValue(mockDomainObject, mockHandle).text)
.not.toEqual(TEST_RANGE_VALUE);
// There should be no additional formatting
// i.e. the message string stays a string
expect(column.getValue(mockDomainObject, mockHandle).text)
.toEqual(123.45678);
});
});
}
);

View File

@@ -1,80 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* RTEventSpec. Created by shale on 06/25/2015.
*/
define(
["../../src/policies/RTMessagesViewPolicy"],
function (RTMessagesViewPolicy) {
describe("The real time Messages view policy", function () {
var testView,
mockDomainObject,
mockTelemetry,
testMetadata,
policy;
beforeEach(function () {
testView = { key: "rtmessages" };
testMetadata = {};
mockDomainObject = jasmine.createSpyObj(
'domainObject',
['getId', 'getModel', 'getCapability']
);
mockTelemetry = jasmine.createSpyObj(
'telemetry',
['getMetadata']
);
mockDomainObject.getCapability.andCallFake(function (c) {
return c === 'telemetry' ? mockTelemetry : undefined;
});
mockTelemetry.getMetadata.andReturn(testMetadata);
policy = new RTMessagesViewPolicy();
});
it("allows the real time messages view for domain objects with string telemetry", function () {
testMetadata.ranges = [ { key: "foo", format: "string" } ];
expect(policy.allow(testView, mockDomainObject)).toBeTruthy();
});
it("disallows the real time messages view for domain objects without string telemetry", function () {
testMetadata.ranges = [ { key: "foo", format: "somethingElse" } ];
expect(policy.allow(testView, mockDomainObject)).toBeFalsy();
});
it("disallows the real time messages view for domain objects without telemetry", function () {
testMetadata.ranges = [ { key: "foo", format: "string" } ];
mockDomainObject.getCapability.andReturn(undefined);
expect(policy.allow(testView, mockDomainObject)).toBeFalsy();
});
it("allows other views", function () {
testView.key = "somethingElse";
testMetadata.ranges = [ { key: "foo", format: "somethingElse" } ];
expect(policy.allow(testView, mockDomainObject)).toBeTruthy();
});
});
}
);

View File

@@ -1,2 +0,0 @@
This is a placeholder implementation of a Scrolling List view
which is compatible with realtime-only data.

View File

@@ -1,63 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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([
"./src/RTScrollingListController",
"text!./res/templates/rtscrolling.html",
'legacyRegistry'
], function (
RTScrollingListController,
rtscrollingTemplate,
legacyRegistry
) {
legacyRegistry.register("platform/features/rtscrolling", {
"name": "Scrolling Lists",
"description": "Time-ordered list of latest data.",
"extensions": {
"views": [
{
"key": "scrolling",
"name": "Scrolling",
"glyph": "5",
"description": "Scrolling list of data values.",
"template": rtscrollingTemplate,
"needs": [
"telemetry"
],
"delegation": true
}
],
"controllers": [
{
"key": "RTScrollingListController",
"implementation": RTScrollingListController,
"depends": [
"$scope",
"telemetryHandler",
"telemetryFormatter"
]
}
]
}
});
});

View File

@@ -1,51 +0,0 @@
<!--
Open MCT Web, Copyright (c) 2014-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web 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 Web 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.
-->
<div class="w1">
<div class="w2"
ng-controller="RTScrollingListController as rtscroll">
<!-- To add filtering, add class 'filterable' to <table> and uncomment 2nd <tr> in <thead> -->
<table class="tabular">
<thead>
<tr>
<th ng-repeat="header in rtscroll.headers()">
{{header}}
</th>
</tr>
<!--tr>
<th ng-repeat="header in headers">
<input type="text" />
</th>
</tr-->
</thead>
<tbody>
<tr ng-repeat="row in rtscroll.rows()">
<td ng-repeat="cell in row"
ng-class="cell.cssClass">
{{cell.text}}
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@@ -1,71 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
/**
* A column which will report telemetry domain values
* (typically, timestamps.) Used by the ScrollingListController.
*
* @memberof platform/features/rtscrolling
* @constructor
* @param domainMetadata an object with the machine- and human-
* readable names for this domain (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function DomainColumn(telemetryFormatter) {
return {
/**
* Get the title to display in this column's header.
* @returns {string} the title to display
* @memberof platform/features/rtscrolling.DomainColumn#
*/
getTitle: function () {
return "Time";
},
/**
* Get the text to display inside a row under this
* column.
* @returns {string} the text to display
* @memberof platform/features/rtscrolling.DomainColumn#
*/
getValue: function (domainObject, handle) {
return {
text: telemetryFormatter.formatDomainValue(
handle.getDomainValue(domainObject)
)
};
}
};
}
return DomainColumn;
}
);

View File

@@ -1,64 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* Module defining NameColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
/**
* A column which will report the name of the domain object
* which exposed specific telemetry values.
*
* @memberof platform/features/rtscrolling
* @constructor
*/
function NameColumn() {
return {
/**
* Get the title to display in this column's header.
* @returns {string} the title to display
* @memberof platform/features/rtscrolling.NameColumn#
*/
getTitle: function () {
return "Name";
},
/**
* Get the text to display inside a row under this
* column. This returns the domain object's name.
* @returns {string} the text to display
* @memberof platform/features/rtscrolling.NameColumn#
*/
getValue: function (domainObject) {
return {
text: domainObject.getModel().name
};
}
};
}
return NameColumn;
}
);

View File

@@ -1,137 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* Module defining ListController. Created by vwoeltje on 11/18/14.
*/
define(
["./NameColumn", "./DomainColumn", "./RangeColumn"],
function (NameColumn, DomainColumn, RangeColumn) {
var ROW_COUNT = 100;
/**
* The RTScrollingListController is responsible for populating
* the contents of the scrolling list view.
* @memberof platform/features/rtscrolling
* @constructor
*/
function RTScrollingListController($scope, telemetryHandler, telemetryFormatter) {
var handle,
lastUpdated = {},
lastIds = [],
columns = [],
headers = [],
rows = [];
function getTelemetryObjects() {
return handle ? handle.getTelemetryObjects() : [];
}
function idsChanged(telemetryObjects) {
function mismatch(id, index) {
return id !== telemetryObjects[index].getId();
}
return lastIds.length !== telemetryObjects.length ||
lastIds.some(mismatch);
}
function setupColumns(telemetryObjects) {
var id = $scope.domainObject && $scope.domainObject.getId(),
firstId =
telemetryObjects[0] && telemetryObjects[0].getId();
columns = [];
if (telemetryObjects > 1 || id !== firstId) {
columns.push(new NameColumn());
}
columns.push(new DomainColumn(telemetryFormatter));
columns.push(new RangeColumn());
headers = columns.map(function (column) {
return column.getTitle();
});
}
function updateObjects(telemetryObjects) {
if (idsChanged(telemetryObjects)) {
setupColumns(telemetryObjects);
lastIds = telemetryObjects.map(function (telemetryObject) {
return telemetryObject.getId();
});
}
}
function addRow(telemetryObject) {
var id = telemetryObject.getId(),
domainValue = handle.getDomainValue(telemetryObject);
if (lastUpdated[id] !== domainValue &&
domainValue !== undefined) {
rows.unshift(columns.map(function (column) {
return column.getValue(telemetryObject, handle);
}));
rows.splice(ROW_COUNT, Number.MAX_VALUE);
lastUpdated[id] = domainValue;
}
}
function updateValues() {
getTelemetryObjects().forEach(addRow);
}
function releaseSubscription() {
if (handle) {
handle.unsubscribe();
}
}
function makeSubscription(domainObject) {
releaseSubscription();
rows = [];
handle = telemetryHandler.handle(
domainObject,
updateValues,
true
);
}
$scope.$on("$destroy", releaseSubscription);
$scope.$watch("domainObject", makeSubscription);
$scope.$watch(getTelemetryObjects, updateObjects);
return {
rows: function () {
return rows;
},
headers: function () {
return headers;
}
};
}
return RTScrollingListController;
}
);

View File

@@ -1,85 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
/**
* A column which will report telemetry range values
* (typically, measurements.) Used by the RTScrollingListController.
*
* @memberof platform/features/rtscrolling
* @constructor
* @param rangeMetadata an object with the machine- and human-
* readable names for this range (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function RangeColumn() {
function findRange(domainObject) {
var telemetry = domainObject.getCapability('telemetry'),
metadata = telemetry ? telemetry.getMetadata() : {},
ranges = metadata.ranges || [{}];
return ranges[0].key;
}
return {
/**
* Get the title to display in this column's header.
* @returns {string} the title to display
* @memberof platform/features/rtscrolling.RangeColumn#
*/
getTitle: function () {
return "Value";
},
/**
* Get the text to display inside a row under this
* column.
* @returns {string} the text to display
* @memberof platform/features/rtscrolling.RangeColumn#
*/
getValue: function (domainObject, handle) {
var range = findRange(domainObject),
limit = domainObject.getCapability('limit'),
value = handle.getRangeValue(domainObject),
alarm = limit && limit.evaluate(
handle.getDatum(domainObject),
range
);
return {
cssClass: alarm && alarm.cssClass,
text: value
};
}
};
}
return RangeColumn;
}
);

View File

@@ -1,62 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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([
"./src/ScrollingListController",
"text!./res/templates/scrolling.html",
'legacyRegistry'
], function (
ScrollingListController,
scrollingTemplate,
legacyRegistry
) {
legacyRegistry.register("platform/features/scrolling", {
"name": "Scrolling Lists",
"description": "Time-ordered list of latest data.",
"extensions": {
"views": [
{
"key": "scrolling",
"name": "Scrolling",
"glyph": "5",
"description": "Scrolling list of data values.",
"template": scrollingTemplate,
"needs": [
"telemetry"
],
"delegation": true
}
],
"controllers": [
{
"key": "ScrollingListController",
"implementation": ScrollingListController,
"depends": [
"$scope",
"telemetryFormatter"
]
}
]
}
});
});

View File

@@ -1,51 +0,0 @@
<!--
Open MCT Web, Copyright (c) 2014-2015, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT Web 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 Web 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.
-->
<div class="w1" ng-controller="TelemetryController as telemetry">
<div class="w2"
ng-controller="ScrollingListController">
<!-- To add filtering, add class 'filterable' to <table> and uncomment 2nd <tr> in <thead> -->
<table class="tabular">
<thead>
<tr>
<th ng-repeat="header in headers">
{{header}}
</th>
</tr>
<!--tr>
<th ng-repeat="header in headers">
<input type="text" />
</th>
</tr-->
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="cell in row"
ng-class="cell.cssClass">
{{cell.text}}
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@@ -1,63 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
/**
* A column which will report telemetry domain values
* (typically, timestamps.) Used by the ScrollingListController.
*
* @memberof platform/features/scrolling
* @implements {platform/features/scrolling.ScrollingColumn}
* @constructor
* @param domainMetadata an object with the machine- and human-
* readable names for this domain (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function DomainColumn(domainMetadata, telemetryFormatter) {
this.domainMetadata = domainMetadata;
this.telemetryFormatter = telemetryFormatter;
}
DomainColumn.prototype.getTitle = function () {
return this.domainMetadata.name;
};
DomainColumn.prototype.getValue = function (domainObject, datum) {
return {
text: this.telemetryFormatter.formatDomainValue(
datum[this.domainMetadata.key],
this.domainMetadata.format
)
};
};
return DomainColumn;
}
);

View File

@@ -1,66 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
/**
* A column which will report telemetry range values
* (typically, measurements.) Used by the ScrollingListController.
*
* @memberof platform/features/scrolling
* @implements {platform/features/scrolling.ScrollingColumn}
* @constructor
* @param rangeMetadata an object with the machine- and human-
* readable names for this range (in `key` and `name`
* fields, respectively.)
* @param {TelemetryFormatter} telemetryFormatter the telemetry
* formatting service, for making values human-readable.
*/
function RangeColumn(rangeMetadata, telemetryFormatter) {
this.rangeMetadata = rangeMetadata;
this.telemetryFormatter = telemetryFormatter;
}
RangeColumn.prototype.getTitle = function () {
return this.rangeMetadata.name;
};
RangeColumn.prototype.getValue = function (domainObject, datum) {
var range = this.rangeMetadata.key,
limit = domainObject.getCapability('limit'),
value = datum[range],
alarm = limit && limit.evaluate(datum, range);
return {
cssClass: alarm && alarm.cssClass,
text: this.telemetryFormatter.formatRangeValue(value)
};
};
return RangeColumn;
}
);

View File

@@ -1,157 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* This bundle implements a "Scrolling List" view of telemetry data.
* @namespace platform/features/scrolling
*/
define(
["./NameColumn", "./DomainColumn", "./RangeColumn", "./ScrollingListPopulator"],
function (NameColumn, DomainColumn, RangeColumn, ScrollingListPopulator) {
var ROW_COUNT = 18;
/**
* The ScrollingListController is responsible for populating
* the contents of the scrolling list view.
* @memberof platform/features/scrolling
* @constructor
*/
function ScrollingListController($scope, formatter) {
var populator = new ScrollingListPopulator([]);
// Get a set of populated, ready-to-display rows for the
// latest data values.
function getRows(telemetry) {
var datas = telemetry.getResponse(),
objects = telemetry.getTelemetryObjects();
return populator.getRows(datas, objects, ROW_COUNT);
}
// Update the contents
function updateRows() {
var telemetry = $scope.telemetry;
$scope.rows = telemetry ? getRows(telemetry) : [];
}
// Set up columns based on telemetry metadata. This will
// include one column for each domain and range type, as
// well as a column for the domain object name.
function setupColumns(metadatas) {
var domainKeys = {},
rangeKeys = {},
columns = [];
// Add a domain to the set of columns, if a domain
// with the same key has not yet been inclued.
function addDomain(domain) {
var key = domain.key;
if (key && !domainKeys[key]) {
domainKeys[key] = true;
columns.push(new DomainColumn(domain, formatter));
}
}
// Add a range to the set of columns, if a range
// with the same key has not yet been inclued.
function addRange(range) {
var key = range.key;
if (key && !rangeKeys[key]) {
rangeKeys[key] = true;
columns.push(new RangeColumn(range, formatter));
}
}
// We cannot proceed if metadata is not available;
// clear all rows/columns.
if (!Array.isArray(metadatas)) {
columns = [];
$scope.rows = [];
$scope.headers = [];
return;
}
columns = [ new NameColumn() ];
// Add domain, range columns
metadatas.forEach(function (metadata) {
(metadata.domains || []).forEach(addDomain);
});
metadatas.forEach(function (metadata) {
(metadata.ranges || []).forEach(addRange);
});
// Add default domain, range columns if none
// were described in metadata.
if (Object.keys(domainKeys).length < 1) {
columns.push(new DomainColumn({name: "Time"}, formatter));
}
if (Object.keys(rangeKeys).length < 1) {
columns.push(new RangeColumn({name: "Value"}, formatter));
}
// We have all columns now; use them to initializer
// the populator, which will use them to generate
// actual rows and headers.
populator = new ScrollingListPopulator(columns);
// Initialize headers
$scope.headers = populator.getHeaders();
// Fill in the contents of the rows.
updateRows();
}
$scope.$on("telemetryUpdate", updateRows);
$scope.$watch("telemetry.getMetadata()", setupColumns);
}
/**
* A description of how to display a certain column of data in a
* Scrolling List view.
* @interface platform/features/scrolling.ScrollingColumn
* @private
*/
/**
* Get the title to display in this column's header.
* @returns {string} the title to display
* @method platform/features/scrolling.ScrollingColumn#getTitle
*/
/**
* Get the text to display inside a row under this
* column.
* @param {DomainObject} domainObject the domain object associated
* with this row
* @param {TelemetrySeries} series the telemetry data associated
* with this row
* @param {number} index the index of the telemetry datum associated
* with this row
* @returns {string} the text to display
* @method platform/features/scrolling.ScrollingColumn#getValue
*/
return ScrollingListController;
}
);

View File

@@ -1,187 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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 () {
/**
* The ScrollingListPopulator is responsible for filling in the
* values which should appear within columns of a scrolling list
* view, based on received telemetry data.
* @memberof platform/features/scrolling
* @constructor
* @param {Column[]} columns the columns to be populated
*/
function ScrollingListPopulator(columns) {
this.columns = columns;
}
/**
* Look up the most recent values from a set of data objects.
* Returns an array of objects in the order in which data
* should be displayed; each element is an object with
* two properties:
*
* * objectIndex: The index of the domain object associated
* with the data point to be displayed in that
* row.
* * pointIndex: The index of the data point itself, within
* its data set.
*
* @param {Array<Telemetry>} datas an array of the most recent
* data objects; expected to be in the same order
* as the domain objects provided at constructor
* @param {number} count the number of rows to provide
* @returns {Array} latest data values in display order
* @private
*/
function getLatestDataValues(datas, count) {
var latest = [],
candidate,
candidateTime,
used = datas.map(function () { return 0; });
// This algorithm is O(nk) for n rows and k telemetry elements;
// one O(k) linear search for a max is made for each of n rows.
// This could be done in O(n lg k + k lg k), using a priority
// queue (where priority is max-finding) containing k initial
// values. For n rows, pop the max from the queue and replenish
// the queue with a value from the data at the same
// objectIndex, if available.
// But k is small, so this might not give an observable
// improvement in performance.
// Find the most recent unused data point (this will be used
// in a loop to find and the N most recent data points)
function findCandidate(data, i) {
var nextTime,
pointCount = data.getPointCount(),
pointIndex = pointCount - used[i] - 1;
if (data && pointIndex >= 0) {
nextTime = data.getDomainValue(pointIndex);
if (nextTime > candidateTime) {
candidateTime = nextTime;
candidate = {
objectIndex: i,
pointIndex: pointIndex
};
}
}
}
// Assemble a list of the most recent data points
while (latest.length < count) {
// Reset variables pre-search
candidateTime = Number.NEGATIVE_INFINITY;
candidate = undefined;
// Linear search for most recent
datas.forEach(findCandidate);
if (candidate) {
// Record this data point - it is the most recent
latest.push(candidate);
// Track the data points used so we can look farther back
// in the data set on the next iteration
used[candidate.objectIndex] = used[candidate.objectIndex] + 1;
} else {
// Ran out of candidates; not enough data points
// available to fill all rows.
break;
}
}
return latest;
}
/**
* Get the text which should appear in headers for the
* provided columns.
* @returns {string[]} column headers
*/
ScrollingListPopulator.prototype.getHeaders = function () {
return this.columns.map(function (column) {
return column.getTitle();
});
};
/**
* Get the contents of rows for the scrolling list view.
* @param {TelemetrySeries[]} datas the data sets
* @param {DomainObject[]} objects the domain objects which
* provided the data sets; these should match
* index-to-index with the `datas` argument
* @param {number} count the number of rows to populate
* @returns {string[][]} an array of rows, each of which
* is an array of values which should appear
* in that row
*/
ScrollingListPopulator.prototype.getRows = function (datas, objects, count) {
var values = getLatestDataValues(datas, count),
self = this;
// From a telemetry series, retrieve a single data point
// containing all fields for domains/ranges
function makeDatum(domainObject, series, index) {
var telemetry = domainObject.getCapability('telemetry'),
metadata = telemetry ? telemetry.getMetadata() : {},
result = {};
(metadata.domains || []).forEach(function (domain) {
result[domain.key] =
series.getDomainValue(index, domain.key);
});
(metadata.ranges || []).forEach(function (range) {
result[range.key] =
series.getRangeValue(index, range.key);
});
return result;
}
// Each value will become a row, which will contain
// some value in each column (rendering by the
// column object itself)
return values.map(function (value) {
var datum = makeDatum(
objects[value.objectIndex],
datas[value.objectIndex],
value.pointIndex
);
return self.columns.map(function (column) {
return column.getValue(
objects[value.objectIndex],
datum
);
});
});
};
return ScrollingListPopulator;
}
);

View File

@@ -1,82 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../src/DomainColumn"],
function (DomainColumn) {
var TEST_DOMAIN_VALUE = "some formatted domain value";
describe("A domain column", function () {
var mockDataSet,
testMetadata,
mockFormatter,
column;
beforeEach(function () {
mockDataSet = jasmine.createSpyObj(
"data",
[ "getDomainValue" ]
);
mockFormatter = jasmine.createSpyObj(
"formatter",
[ "formatDomainValue", "formatRangeValue" ]
);
testMetadata = {
key: "testKey",
name: "Test Name"
};
mockFormatter.formatDomainValue.andReturn(TEST_DOMAIN_VALUE);
column = new DomainColumn(testMetadata, mockFormatter);
});
it("reports a column header from domain metadata", function () {
expect(column.getTitle()).toEqual("Test Name");
});
xit("looks up data from a data set", function () {
column.getValue(undefined, mockDataSet, 42);
expect(mockDataSet.getDomainValue)
.toHaveBeenCalledWith(42, "testKey");
});
xit("formats domain values as time", function () {
mockDataSet.getDomainValue.andReturn(402513731000);
// Should have just given the value the formatter gave
expect(column.getValue(undefined, mockDataSet, 42).text)
.toEqual(TEST_DOMAIN_VALUE);
// Make sure that service interactions were as expected
expect(mockFormatter.formatDomainValue)
.toHaveBeenCalledWith(402513731000);
expect(mockFormatter.formatRangeValue)
.not.toHaveBeenCalled();
});
});
}
);

View File

@@ -1,74 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../src/RangeColumn"],
function (RangeColumn) {
var TEST_RANGE_VALUE = "some formatted range value";
describe("A range column", function () {
var testDatum,
testMetadata,
mockFormatter,
mockDomainObject,
column;
beforeEach(function () {
testDatum = { testKey: 123, otherKey: 456 };
mockFormatter = jasmine.createSpyObj(
"formatter",
[ "formatDomainValue", "formatRangeValue" ]
);
testMetadata = {
key: "testKey",
name: "Test Name"
};
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[ "getModel", "getCapability" ]
);
mockFormatter.formatRangeValue.andReturn(TEST_RANGE_VALUE);
column = new RangeColumn(testMetadata, mockFormatter);
});
it("reports a column header from range metadata", function () {
expect(column.getTitle()).toEqual("Test Name");
});
it("formats range values as numbers", function () {
expect(column.getValue(mockDomainObject, testDatum).text)
.toEqual(TEST_RANGE_VALUE);
// Make sure that service interactions were as expected
expect(mockFormatter.formatRangeValue)
.toHaveBeenCalledWith(testDatum.testKey);
expect(mockFormatter.formatDomainValue)
.not.toHaveBeenCalled();
});
});
}
);

View File

@@ -1,108 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../src/ScrollingListController"],
function (ScrollingListController) {
describe("The scrolling list controller", function () {
var mockScope,
mockTelemetry,
testMetadata,
controller;
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
[ "$on", "$watch" ]
);
mockTelemetry = jasmine.createSpyObj(
"telemetryController",
[ "getResponse", "getMetadata", "getTelemetryObjects" ]
);
testMetadata = [
{
domains: [
{ key: "d0", name: "D0" },
{ key: "d1", name: "D1" }
],
ranges: [
{ key: "r0", name: "R0" },
{ key: "r1", name: "R1" }
]
},
{
domains: [
{ key: "d0", name: "D0" },
{ key: "d2", name: "D2" }
],
ranges: [
{ key: "r0", name: "R0" }
]
}
];
mockTelemetry.getMetadata.andReturn(testMetadata);
mockTelemetry.getResponse.andReturn([]);
mockTelemetry.getTelemetryObjects.andReturn([]);
mockScope.telemetry = mockTelemetry;
controller = new ScrollingListController(mockScope);
});
it("listens for telemetry data updates", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
"telemetryUpdate",
jasmine.any(Function)
);
});
xit("watches for telemetry controller changes", function () {
expect(mockScope.$watch).toHaveBeenCalledWith(
"telemetry",
jasmine.any(Function)
);
});
xit("provides a column for each name and each unique domain, range", function () {
// Should have six columns based on metadata above,
// (name, d0, d1, d2, r0, r1)
mockScope.$watch.mostRecentCall.args[1](mockTelemetry);
expect(mockScope.headers).toEqual(["Name", "D0", "D1", "D2", "R0", "R1"]);
});
it("does not throw if telemetry controller is undefined", function () {
// Just a general robustness check
mockScope.telemetry = undefined;
expect(mockScope.$watch.mostRecentCall.args[1])
.not.toThrow();
});
xit("provides default columns if domain/range metadata is unavailable", function () {
mockTelemetry.getMetadata.andReturn([]);
mockScope.$watch.mostRecentCall.args[1](mockTelemetry);
expect(mockScope.headers).toEqual(["Name", "Time", "Value"]);
});
});
}
);

View File

@@ -1,103 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/**
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../src/ScrollingListPopulator"],
function (ScrollingListPopulator) {
describe("The scrolling list populator", function () {
var mockColumns,
mockDatas,
mockDomainObjects,
populator;
function makeMockColumn(name, index) {
var mockColumn = jasmine.createSpyObj(
"column" + index,
[ "getTitle", "getValue" ]
);
mockColumn.getTitle.andReturn(name);
mockColumn.getValue.andCallFake(function (obj, data, i) {
return data.getDomainValue(i);
});
return mockColumn;
}
function makeMockData(bias, index) {
var mockData = jasmine.createSpyObj(
"data" + index,
[ "getDomainValue", "getPointCount" ]
);
mockData.getPointCount.andReturn(1000);
mockData.getDomainValue.andCallFake(function (i) {
return i + bias;
});
return mockData;
}
function makeMockDomainObject(name, index) {
var mockDomainObject = jasmine.createSpyObj(
"domainObject" + index,
[ "getId", "getModel" ]
);
return mockDomainObject;
}
beforeEach(function () {
mockColumns = ["A", "B", "C", "D"].map(makeMockColumn);
mockDatas = [ 10, 0, 3 ].map(makeMockData);
mockDomainObjects = ["A", "B", "C"].map(makeMockDomainObject);
populator = new ScrollingListPopulator(mockColumns);
});
it("returns column headers", function () {
expect(populator.getHeaders()).toEqual(["A", "B", "C", "D"]);
});
xit("provides rows on request, with all columns in each row", function () {
var rows = populator.getRows(mockDatas, mockDomainObjects, 84);
expect(rows.length).toEqual(84);
rows.forEach(function (row) {
expect(row.length).toEqual(4); // number of columns
});
});
xit("returns rows in reverse domain order", function () {
var rows = populator.getRows(mockDatas, mockDomainObjects, 84),
previous = Number.POSITIVE_INFINITY;
// Should always be most-recent-first; since the mockColumn
// returns the domain value, column contents should be
// non-increasing.
rows.forEach(function (row) {
expect(row[0]).not.toBeGreaterThan(previous);
previous = row[0];
});
});
});
}
);

View File

@@ -22,6 +22,7 @@
define([
"./src/directives/MCTTable",
"./src/controllers/RTTelemetryTableController",
"./src/controllers/TelemetryTableController",
"./src/controllers/TableOptionsController",
'../../commonUI/regions/src/Region',
@@ -29,6 +30,7 @@ define([
"legacyRegistry"
], function (
MCTTable,
RTTelemetryTableController,
TelemetryTableController,
TableOptionsController,
Region,
@@ -57,9 +59,10 @@ define([
"types": [
{
"key": "table",
"name": "Table",
"glyph": "\ue605",
"description": "A table for displaying telemetry data",
"name": "Historical Telemetry Table",
"glyph": "\ue604",
"description": "A static table of all values over time for all included telemetry elements. Rows are timestamped data values for each telemetry element; columns are data fields. The number of rows is based on the range of your query. New incoming data must be manually re-queried for.",
"priority": 861,
"features": "creation",
"delegates": [
"telemetry"
@@ -76,6 +79,30 @@ define([
"views": [
"table"
]
},
{
"key": "rttable",
"name": "Real-time Telemetry Table",
"glyph": "\ue620",
"description": "A scrolling table of latest values for all included telemetry elements. Rows are timestamped data values for each telemetry element; columns are data fields. New incoming data is automatically added to the view.",
"priority": 860,
"features": "creation",
"delegates": [
"telemetry"
],
"inspector": tableInspector,
"contains": [
{
"has": "telemetry"
}
],
"model": {
"composition": []
},
"views": [
"rt-table",
"scrolling-table"
]
}
],
"controllers": [
@@ -84,6 +111,11 @@ define([
"implementation": TelemetryTableController,
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
},
{
"key": "RTTelemetryTableController",
"implementation": RTTelemetryTableController,
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
},
{
"key": "TableOptionsController",
"implementation": TableOptionsController,
@@ -93,15 +125,26 @@ define([
],
"views": [
{
"name": "Table",
"name": "Historical Table",
"key": "table",
"glyph": "\ue605",
"glyph": "\ue604",
"templateUrl": "templates/table.html",
"needs": [
"telemetry"
],
"delegation": true,
"editable": true
},
{
"name": "Real-time Table",
"key": "rt-table",
"glyph": "\ue620",
"templateUrl": "templates/rt-table.html",
"needs": [
"telemetry"
],
"delegation": true,
"editable": true
}
],
"directives": [

View File

@@ -1,10 +1,13 @@
<div class="l-view-section scrolling"
style="overflow: auto;"
ng-style="overrideRowPositioning ?
{'overflow': 'auto'} :
{'overflow': 'scroll'}"
>
<table class="filterable"
ng-style="overrideRowPositioning && {
height: totalHeight + 'px',
'table-layout': overrideRowPositioning ? 'fixed' : 'auto'
'table-layout': overrideRowPositioning ? 'fixed' : 'auto',
'max-width': totalWidth
}">
<thead>
<tr>
@@ -59,6 +62,5 @@
</td>
</tr>
</tbody>
</table>
</div>

View File

@@ -0,0 +1,9 @@
<div ng-controller="RTTelemetryTableController">
<mct-table
headers="headers"
rows="rows"
enableFilter="true"
enableSort="true"
auto-scroll="autoScroll">
</mct-table>
</div>

View File

@@ -19,12 +19,12 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div ng-controller="TableOptionsController" class="flex-elem grows l-inspector-part">
<em class="t-inspector-part-header" title="Display properties for this object">Display</em>
<div ng-controller="TableOptionsController" class="l-controls-first flex-elem grows l-inspector-part">
<em class="t-inspector-part-header" title="Display properties for this object">Table Options</em>
<mct-form
ng-model="configuration.table.columns"
structure="columnsForm"
name="columnsFormState"
class="flex-elem l-flex-row no-validate no-margin reduced-min-width">
class="flex-elem l-flex-row no-validate no-margin">
</mct-form>
</div>

View File

@@ -45,26 +45,28 @@ define(
* @param metadata Metadata describing the domains and ranges available
* @returns {TableConfiguration} This object
*/
TableConfiguration.prototype.buildColumns = function(metadata) {
TableConfiguration.prototype.buildColumns = function (metadata) {
var self = this;
this.columns = [];
if (metadata) {
if (metadata.length > 1){
self.addColumn(new NameColumn(), 0);
}
metadata.forEach(function (metadatum) {
//Push domains first
(metadatum.domains || []).forEach(function (domainMetadata) {
self.addColumn(new DomainColumn(domainMetadata, self.telemetryFormatter));
self.addColumn(new DomainColumn(domainMetadata,
self.telemetryFormatter));
});
(metadatum.ranges || []).forEach(function (rangeMetadata) {
self.addColumn(new RangeColumn(rangeMetadata, self.telemetryFormatter));
self.addColumn(new RangeColumn(rangeMetadata,
self.telemetryFormatter));
});
});
if (this.columns.length > 0){
self.addColumn(new NameColumn(), 0);
}
}
return this;
};
@@ -96,7 +98,7 @@ define(
* Get a simple list of column titles
* @returns {Array} The titles of the columns
*/
TableConfiguration.prototype.getHeaders = function() {
TableConfiguration.prototype.getHeaders = function () {
var self = this;
return this.columns.map(function (column, i){
return self.getColumnTitle(column) || 'Column ' + (i + 1);
@@ -111,9 +113,9 @@ define(
* @returns {Object} Key value pairs where the key is the column
* title, and the value is the formatted value from the provided datum.
*/
TableConfiguration.prototype.getRowValues = function(telemetryObject, datum) {
TableConfiguration.prototype.getRowValues = function (telemetryObject, datum) {
var self = this;
return this.columns.reduce(function(rowObject, column, i){
return this.columns.reduce(function (rowObject, column, i){
var columnTitle = self.getColumnTitle(column) || 'Column ' + (i + 1),
columnValue = column.getValue(telemetryObject, datum);
@@ -123,7 +125,9 @@ define(
// Don't replace something with nothing.
// This occurs when there are multiple columns with the
// column title
if (rowObject[columnTitle] === undefined || rowObject[columnTitle].text === undefined || rowObject[columnTitle].text.length === 0) {
if (rowObject[columnTitle] === undefined ||
rowObject[columnTitle].text === undefined ||
rowObject[columnTitle].text.length === 0) {
rowObject[columnTitle] = columnValue;
}
return rowObject;
@@ -134,7 +138,8 @@ define(
* @private
*/
TableConfiguration.prototype.defaultColumnConfiguration = function () {
return ((this.domainObject.getModel().configuration || {}).table || {}).columns || {};
return ((this.domainObject.getModel().configuration || {}).table ||
{}).columns || {};
};
/**
@@ -156,7 +161,7 @@ define(
* pairs where the key is the column title, and the value is a
* boolean indicating whether the column should be shown.
*/
TableConfiguration.prototype.getColumnConfiguration = function() {
TableConfiguration.prototype.getColumnConfiguration = function () {
var configuration = {},
//Use existing persisted config, or default it
defaultConfig = this.defaultColumnConfiguration();
@@ -166,8 +171,10 @@ define(
* specifying whether the column is visible or not. Default to
* existing (persisted) configuration if available
*/
this.getHeaders().forEach(function(columnTitle) {
configuration[columnTitle] = typeof defaultConfig[columnTitle] === 'undefined' ? true : defaultConfig[columnTitle];
this.getHeaders().forEach(function (columnTitle) {
configuration[columnTitle] =
typeof defaultConfig[columnTitle] === 'undefined' ? true :
defaultConfig[columnTitle];
});
return configuration;

View File

@@ -3,6 +3,15 @@ define(
[],
function () {
/**
* A controller for the MCTTable directive. Populates scope with
* data used for populating, sorting, and filtering
* tables.
* @param $scope
* @param $timeout
* @param element
* @constructor
*/
function MCTTableController($scope, $timeout, element) {
var self = this;
@@ -11,6 +20,9 @@ define(
this.$timeout = $timeout;
this.maxDisplayRows = 50;
this.scrollable = element.find('div');
this.scrollable.on('scroll', this.onScroll.bind(this));
$scope.visibleRows = [];
$scope.overrideRowPositioning = false;
@@ -31,8 +43,6 @@ define(
setDefaults($scope);
element.find('div').on('scroll', this.onScroll.bind(this));
$scope.toggleSort = function (key) {
if (!$scope.enableSort) {
return;
@@ -49,22 +59,96 @@ define(
self.updateRows($scope.rows);
};
/*
* Define watches to listen for changes to headers and rows.
*/
$scope.$watchCollection('filters', function () {
self.updateRows(self.$scope.rows);
self.updateRows($scope.rows);
});
$scope.$watchCollection('headers', this.updateHeaders.bind(this));
$scope.$watchCollection('rows', this.updateRows.bind(this));
$scope.$watch('headers', this.updateHeaders.bind(this));
$scope.$watch('rows', this.updateRows.bind(this));
/*
* Listen for rows added individually (eg. for real-time tables)
*/
$scope.$on('add:row', this.newRow.bind(this));
$scope.$on('remove:row', this.removeRow.bind(this));
}
/**
* On scroll, calculate which rows indexes are visible and
* ensure that an equal number of rows are preloaded for
* scrolling in either direction.
* If auto-scroll is enabled, this function will scroll to the
* bottom of the page
* @private
*/
MCTTableController.prototype.scrollToBottom = function () {
var self = this;
//Use timeout to defer execution until next digest when any
// pending UI changes have completed, eg. a new row in the table.
if (this.$scope.autoScroll) {
this.$timeout(function (){
self.scrollable[0].scrollTop = self.scrollable[0].scrollHeight;
});
}
};
/**
* Handles a row add event. Rows can be added as needed using the
* `addRow` broadcast event.
* @private
*/
MCTTableController.prototype.newRow = function (event, rowIndex) {
var row = this.$scope.rows[rowIndex];
//Add row to the filtered, sorted list of all rows
if (this.filterRows([row]).length > 0) {
this.insertSorted(this.$scope.displayRows, row);
}
this.$timeout(this.setElementSizes.bind(this))
.then(this.scrollToBottom.bind(this));
};
/**
* Handles a row add event. Rows can be added as needed using the
* `addRow` broadcast event.
* @private
*/
MCTTableController.prototype.removeRow = function (event, rowIndex) {
var row = this.$scope.rows[rowIndex],
// Do a sequential search here. Only way of finding row is by
// object equality, so array is in effect unsorted.
indexInDisplayRows = this.$scope.displayRows.indexOf(row);
if (indexInDisplayRows != -1) {
this.$scope.displayRows.splice(indexInDisplayRows, 1);
this.setVisibleRows();
}
};
/**
* @private
*/
MCTTableController.prototype.onScroll = function (event) {
//If user scrolls away from bottom, disable auto-scroll.
// Auto-scroll will be re-enabled if user scrolls to bottom again.
if (this.scrollable[0].scrollTop <
(this.scrollable[0].scrollHeight - this.scrollable[0].offsetHeight)) {
this.$scope.autoScroll = false;
} else {
this.$scope.autoScroll = true;
}
this.setVisibleRows();
this.$scope.$digest();
};
/**
* Sets visible rows based on array
* content and current scroll state.
*/
MCTTableController.prototype.setVisibleRows = function () {
var self = this,
topScroll = event.target.scrollTop,
bottomScroll = topScroll + event.target.offsetHeight,
target = this.scrollable[0],
topScroll = target.scrollTop,
bottomScroll = topScroll + target.offsetHeight,
firstVisible,
lastVisible,
totalVisible,
@@ -72,42 +156,59 @@ define(
start,
end;
//No need to scroll
if (this.$scope.displayRows.length < this.maxDisplayRows) {
return;
}
if (topScroll < this.$scope.headerHeight) {
firstVisible = 0;
//Check whether need to resynchronize visible with display
// rows (if data added)
if (this.$scope.visibleRows.length !=
this.$scope.displayRows.length){
start = 0;
end = this.$scope.displayRows.length;
} else {
//Data is in sync, and no need to calculate scroll,
// so do nothing.
return;
}
} else {
firstVisible = Math.floor(
(topScroll - this.$scope.headerHeight) / this.$scope.rowHeight
//rows has exceeded display maximum, so may be necessary to
// scroll
if (topScroll < this.$scope.headerHeight) {
firstVisible = 0;
} else {
firstVisible = Math.floor(
(topScroll - this.$scope.headerHeight) /
this.$scope.rowHeight
);
}
lastVisible = Math.ceil(
(bottomScroll - this.$scope.headerHeight) /
this.$scope.rowHeight
);
totalVisible = lastVisible - firstVisible;
numberOffscreen = this.maxDisplayRows - totalVisible;
start = firstVisible - Math.floor(numberOffscreen / 2);
end = lastVisible + Math.ceil(numberOffscreen / 2);
if (start < 0) {
start = 0;
end = Math.min(this.maxDisplayRows,
this.$scope.displayRows.length);
} else if (end >= this.$scope.displayRows.length) {
end = this.$scope.displayRows.length;
start = end - this.maxDisplayRows + 1;
}
if (this.$scope.visibleRows[0] &&
this.$scope.visibleRows[0].rowIndex === start &&
this.$scope.visibleRows[this.$scope.visibleRows.length - 1]
.rowIndex === end) {
return; // don't update if no changes are required.
}
}
lastVisible = Math.ceil(
(bottomScroll - this.$scope.headerHeight) / this.$scope.rowHeight
);
totalVisible = lastVisible - firstVisible;
numberOffscreen = this.maxDisplayRows - totalVisible;
start = firstVisible - Math.floor(numberOffscreen / 2);
end = lastVisible + Math.ceil(numberOffscreen / 2);
if (start < 0) {
start = 0;
end = this.$scope.visibleRows.length - 1;
} else if (end >= this.$scope.displayRows.length) {
end = this.$scope.displayRows.length - 1;
start = end - this.maxDisplayRows + 1;
}
if (this.$scope.visibleRows[0].rowIndex === start &&
this.$scope.visibleRows[this.$scope.visibleRows.length-1]
.rowIndex === end) {
return; // don't update if no changes are required.
}
//Set visible rows from display rows, based on calculated offset.
this.$scope.visibleRows = this.$scope.displayRows.slice(start, end)
.map(function(row, i) {
.map(function (row, i) {
return {
rowIndex: start + i,
offsetY: ((start + i) * self.$scope.rowHeight) +
@@ -115,8 +216,6 @@ define(
contents: row
};
});
this.$scope.$digest();
};
/**
@@ -134,10 +233,11 @@ define(
this.$scope.filters = {};
}
// Reset column sort information unless the new headers
// contain the column current sorted on.
if (this.$scope.enableSort && newHeaders.indexOf(this.$scope.sortColumn) === -1) {
this.$scope.sortColumn = undefined;
this.$scope.sortDirection = undefined;
// contain the column currently sorted on.
if (this.$scope.enableSort &&
newHeaders.indexOf(this.$scope.sortColumn) === -1) {
this.$scope.sortColumn = undefined;
this.$scope.sortDirection = undefined;
}
this.updateRows(this.$scope.rows);
};
@@ -153,80 +253,142 @@ define(
firstRow = tbody.find('tr'),
column = firstRow.find('td'),
headerHeight = thead.prop('offsetHeight'),
//row height is hard-coded for now.
rowHeight = 20,
overallHeight = headerHeight + (rowHeight * (this.$scope.displayRows ? this.$scope.displayRows.length - 1 : 0));
columnWidth,
tableWidth = 0,
overallHeight = headerHeight + (rowHeight *
(this.$scope.displayRows ? this.$scope.displayRows.length - 1 : 0));
this.$scope.columnWidths = [];
while (column.length) {
columnWidth = column.prop('offsetWidth');
this.$scope.columnWidths.push(column.prop('offsetWidth'));
tableWidth += columnWidth;
column = column.next();
}
this.$scope.headerHeight = headerHeight;
this.$scope.rowHeight = rowHeight;
this.$scope.totalHeight = overallHeight;
this.setVisibleRows();
this.$scope.visibleRows = this.$scope.displayRows.slice(0, this.maxDisplayRows).map(function(row, i) {
return {
rowIndex: i,
offsetY: (i * self.$scope.rowHeight) + self.$scope.headerHeight,
contents: row
};
});
if (tableWidth > 0) {
this.$scope.totalWidth = tableWidth + 'px';
} else {
this.$scope.totalWidth = 'none';
}
this.$scope.overrideRowPositioning = true;
};
/**
* @private
*/
MCTTableController.prototype.insertSorted = function (array, element) {
var index = -1,
self = this,
sortKey = this.$scope.sortColumn;
function binarySearch(searchArray, searchElement, min, max){
var sampleAt = Math.floor((max - min) / 2) + min,
valA,
valB;
if (max < min) {
return min; // Element is not in array, min gives direction
}
valA = isNaN(searchElement[sortKey].text) ?
searchElement[sortKey].text :
parseFloat(searchElement[sortKey].text);
valB = isNaN(searchArray[sampleAt][sortKey].text) ?
searchArray[sampleAt][sortKey].text :
parseFloat(searchArray[sampleAt][sortKey].text);
switch(self.sortComparator(valA, valB)) {
case -1:
return binarySearch(searchArray, searchElement, min,
sampleAt - 1);
case 0 :
return sampleAt;
case 1 :
return binarySearch(searchArray, searchElement,
sampleAt + 1, max);
}
}
if (!this.$scope.sortColumn || !this.$scope.sortDirection) {
//No sorting applied, push it on the end.
index = array.length;
} else {
//Sort is enabled, perform binary search to find insertion point
index = binarySearch(array, element, 0, array.length - 1);
}
if (index === -1){
array.unshift(element);
} else if (index === array.length){
array.push(element);
} else {
array.splice(index, 0, element);
}
};
/**
* Compare two variables, returning a number that represents
* which is larger. Similar to the default array sort
* comparator, but does not coerce all values to string before
* conversion. Strings are lowercased before comparison.
*
* @private
*/
MCTTableController.prototype.sortComparator = function (a, b) {
var result = 0,
sortDirectionMultiplier;
if (typeof a === "string" && typeof b === "string") {
a = a.toLowerCase();
b = b.toLowerCase();
}
if (a < b) {
result = -1;
}
if (a > b) {
result = 1;
}
if (this.$scope.sortDirection === 'asc') {
sortDirectionMultiplier = 1;
} else if (this.$scope.sortDirection === 'desc') {
sortDirectionMultiplier = -1;
}
return result * sortDirectionMultiplier;
};
/**
* Returns a new array which is a result of applying the sort
* criteria defined in $scope.
*
* Does not modify the array that was passed in.
*/
MCTTableController.prototype.sortRows = function(rowsToSort) {
/**
* Compare two variables, returning a number that represents
* which is larger. Similar to the default array sort
* comparator, but does not coerce all values to string before
* conversion. Strings are lowercased before comparison.
*/
function genericComparator(a, b) {
if (typeof a === "string" && typeof b === "string") {
a = a.toLowerCase();
b = b.toLowerCase();
}
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
MCTTableController.prototype.sortRows = function (rowsToSort) {
var self = this,
sortKey = this.$scope.sortColumn;
if (!this.$scope.sortColumn || !this.$scope.sortDirection) {
return rowsToSort;
}
var sortKey = this.$scope.sortColumn,
sortDirectionMultiplier;
if (this.$scope.sortDirection === 'asc') {
sortDirectionMultiplier = 1;
} else if (this.$scope.sortDirection === 'desc') {
sortDirectionMultiplier = -1;
}
return rowsToSort.slice(0).sort(function(a, b) {
return rowsToSort.sort(function (a, b) {
//If the values to compare can be compared as
// numbers, do so. String comparison of number
// values can cause inconsistencies
var valA = isNaN(a[sortKey].text) ? a[sortKey].text : parseFloat(a[sortKey].text),
valB = isNaN(b[sortKey].text) ? b[sortKey].text : parseFloat(b[sortKey].text);
var valA = isNaN(a[sortKey].text) ? a[sortKey].text :
parseFloat(a[sortKey].text),
valB = isNaN(b[sortKey].text) ? b[sortKey].text :
parseFloat(b[sortKey].text);
return genericComparator(valA, valB) *
sortDirectionMultiplier;
return self.sortComparator(valA, valB);
});
};
@@ -236,7 +398,7 @@ define(
* pre-calculate optimal column sizes without having to render
* every row.
*/
MCTTableController.prototype.findLargestRow = function(rows) {
MCTTableController.prototype.findLargestRow = function (rows) {
var largestRow = rows.reduce(function (largestRow, row) {
Object.keys(row).forEach(function (key) {
var currentColumn = row[key].text,
@@ -257,10 +419,11 @@ define(
return largestRow;
}, JSON.parse(JSON.stringify(rows[0] || {})));
largestRow = JSON.parse(JSON.stringify(largestRow));
// Pad with characters to accomodate variable-width fonts,
// and remove characters that would allow word-wrapping.
largestRow = JSON.parse(JSON.stringify(largestRow));
Object.keys(largestRow).forEach(function(key) {
Object.keys(largestRow).forEach(function (key) {
var padCharacters,
i;
@@ -275,8 +438,15 @@ define(
return largestRow;
};
/**
* Calculates the widest row in the table, pads that row, and adds
* it to the table. Allows the table to size itself, then uses this
* as basis for column dimensions.
* @private
*/
MCTTableController.prototype.resize = function (){
var largestRow = this.findLargestRow(this.$scope.displayRows);
var largestRow = this.findLargestRow(this.$scope.displayRows),
self = this;
this.$scope.visibleRows = [
{
rowIndex: 0,
@@ -285,7 +455,28 @@ define(
}
];
this.$timeout(this.setElementSizes.bind(this));
//Wait a timeout to allow digest of previous change to visible
// rows to happen.
this.$timeout(function () {
//Remove temporary padding row used for setting column widths
self.$scope.visibleRows = [];
self.setElementSizes();
});
};
/**
* @priate
*/
MCTTableController.prototype.filterAndSort = function (rows) {
var displayRows = rows;
if (this.$scope.enableFilter) {
displayRows = this.filterRows(displayRows);
}
if (this.$scope.enableSort) {
displayRows = this.sortRows(displayRows.slice(0));
}
this.$scope.displayRows = displayRows;
};
/**
@@ -293,29 +484,27 @@ define(
* will be sorted before display.
*/
MCTTableController.prototype.updateRows = function (newRows) {
var displayRows = newRows;
//Reset visible rows because new row data available.
this.$scope.visibleRows = [];
this.$scope.overrideRowPositioning = false;
//Nothing to show because no columns visible
if (!this.$scope.displayHeaders) {
return;
}
if (this.$scope.enableFilter) {
displayRows = this.filterRows(displayRows);
}
if (this.$scope.enableSort) {
displayRows = this.sortRows(displayRows);
}
this.$scope.displayRows = displayRows;
this.filterAndSort(newRows || []);
this.resize();
};
/**
* Filter rows.
* Applies user defined filters to rows. These filters are based on
* the text entered in the search areas in each column.
* @param rowsToFilter {Object[]} The rows to apply filters to
* @returns {Object[]} A filtered copy of the supplied rows
*/
MCTTableController.prototype.filterRows = function(rowsToFilter) {
MCTTableController.prototype.filterRows = function (rowsToFilter) {
var filters = {},
self = this;
@@ -323,7 +512,7 @@ define(
* Returns true if row matches all filters.
*/
function matchRow(filters, row) {
return Object.keys(filters).every(function(key) {
return Object.keys(filters).every(function (key) {
if (!row[key]) {
return false;
}
@@ -336,7 +525,7 @@ define(
return rowsToFilter;
}
Object.keys(this.$scope.filters).forEach(function(key) {
Object.keys(this.$scope.filters).forEach(function (key) {
if (!self.$scope.filters[key]) {
return;
}

View File

@@ -0,0 +1,123 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define*/
define(
[
'./TelemetryTableController'
],
function (TableController) {
"use strict";
/**
* Extends TelemetryTableController and adds real-time streaming
* support.
* @memberof platform/features/table
* @param $scope
* @param telemetryHandler
* @param telemetryFormatter
* @constructor
*/
function RTTelemetryTableController($scope, telemetryHandler, telemetryFormatter) {
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
$scope.autoScroll = false;
this.maxRows = 100000;
/*
* Determine if auto-scroll should be enabled. Is enabled
* automatically when telemetry type is string
*/
function hasStringTelemetry(domainObject) {
var telemetry = domainObject &&
domainObject.getCapability('telemetry'),
metadata = telemetry ? telemetry.getMetadata() : {},
ranges = metadata.ranges || [];
return ranges.some(function (range) {
return range.format === 'string';
});
}
$scope.$watch('domainObject', function (domainObject) {
//When a domain object becomes available, check whether the
// view should auto-scroll to the bottom.
if (domainObject && hasStringTelemetry(domainObject)){
$scope.autoScroll = true;
}
});
}
RTTelemetryTableController.prototype = Object.create(TableController.prototype);
/**
Override the subscribe function defined on the parent controller in
order to handle realtime telemetry instead of historical.
*/
RTTelemetryTableController.prototype.subscribe = function () {
var self = this;
self.$scope.rows = undefined;
(this.subscriptions || []).forEach(function (unsubscribe){
unsubscribe();
});
if (this.handle) {
this.handle.unsubscribe();
}
function updateData(){
var datum,
row;
self.handle.getTelemetryObjects().forEach(function (telemetryObject){
datum = self.handle.getDatum(telemetryObject);
if (datum) {
row = self.table.getRowValues(telemetryObject, datum);
if (!self.$scope.rows){
self.$scope.rows = [row];
self.$scope.$digest();
} else {
self.$scope.rows.push(row);
if (self.$scope.rows.length > self.maxRows) {
self.$scope.$broadcast('remove:row', 0);
self.$scope.rows.shift();
}
self.$scope.$broadcast('add:row',
self.$scope.rows.length - 1);
}
}
});
}
this.handle = this.$scope.domainObject && this.telemetryHandler.handle(
this.$scope.domainObject,
updateData,
true // Lossless
);
this.setup();
};
return RTTelemetryTableController;
}
);

View File

@@ -56,9 +56,9 @@ define(
self.populateForm(model);
});
$scope.$watchCollection('configuration.table.columns', function(columns){
$scope.$watchCollection('configuration.table.columns', function (columns){
if (columns){
self.domainObject.useCapability('mutation', function(model) {
self.domainObject.useCapability('mutation', function (model) {
model.configuration.table.columns = columns;
});
self.domainObject.getCapability('persistence').persist();

View File

@@ -52,10 +52,11 @@ define(
this.handle = undefined;
//this.pending = false;
this.telemetryHandler = telemetryHandler;
this.table = new TableConfiguration($scope.domainObject, telemetryFormatter);
this.table = new TableConfiguration($scope.domainObject,
telemetryFormatter);
this.changeListeners = [];
$scope.rows = [];
$scope.rows = undefined;
// Subscribe to telemetry when a domain object becomes available
this.$scope.$watch('domainObject', function(domainObject){
@@ -71,21 +72,24 @@ define(
this.$scope.$on("$destroy", this.destroy.bind(this));
}
TelemetryTableController.prototype.registerChangeListeners = function() {
//Defer registration of change listeners until domain object is
// available in order to avoid race conditions
/**
* Defer registration of change listeners until domain object is
* available in order to avoid race conditions
* @private
*/
TelemetryTableController.prototype.registerChangeListeners = function () {
this.changeListeners.forEach(function (listener) {
return listener && listener();
});
this.changeListeners = [];
// When composition changes, re-subscribe to the various
// telemetry subscriptions
this.changeListeners.push(this.$scope.$watchCollection('domainObject.getModel().composition', this.subscribe.bind(this)));
this.changeListeners.push(this.$scope.$watchCollection(
'domainObject.getModel().composition', this.subscribe.bind(this)));
//Change of bounds in time conductor
this.changeListeners.push(this.$scope.$on('telemetry:display:bounds', this.subscribe.bind(this)));
this.changeListeners.push(this.$scope.$on('telemetry:display:bounds',
this.subscribe.bind(this)));
};
/**
@@ -99,16 +103,17 @@ define(
};
/**
Create a new subscription. This is called when
Create a new subscription. This can be overridden by children to
change default behaviour (which is to retrieve historical telemetry
only).
*/
TelemetryTableController.prototype.subscribe = function() {
TelemetryTableController.prototype.subscribe = function () {
var self = this;
if (this.handle) {
this.handle.unsubscribe();
}
this.$scope.rows = [];
//Noop because not supporting realtime data right now
function noop(){
}
@@ -119,25 +124,37 @@ define(
true // Lossless
);
this.handle.request({}, this.addHistoricalData.bind(this));
this.handle.request({}).then(this.addHistoricalData.bind(this));
this.setup();
};
/**
* Add any historical data available
* Populates historical data on scope when it becomes available
* @private
*/
TelemetryTableController.prototype.addHistoricalData = function(domainObject, series) {
var i;
for (i=0; i < series.getPointCount(); i++) {
this.updateRows(domainObject, this.handle.makeDatum(domainObject, series, i));
}
TelemetryTableController.prototype.addHistoricalData = function () {
var rowData = [],
self = this;
this.handle.getTelemetryObjects().forEach(function (telemetryObject){
var series = self.handle.getSeries(telemetryObject) || {},
pointCount = series.getPointCount ? series.getPointCount() : 0,
i = 0;
for (; i < pointCount; i++) {
rowData.push(self.table.getRowValues(telemetryObject,
self.handle.makeDatum(telemetryObject, series, i)));
}
});
this.$scope.rows = rowData;
};
/**
* Setup table columns based on domain object metadata
*/
TelemetryTableController.prototype.setup = function() {
TelemetryTableController.prototype.setup = function () {
var handle = this.handle,
table = this.table,
self = this;
@@ -159,7 +176,7 @@ define(
};
/**
* Add data to rows
* @private
* @param object The object for which data is available (table may
* be composed of multiple objects)
* @param datum The data received from the telemetry source
@@ -171,6 +188,7 @@ define(
/**
* When column configuration changes, update the visible headers
* accordingly.
* @private
*/
TelemetryTableController.prototype.filterColumns = function (columnConfig) {
if (!columnConfig){
@@ -178,7 +196,7 @@ define(
this.table.saveColumnConfiguration(columnConfig);
}
//Populate headers with visible columns (determined by configuration)
this.$scope.headers = Object.keys(columnConfig).filter(function(column) {
this.$scope.headers = Object.keys(columnConfig).filter(function (column) {
return columnConfig[column];
});
};

View File

@@ -1,19 +1,49 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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(
["../controllers/MCTTableController"],
function (MCTTableController) {
function MCTTable() {
[
"../controllers/MCTTableController",
"text!../../res/templates/mct-table.html"
],
function (MCTTableController, TableTemplate) {
/**
* Defines a generic 'Table' component. The table can be populated
* en-masse by setting the rows attribute, or rows can be added as
* needed via a broadcast 'addRow' event.
* @constructor
*/
function MCTTable($timeout) {
return {
restrict: "E",
templateUrl: "platform/features/table/res/templates/mct-data-table.html",
template: TableTemplate,
controller: ['$scope', '$timeout', '$element', MCTTableController],
scope: {
headers: "=",
rows: "=",
enableFilter: "=?",
enableSort: "=?"
}
enableSort: "=?",
autoScroll: "=?"
},
};
}

View File

@@ -118,13 +118,13 @@ define(
});
it("populates the columns attribute", function() {
expect(table.columns.length).toBe(4);
expect(table.columns.length).toBe(5);
});
it("Build columns populates columns with domains to the left", function() {
expect(table.columns[0] instanceof DomainColumn).toBeTruthy();
expect(table.columns[1] instanceof DomainColumn).toBeTruthy();
expect(table.columns[2] instanceof DomainColumn).toBeFalsy();
expect(table.columns[2] instanceof DomainColumn).toBeTruthy();
expect(table.columns[3] instanceof DomainColumn).toBeFalsy();
});
it("Produces headers for each column based on title", function() {
@@ -133,7 +133,7 @@ define(
spyOn(firstColumn, 'getTitle');
headers = table.getHeaders();
expect(headers.length).toBe(4);
expect(headers.length).toBe(5);
expect(firstColumn.getTitle).toHaveBeenCalled();
});

View File

@@ -34,10 +34,20 @@ define(
mockTimeout,
mockElement;
function promise(value) {
return {
then: function (callback) {
return promise(callback(value));
}
};
}
beforeEach(function() {
watches = {};
mockScope = jasmine.createSpyObj('scope', [
'$watch',
'$on',
'$watchCollection'
]);
mockScope.$watchCollection.andCallFake(function(event, callback) {
@@ -52,14 +62,15 @@ define(
mockScope.displayHeaders = true;
mockTimeout = jasmine.createSpy('$timeout');
mockTimeout.andReturn(promise(undefined));
controller = new MCTTableController(mockScope, mockTimeout, mockElement);
});
it('Reacts to changes to filters, headers, and rows', function() {
expect(mockScope.$watchCollection).toHaveBeenCalledWith('filters', jasmine.any(Function));
expect(mockScope.$watchCollection).toHaveBeenCalledWith('headers', jasmine.any(Function));
expect(mockScope.$watchCollection).toHaveBeenCalledWith('rows', jasmine.any(Function));
expect(mockScope.$watch).toHaveBeenCalledWith('headers', jasmine.any(Function));
expect(mockScope.$watch).toHaveBeenCalledWith('rows', jasmine.any(Function));
});
describe('rows', function() {
@@ -82,6 +93,7 @@ define(
'col3': {'text': 'row3 col3'}
}
];
mockScope.rows = testRows;
});
it('Filters results based on filter input', function() {
@@ -106,6 +118,31 @@ define(
expect(mockScope.displayRows).toEqual(testRows);
});
it('Supports adding rows individually', function() {
var addRowFunc = mockScope.$on.calls[mockScope.$on.calls.length-2].args[1],
row4 = {
'col1': {'text': 'row3 col1'},
'col2': {'text': 'ghi'},
'col3': {'text': 'row3 col3'}
};
controller.updateRows(testRows);
expect(mockScope.displayRows.length).toBe(3);
testRows.push(row4);
addRowFunc(undefined, 3);
expect(mockScope.displayRows.length).toBe(4);
});
it('Supports removing rows individually', function() {
var removeRowFunc = mockScope.$on.calls[mockScope.$on.calls.length-1].args[1];
controller.updateRows(testRows);
expect(mockScope.displayRows.length).toBe(3);
spyOn(controller, 'setVisibleRows');
//controller.setVisibleRows.andReturn(undefined);
removeRowFunc(undefined, 2);
expect(mockScope.displayRows.length).toBe(2);
expect(controller.setVisibleRows).toHaveBeenCalled();
});
describe('sorting', function() {
var sortedRows;
@@ -139,7 +176,98 @@ define(
expect(sortedRows[1].col2.text).toEqual('def');
expect(sortedRows[2].col2.text).toEqual('abc');
});
describe('Adding new rows', function() {
var row4,
row5,
row6;
beforeEach(function() {
row4 = {
'col1': {'text': 'row5 col1'},
'col2': {'text': 'xyz'},
'col3': {'text': 'row5 col3'}
};
row5 = {
'col1': {'text': 'row6 col1'},
'col2': {'text': 'aaa'},
'col3': {'text': 'row6 col3'}
};
row6 = {
'col1': {'text': 'row6 col1'},
'col2': {'text': 'ggg'},
'col3': {'text': 'row6 col3'}
};
});
it('Adds new rows at the correct sort position when' +
' sorted ', function() {
mockScope.sortColumn = 'col2';
mockScope.sortDirection = 'desc';
mockScope.displayRows = controller.sortRows(testRows.slice(0));
mockScope.rows.push(row4);
controller.newRow(undefined, mockScope.rows.length-1);
expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
mockScope.rows.push(row5);
controller.newRow(undefined, mockScope.rows.length-1);
expect(mockScope.displayRows[4].col2.text).toEqual('aaa');
mockScope.rows.push(row6);
controller.newRow(undefined, mockScope.rows.length-1);
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
//Add a duplicate row
mockScope.rows.push(row6);
controller.newRow(undefined, mockScope.rows.length-1);
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
});
it('Adds new rows at the correct sort position when' +
' sorted and filtered', function() {
mockScope.sortColumn = 'col2';
mockScope.sortDirection = 'desc';
mockScope.filters = {'col2': 'a'};//Include only
// rows with 'a'
mockScope.displayRows = controller.sortRows(testRows.slice(0));
mockScope.displayRows = controller.filterRows(testRows);
mockScope.rows.push(row5);
controller.newRow(undefined, mockScope.rows.length-1);
expect(mockScope.displayRows.length).toBe(2);
expect(mockScope.displayRows[1].col2.text).toEqual('aaa');
mockScope.rows.push(row6);
controller.newRow(undefined, mockScope.rows.length-1);
expect(mockScope.displayRows.length).toBe(2);
//Row was not added because does not match filter
});
it('Adds new rows at the correct sort position when' +
' not sorted ', function() {
mockScope.sortColumn = undefined;
mockScope.sortDirection = undefined;
mockScope.filters = {};
mockScope.displayRows = testRows.slice(0);
mockScope.rows.push(row5);
controller.newRow(undefined, mockScope.rows.length-1);
expect(mockScope.displayRows[3].col2.text).toEqual('aaa');
mockScope.rows.push(row6);
controller.newRow(undefined, mockScope.rows.length-1);
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
});
});
});
});
});
});

View File

@@ -0,0 +1,166 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,xit*/
define(
[
"../../src/controllers/RTTelemetryTableController"
],
function (TableController) {
"use strict";
describe('The real-time table controller', function () {
var mockScope,
mockTelemetryHandler,
mockTelemetryHandle,
mockTelemetryFormatter,
mockDomainObject,
mockTable,
mockConfiguration,
watches,
mockTableRow,
controller;
function promise(value) {
return {
then: function (callback){
return promise(callback(value));
}
};
}
beforeEach(function () {
watches = {};
mockTableRow = {'col1': 'val1', 'col2': 'row2'};
mockScope = jasmine.createSpyObj('scope', [
'$on',
'$watch',
'$watchCollection',
'$digest',
'$broadcast'
]);
mockScope.$on.andCallFake(function (expression, callback){
watches[expression] = callback;
});
mockScope.$watch.andCallFake(function (expression, callback){
watches[expression] = callback;
});
mockScope.$watchCollection.andCallFake(function (expression, callback){
watches[expression] = callback;
});
mockConfiguration = {
'range1': true,
'range2': true,
'domain1': true
};
mockTable = jasmine.createSpyObj('table',
[
'buildColumns',
'getColumnConfiguration',
'getRowValues',
'saveColumnConfiguration'
]
);
mockTable.columns = [];
mockTable.getColumnConfiguration.andReturn(mockConfiguration);
mockTable.getRowValues.andReturn(mockTableRow);
mockDomainObject= jasmine.createSpyObj('domainObject', [
'getCapability',
'useCapability',
'getModel'
]);
mockDomainObject.getModel.andReturn({});
mockDomainObject.getCapability.andReturn(
{
getMetadata: function (){
return {ranges: [{format: 'string'}]};
}
});
mockScope.domainObject = mockDomainObject;
mockTelemetryHandle = jasmine.createSpyObj('telemetryHandle', [
'getMetadata',
'unsubscribe',
'getDatum',
'promiseTelemetryObjects',
'getTelemetryObjects'
]);
// Arbitrary array with non-zero length, contents are not
// used by mocks
mockTelemetryHandle.getTelemetryObjects.andReturn([{}]);
mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined));
mockTelemetryHandle.getDatum.andReturn({});
mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [
'handle'
]);
mockTelemetryHandler.handle.andReturn(mockTelemetryHandle);
controller = new TableController(mockScope, mockTelemetryHandler, mockTelemetryFormatter);
controller.table = mockTable;
controller.handle = mockTelemetryHandle;
});
it('registers for streaming telemetry', function () {
controller.subscribe();
expect(mockTelemetryHandler.handle).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function), true);
});
describe('receives new telemetry', function () {
beforeEach(function() {
controller.subscribe();
mockScope.rows = [];
});
it('updates table with new streaming telemetry', function () {
mockTelemetryHandler.handle.mostRecentCall.args[1]();
expect(mockScope.$broadcast).toHaveBeenCalledWith('add:row', 0);
});
it('observes the row limit', function () {
var i = 0;
controller.maxRows = 10;
//Fill rows array with elements
for (; i < 10; i++) {
mockScope.rows.push({row: i});
}
mockTelemetryHandler.handle.mostRecentCall.args[1]();
expect(mockScope.rows.length).toBe(controller.maxRows);
expect(mockScope.rows[mockScope.rows.length-1]).toBe(mockTableRow);
expect(mockScope.rows[0].row).toBe(1);
});
});
it('enables autoscroll for event telemetry', function () {
controller.subscribe();
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
expect(mockScope.autoScroll).toBe(true);
});
});
}
);

View File

@@ -26,7 +26,7 @@ define(
],
function (TableController) {
describe('The Table Controller', function() {
describe('The Table Controller', function () {
var mockScope,
mockTelemetryHandler,
mockTelemetryHandle,
@@ -45,7 +45,7 @@ define(
};
}
beforeEach(function() {
beforeEach(function () {
watches = {};
mockScope = jasmine.createSpyObj('scope', [
'$on',
@@ -53,13 +53,13 @@ define(
'$watchCollection'
]);
mockScope.$on.andCallFake(function(expression, callback){
mockScope.$on.andCallFake(function (expression, callback){
watches[expression] = callback;
});
mockScope.$watch.andCallFake(function(expression, callback){
mockScope.$watch.andCallFake(function (expression, callback){
watches[expression] = callback;
});
mockScope.$watchCollection.andCallFake(function(expression, callback){
mockScope.$watchCollection.andCallFake(function (expression, callback){
watches[expression] = callback;
});
@@ -92,11 +92,15 @@ define(
mockTelemetryHandle = jasmine.createSpyObj('telemetryHandle', [
'request',
'promiseTelemetryObjects',
'getTelemetryObjects',
'getMetadata',
'getSeries',
'unsubscribe',
'makeDatum'
]);
mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined));
mockTelemetryHandle.request.andReturn(promise(undefined));
mockTelemetryHandle.getTelemetryObjects.andReturn([]);
mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [
'handle'
@@ -108,60 +112,63 @@ define(
controller.handle = mockTelemetryHandle;
});
it('subscribes to telemetry handler for telemetry updates', function() {
it('subscribes to telemetry handler for telemetry updates', function () {
controller.subscribe();
expect(mockTelemetryHandler.handle).toHaveBeenCalled();
expect(mockTelemetryHandle.request).toHaveBeenCalled();
});
it('Unsubscribes from telemetry when scope is destroyed',function() {
it('Unsubscribes from telemetry when scope is destroyed',function () {
controller.handle = mockTelemetryHandle;
watches.$destroy();
expect(mockTelemetryHandle.unsubscribe).toHaveBeenCalled();
});
describe('the controller makes use of the table', function() {
describe('the controller makes use of the table', function () {
it('to create column definitions from telemetry' +
' metadata', function() {
' metadata', function () {
controller.setup();
expect(mockTable.buildColumns).toHaveBeenCalled();
});
it('to create column configuration, which is written to the' +
' object model', function() {
' object model', function () {
controller.setup();
expect(mockTable.getColumnConfiguration).toHaveBeenCalled();
expect(mockTable.saveColumnConfiguration).toHaveBeenCalled();
});
});
it('updates the rows on scope when historical telemetry is received', function(){
it('updates the rows on scope when historical telemetry is received', function (){
var mockSeries = {
getPointCount: function() {
getPointCount: function () {
return 5;
},
getDomainValue: function() {
getDomainValue: function () {
return 'Domain Value';
},
getRangeValue: function() {
getRangeValue: function () {
return 'Range Value';
}
},
mockRow = {'domain': 'Domain Value', 'range': 'Range' +
' Value'};
mockTelemetryHandle.makeDatum.andCallFake(function(){
mockTelemetryHandle.makeDatum.andCallFake(function (){
return mockRow;
});
mockTable.getRowValues.andReturn(mockRow);
mockTelemetryHandle.getTelemetryObjects.andReturn([mockDomainObject]);
mockTelemetryHandle.getSeries.andReturn(mockSeries);
controller.addHistoricalData(mockDomainObject, mockSeries);
expect(controller.$scope.rows.length).toBe(5);
expect(controller.$scope.rows[0]).toBe(mockRow);
});
it('filters the visible columns based on configuration', function(){
it('filters the visible columns based on configuration', function (){
controller.filterColumns();
expect(controller.$scope.headers.length).toBe(3);
expect(controller.$scope.headers[2]).toEqual('domain1');
@@ -172,14 +179,14 @@ define(
expect(controller.$scope.headers[2]).toBeUndefined();
});
describe('creates event listeners', function(){
beforeEach(function() {
describe('creates event listeners', function (){
beforeEach(function () {
spyOn(controller,'subscribe');
spyOn(controller, 'filterColumns');
});
it('triggers telemetry subscription update when domain' +
' object changes', function() {
' object changes', function () {
controller.registerChangeListeners();
//'watches' object is populated by fake scope watch and
// watchCollection functions defined above
@@ -189,7 +196,7 @@ define(
});
it('triggers telemetry subscription update when domain' +
' object composition changes', function() {
' object composition changes', function () {
controller.registerChangeListeners();
expect(watches['domainObject.getModel().composition']).toBeDefined();
watches['domainObject.getModel().composition']();
@@ -197,7 +204,7 @@ define(
});
it('triggers telemetry subscription update when time' +
' conductor bounds change', function() {
' conductor bounds change', function () {
controller.registerChangeListeners();
expect(watches['telemetry:display:bounds']).toBeDefined();
watches['telemetry:display:bounds']();
@@ -205,7 +212,7 @@ define(
});
it('triggers refiltering of the columns when configuration' +
' changes', function() {
' changes', function () {
controller.setup();
expect(watches['domainObject.getModel().configuration.table.columns']).toBeDefined();
watches['domainObject.getModel().configuration.table.columns']();

View File

@@ -21,6 +21,7 @@
*****************************************************************************/
define([
"./src/actions/ExportTimelineAsCSVAction",
"./src/controllers/TimelineController",
"./src/controllers/TimelineGraphController",
"./src/controllers/TimelineDateTimeController",
@@ -49,6 +50,7 @@ define([
"text!./res/templates/controls/datetime.html",
'legacyRegistry'
], function (
ExportTimelineAsCSVAction,
TimelineController,
TimelineGraphController,
TimelineDateTimeController,
@@ -83,6 +85,15 @@ define([
"description": "Resources, templates, CSS, and code for Timelines.",
"resources": "res",
"extensions": {
"actions": [
{
"key": "timeline.export",
"name": "Export Timeline as CSV",
"category": "contextual",
"implementation": ExportTimelineAsCSVAction,
"depends": [ "exportService", "notificationService" ]
}
],
"constants": [
{
"key": "TIMELINE_MINIMUM_DURATION",
@@ -126,8 +137,9 @@ define([
{
"key": "timeline",
"name": "Timeline",
"glyph": "S",
"description": "A container for arranging Timelines and Activities in time.",
"glyph": "\u0053",
"description": "A time-oriented container that lets you enclose and organize other Timelines and Activities. The Timeline view provides both tabular and Gantt views as well as resource utilization graphing of Activities.",
"priority": 502,
"features": [
"creation"
],
@@ -159,20 +171,24 @@ define([
}
],
"model": {
"composition": []
"composition": [],
"start": {
"timestamp": 0
}
}
},
{
"key": "activity",
"name": "Activity",
"glyph": "a",
"glyph": "\u0061",
"features": [
"creation"
],
"contains": [
"activity"
],
"description": "An action that takes place in time. You can define a start time and duration. Activities can be nested within other Activities, or within Timelines.",
"description": "An event or process that starts and ends at a discrete datetime. Activities can be nested in other Activities, and can be added to Timelines. Activity Modes can be added to an Activity to define its resource utilization over time.",
"priority": 501,
"properties": [
{
"name": "Start date/time",
@@ -198,17 +214,24 @@ define([
"composition": [],
"relationships": {
"modes": []
},
"start": {
"timestamp": 0
},
"duration": {
"timestamp": 0
}
}
},
{
"key": "mode",
"name": "Activity Mode",
"glyph": "A",
"glyph": "\u0041",
"features": [
"creation"
],
"description": "Define resource utilizations over time, then apply to an Activity.",
"description": "When a sub-system utilizes Power or Communications resources over time, you can define those values in an Activity Mode. Activity Modes can then be linked to Activities to allow resource utilization graphing and estimating in a Timeline.",
"priority": 500,
"model": {
"resources": {
"comms": 0,
@@ -243,7 +266,7 @@ define([
{
"key": "values",
"name": "Values",
"glyph": "A",
"glyph": "\u0041",
"template": valuesTemplate,
"type": "mode",
"uses": [
@@ -254,9 +277,9 @@ define([
{
"key": "timeline",
"name": "Timeline",
"glyph": "S",
"glyph": "\u0053",
"type": "timeline",
"description": "A timeline view of Timelines and Activities.",
"description": "A time-oriented container that lets you enclose and organize other Timelines and Activities. The Timeline view provides both tabular and Gantt views as well as resource utilization graphing of Activities.",
"template": timelineTemplate,
"editable": true,
"toolbar": {
@@ -265,18 +288,18 @@ define([
"items": [
{
"method": "add",
"glyph": "+",
"glyph": "\u002b",
"control": "menu-button",
"text": "Add",
"options": [
{
"name": "Timeline",
"glyph": "S",
"glyph": "\u0053",
"key": "timeline"
},
{
"name": "Activity",
"glyph": "a",
"glyph": "\u0061",
"key": "activity"
}
]
@@ -286,37 +309,39 @@ define([
{
"items": [
{
"glyph": "é",
"description": "Graph resource utilization",
"glyph": "\u00e9",
"description": "Graph Resource Utilization",
"control": "button",
"method": "toggleGraph"
},
{
"glyph": "A",
"glyph": "\u0041",
"control": "dialog-button",
"description": "Apply Activity Modes...",
"title": "Apply Activity Modes",
"dialog": {
"control": "selector",
"name": "Modes",
"type": "mode"
"type": "mode",
"layout": "controls-under"
},
"property": "modes"
},
{
"glyph": "è",
"glyph": "\u00e8",
"description": "Edit Activity Link",
"title": "Activity Link",
"control": "dialog-button",
"dialog": {
"control": "textfield",
"name": "Link",
"pattern": "^(ftp|https?)\\:\\/\\/\\w+(\\.\\w+)*(\\:\\d+)?(\\/\\S*)*$"
"pattern": "^(ftp|https?)\\:\\/\\/\\w+(\\.\\w+)*(\\:\\d+)?(\\/\\S*)*$",
"cssclass": "l-input-lg"
},
"property": "link"
},
{
"glyph": "G",
"glyph": "\u0047",
"description": "Edit Properties...",
"control": "button",
"method": "properties"
@@ -327,9 +352,9 @@ define([
"items": [
{
"method": "remove",
"description": "Remove item",
"description": "Remove Item",
"control": "button",
"glyph": "Z"
"glyph": "\u005a"
}
]
}

View File

@@ -59,7 +59,7 @@
.handle {
cursor: col-resize;
&.mid {
cursor: ew-resize;
cursor: move;
}
}
}

View File

@@ -140,17 +140,3 @@
}
}
}
.edit-mode .s-swimlane,
.s-status-editing .s-swimlane {
cursor: pointer;
.t-object-label {
border-radius: $controlCr;
cursor: move;
padding: 2px 5px;
&:hover {
background: rgba($colorBodyFg, 0.3);
color: pullForward($colorBodyFg, 20%);
}
}
}

View File

@@ -23,6 +23,11 @@
.l-timeline-holder {
@include absPosDefault();
.l-header {
@include user-select(none);
cursor: default;
}
.l-timeline-pane {
@include absPosDefault();
@@ -33,6 +38,9 @@
.l-swimlanes-holder {
@include absPosDefault();
top: $timelineTopPaneHeaderH + 1;
.l-col.l-plot-resource {
cursor: pointer;
}
}
// Overall layout
@@ -250,21 +258,15 @@
&.l-plot-resource {
border-left: none !important;
cursor: pointer;
padding-left: 0;
}
&.l-title {
width: $timelineColTitleW;
.rep-object-label[draggable="true"] {
.rep-object-label {
border-radius: $basicCr;
@include transition(background-color, 0.25s);
cursor: pointer;
display: inline-block;
padding: 0 $interiorMarginSm;
&:hover {
background-color: $colorItemTreeHoverBg;
}
padding: 0 $interiorMargin;
}
}
@@ -314,3 +316,11 @@
height: 5px
}
}
.s-status-editing .l-title .rep-object-label[draggable="true"] {
@include transition(background-color, 0.25s);
cursor: pointer;
&:hover {
background-color: $colorItemTreeHoverBg;
}
}

View File

@@ -29,9 +29,11 @@
}">
<div class="l-cols">
<span class="l-col l-col-icon l-plot-resource"
ng-click="ngModel.toggleGraph()">
ng-click="ngModel.toggleGraph()"
title="Click to enable or disable inclusion in Resource Graphing">
<span class="ui-symbol"
ng-show="ngModel.graph()">
ng-show="ngModel.graph()"
>
&#x00e9;
</span>
</span>

View File

@@ -26,20 +26,17 @@
<!-- LEFT PANE: TABULAR AND RESOURCE LEGEND AREAS -->
<mct-split-pane anchor="bottom"
position="pane.y"
class="abs horizontal split-pane-component l-timeline-pane l-pane-l t-pane-v"
>
class="abs horizontal split-pane-component l-timeline-pane l-pane-l t-pane-v">
<!-- TOP PANE TABULAR AREA. ADD CLASS "hidden" FOR INTERIM NO-TABULAR DELIVERY -->
<div class="split-pane-component s-timeline-tabular l-timeline-pane t-pane-h l-pane-top">
<!-- TABULAR LEFT FIXED AREA -->
<div
class="t-pane-v l-pane-l l-tabular-l"
ng-if="true"
>
<div class="t-pane-v l-pane-l l-tabular-l"
ng-if="true">
<div class="t-header l-header s-header">
<div class="l-cols">
<span class="l-col l-col-icon l-plot-resource ui-symbol">&#x00e9;</span>
<span class="l-col l-col-icon l-col-link ui-symbol">&#x00e8;</span>
<span title="Resource Graphing: click a row to toggle" class="l-col l-col-icon l-plot-resource ui-symbol">&#x00e9;</span>
<span title="Activity Links" class="l-col l-col-icon l-col-link ui-symbol">&#x00e8;</span>
<span class="l-col l-title">Title</span>
</div>
</div>
@@ -54,9 +51,7 @@
</div>
<!-- TABULAR RIGHT HORZ SCROLLING AREA -->
<div
class="t-pane-v l-pane-r l-tabular-r"
>
<div class="t-pane-v l-pane-r l-tabular-r">
<div class="l-width">
<div class="t-header l-header s-header">
<div class="l-cols">
@@ -102,12 +97,10 @@
<span ng-controller="TimelineZoomController as zoomController" class="abs">
<mct-split-pane anchor="bottom"
position="pane.y"
class="abs split-pane-component l-timeline-pane l-pane-r t-pane-v"
>
class="abs split-pane-component l-timeline-pane l-pane-r t-pane-v">
<!-- TOP PANE GANTT BARS -->
<div class="split-pane-component l-timeline-pane t-pane-h l-pane-top t-timeline-gantt l-timeline-gantt s-timeline-gantt"
>
<div class="split-pane-component l-timeline-pane t-pane-h l-pane-top t-timeline-gantt l-timeline-gantt s-timeline-gantt">
<div class="l-hover-btns-holder s-hover-btns-holder t-btns-zoom">
<a class="t-btn l-btn s-btn"
ng-click="zoomController.zoom(-1)"
@@ -124,8 +117,7 @@
</a>
</div>
<div style="overflow: hidden; position: absolute; left: 0; top: 0; right: 0; height: 30px;"
mct-scroll-x="scroll.x">
<div style="overflow: hidden; position: absolute; left: 0; top: 0; right: 0; height: 30px;" mct-scroll-x="scroll.x">
<mct-include key="'timeline-ticks'"
parameters="{
fullWidth: zoomController.toPixels(zoomController.duration()),
@@ -137,9 +129,6 @@
</mct-include>
</div>
<!-- TO-DO:
Make this control y-scroll of both .t-swimlanes-holder elements in TOP PANE TABULAR AREA
-->
<div class="t-swimlanes-holder l-swimlanes-holder"
mct-scroll-x="scroll.x"
mct-scroll-y="scroll.y">
@@ -175,12 +164,9 @@
mct-drag-up="handle.finish()">
</span>
</span>
</div>
</div>
</div>
</div>
<!-- HORZ SPLITTER -->
@@ -188,11 +174,8 @@
<!-- BOTTOM PANE RESOURCE GRAPHS AND RIGHT PANE HORIZONTAL SCROLL CONTROL -->
<div class="split-pane-component l-timeline-resource-graph l-timeline-pane t-pane-h l-pane-btm">
<div class="l-graphs-holder"
mct-resize="scroll.width = bounds.width">
<!-- TO-DO: Make this control y-scroll of .t-graph-labels-holder -->
<div class="t-graphs l-graphs">
<mct-include key="'timeline-resource-graphs'"
parameters="{
@@ -202,9 +185,7 @@
}">
</mct-include>
</div>
</div>
<!-- TO-DO: Make this control x-scroll of .t-timeline-gantt -->
<div mct-scroll-x="scroll.x"
class="t-pane-r-scroll-h-control l-scroll-control s-scroll-control">
<div class="l-width-control"

View File

@@ -0,0 +1,50 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define*/
define([], function () {
"use strict";
/**
* A column containing references to other objects contained
* in a domain object's composition.
* @param {number} index the zero-based index of the composition
* element associated with this column
* @constructor
* @implements {platform/features/timeline.TimelineCSVColumn}
*/
function CompositionColumn(index) {
this.index = index;
}
CompositionColumn.prototype.name = function () {
return "Child " + (this.index + 1);
};
CompositionColumn.prototype.value = function (domainObject) {
var model = domainObject.getModel(),
composition = model.composition || [];
return (composition[this.index]) || "";
};
return CompositionColumn;
});

View File

@@ -0,0 +1,70 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define*/
define(["./ExportTimelineAsCSVTask"], function (ExportTimelineAsCSVTask) {
'use strict';
/**
* Implements the "Export Timeline as CSV" action.
*
* @param exportService the service used to perform the CSV export
* @param notificationService the service used to show notifications
* @param context the Action's context
* @implements {Action}
* @constructor
* @memberof {platform/features/timeline}
*/
function ExportTimelineAsCSVAction(exportService, notificationService, context) {
this.task = new ExportTimelineAsCSVTask(
exportService,
context.domainObject
);
this.notificationService = notificationService;
}
ExportTimelineAsCSVAction.prototype.perform = function () {
var notificationService = this.notificationService,
notification = notificationService.notify({
title: "Exporting CSV",
unknownProgress: true
});
return this.task.run()
.then(function () {
notification.dismiss();
})
.catch(function () {
notification.dismiss();
notificationService.error("Error exporting CSV");
});
};
ExportTimelineAsCSVAction.appliesTo = function (context) {
return context.domainObject &&
context.domainObject.hasCapability('type') &&
context.domainObject.getCapability('type')
.instanceOf('timeline');
};
return ExportTimelineAsCSVAction;
});

View File

@@ -0,0 +1,70 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define,Promise*/
/**
* Module defining ExportTimelineAsCSVTask. Created by vwoeltje on 2/8/16.
*/
define([
"./TimelineTraverser",
"./TimelineColumnizer"
], function (TimelineTraverser, TimelineColumnizer) {
"use strict";
/**
* Runs (and coordinates) the preparation and export of CSV data
* for the "Export Timeline as CSV" action.
*
* @constructor
* @memberof {platform/features/timeline}
* @param exportService the service used to export as CSV
* @param {DomainObject} domainObject the timeline being exported
*/
function ExportTimelineAsCSVTask(exportService, domainObject) {
this.domainObject = domainObject;
this.exportService = exportService;
}
/**
* Run this CSV export task.
*
* @returns {Promise} a promise that will be resolved when the
* export has finished (or rejected if there are problems.)
*/
ExportTimelineAsCSVTask.prototype.run = function () {
var exportService = this.exportService;
function doExport(objects) {
var exporter = new TimelineColumnizer(objects),
options = { headers: exporter.headers() };
return exporter.rows().then(function (rows) {
return exportService.exportCSV(rows, options);
});
}
return new TimelineTraverser(this.domainObject)
.buildObjectList()
.then(doExport);
};
return ExportTimelineAsCSVTask;
});

View File

@@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -19,35 +19,26 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
/**
* Module defining NameColumn. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
define([], function () {
"use strict";
/**
* A column which will report the name of the domain object
* which exposed specific telemetry values.
*
* @memberof platform/features/scrolling
* @implements {platform/features/scrolling.ScrollingColumn}
* @constructor
*/
function NameColumn() {
}
NameColumn.prototype.getTitle = function () {
return "Name";
};
NameColumn.prototype.getValue = function (domainObject) {
return {
text: domainObject.getModel().name
};
};
return NameColumn;
/**
* A column showing domain object identifiers.
* @constructor
* @implements {platform/features/timeline.TimelineCSVColumn}
*/
function IdColumn() {
}
);
IdColumn.prototype.name = function () {
return "Identifier";
};
IdColumn.prototype.value = function (domainObject) {
return domainObject.getId();
};
return IdColumn;
});

View File

@@ -0,0 +1,50 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define*/
define([], function () {
"use strict";
/**
* A column reflecting properties from domain object metadata.
* @constructor
* @implements {platform/features/timeline.TimelineCSVColumn}
*/
function MetadataColumn(propertyName) {
this.propertyName = propertyName;
}
MetadataColumn.prototype.name = function () {
return this.propertyName;
};
MetadataColumn.prototype.value = function (domainObject) {
var properties = domainObject.useCapability('metadata'),
name = this.propertyName;
return properties.reduce(function (value, property) {
return property.name === name ?
property.value : value;
}, "");
};
return MetadataColumn;
});

View File

@@ -0,0 +1,49 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define*/
define([], function () {
"use strict";
/**
* A column showing relationships to activity modes.
* @constructor
* @param {number} index the zero-based index of the composition
* element associated with this column
* @implements {platform/features/timeline.TimelineCSVColumn}
*/
function ModeColumn(index) {
this.index = index;
}
ModeColumn.prototype.name = function () {
return "Activity Mode " + (this.index + 1);
};
ModeColumn.prototype.value = function (domainObject) {
var model = domainObject.getModel(),
composition = (model.relationships || {}).modes || [];
return (composition[this.index]) || "";
};
return ModeColumn;
});

View File

@@ -0,0 +1,161 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define,Promise*/
define([
"./IdColumn",
"./ModeColumn",
"./CompositionColumn",
"./MetadataColumn",
"./TimespanColumn"
], function (
IdColumn,
ModeColumn,
CompositionColumn,
MetadataColumn,
TimespanColumn
) {
'use strict';
/**
* A description of how to populate a given column within a
* prepared table of domain object data, for CSV export.
* @interface platform/features/timeline.TimelineCSVColumn
*/
/**
* Get the value that belongs in this column for a given
* domain object.
* @memberof {platform/features/timeline.TimelineCSVColumn#}
* @method value
* @param {DomainObject} domainObject the domain object
* represented by this row
* @returns {string|Promise<string>} the value for this cell
*/
/**
* Get the name of this column, as belongs in a header.
* @memberof {platform/features/timeline.TimelineCSVColumn#}
* @method name
* @returns {string} the name of this column
*/
/**
* Handles conversion of a list of domain objects to a table
* representation appropriate for CSV export.
*
* @param {DomainObject[]} domainObjects the objects to include
* in the exported data
* @constructor
* @memberof {platform/features/timeline}
*/
function TimelineColumnizer(domainObjects) {
var maxComposition = 0,
maxRelationships = 0,
columnNames = {},
columns = [],
foundTimespan = false,
i;
function addMetadataProperty(property) {
var name = property.name;
if (!columnNames[name]) {
columnNames[name] = true;
columns.push(new MetadataColumn(name));
}
}
columns.push(new IdColumn());
domainObjects.forEach(function (domainObject) {
var model = domainObject.getModel(),
composition = model.composition,
relationships = model.relationships,
modes = relationships && relationships.modes,
metadataProperties = domainObject.useCapability('metadata');
if (composition) {
maxComposition = Math.max(maxComposition, composition.length);
}
if (modes) {
maxRelationships = Math.max(maxRelationships, modes.length);
}
if (domainObject.hasCapability('timespan')) {
foundTimespan = true;
}
if (metadataProperties) {
metadataProperties.forEach(addMetadataProperty);
}
});
if (foundTimespan) {
columns.push(new TimespanColumn(true));
columns.push(new TimespanColumn(false));
}
for (i = 0; i < maxComposition; i += 1) {
columns.push(new CompositionColumn(i));
}
for (i = 0; i < maxRelationships; i += 1) {
columns.push(new ModeColumn(i));
}
this.domainObjects = domainObjects;
this.columns = columns;
}
/**
* Get a tabular representation of domain object data.
* Each row corresponds to a single object; each element
* in each row corresponds to a property designated by
* the `headers`, correlated by index.
* @returns {Promise.<string[][]>} domain object data
*/
TimelineColumnizer.prototype.rows = function () {
var columns = this.columns;
function toRow(domainObject) {
return Promise.all(columns.map(function (column) {
return column.value(domainObject);
}));
}
return Promise.all(this.domainObjects.map(toRow));
};
/**
* Get the column headers associated with this tabular
* representation of objects.
* @returns {string[]} column headers
*/
TimelineColumnizer.prototype.headers = function () {
return this.columns.map(function (column) {
return column.name();
});
};
return TimelineColumnizer;
});

View File

@@ -0,0 +1,85 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define,Promise*/
define([], function () {
"use strict";
/**
* Builds a list of domain objects which should be included
* in the CSV export of a given timeline.
* @param {DomainObject} domainObject the object being exported
* @constructor
*/
function TimelineTraverser(domainObject) {
this.domainObject = domainObject;
}
/**
* Get a list of domain objects for CSV export.
* @returns {Promise.<DomainObject[]>} a list of domain objects
*/
TimelineTraverser.prototype.buildObjectList = function () {
var idSet = {},
objects = [];
function addObject(domainObject) {
var id = domainObject.getId(),
subtasks = [];
function addCompositionObjects() {
return domainObject.useCapability('composition')
.then(function (childObjects) {
return Promise.all(childObjects.map(addObject));
});
}
function addRelationships() {
var relationship = domainObject.getCapability('relationship');
relationship.getRelatedObjects('modes')
.then(function (modeObjects) {
return Promise.all(modeObjects.map(addObject));
});
}
if (!idSet[id]) {
idSet[id] = true;
objects.push(domainObject);
if (domainObject.hasCapability('composition')) {
subtasks.push(addCompositionObjects());
}
if (domainObject.hasCapability('relationship')) {
subtasks.push(addRelationships());
}
}
return Promise.all(subtasks);
}
return addObject(this.domainObject).then(function () {
return objects;
});
};
return TimelineTraverser;
});

View File

@@ -0,0 +1,55 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define*/
define(['../TimelineFormatter'], function (TimelineFormatter) {
"use strict";
var FORMATTER = new TimelineFormatter();
/**
* A column showing start or end times associated with a domain object.
* @constructor
* @param {boolean} isStart true if this column refers to the object's
* start time; false if it refers to the object's end time
* @implements {platform/features/timeline.TimelineCSVColumn}
*/
function TimespanColumn(isStart) {
this.isStart = isStart;
}
TimespanColumn.prototype.name = function () {
return this.isStart ? "Start" : "End";
};
TimespanColumn.prototype.value = function (domainObject) {
var isStart = this.isStart;
return domainObject.hasCapability('timespan') ?
domainObject.useCapability('timespan').then(function (timespan) {
return FORMATTER.format(
isStart ? timespan.getStart() : timespan.getEnd()
);
}) : "";
};
return TimespanColumn;
});

View File

@@ -43,11 +43,13 @@ define(
function modes(value) {
// Can be used as a setter...
if (arguments.length > 0 && Array.isArray(value)) {
// Update the relationships
mutator.mutate(function (model) {
model.relationships = model.relationships || {};
model.relationships[ACTIVITY_RELATIONSHIP] = value;
}).then(persister.persist);
if ((model.relationships || {})[ACTIVITY_RELATIONSHIP] !== value) {
// Update the relationships
mutator.mutate(function (model) {
model.relationships = model.relationships || {};
model.relationships[ACTIVITY_RELATIONSHIP] = value;
}).then(persister.persist);
}
}
// ...otherwise, use as a getter
return (model.relationships || {})[ACTIVITY_RELATIONSHIP] || [];

View File

@@ -72,12 +72,6 @@ define(
return swimlane.children.map(matches).reduce(or, false);
}
// Remove a domain object from its current location
function remove(domainObject) {
return domainObject &&
domainObject.getCapability('action').perform('remove');
}
// Initiate mutation of a domain object
function doMutate(domainObject, mutator) {
return asPromise(
@@ -104,6 +98,27 @@ define(
return swimlane.highlight() || expandedForDropInto();
}
function isReorder(targetObject, droppedObject) {
var droppedContext = droppedObject.getCapability('context'),
droppedParent =
droppedContext && droppedContext.getParent(),
droppedParentId = droppedParent && droppedParent.getId();
return targetObject.getId() === droppedParentId;
}
// Choose an appropriate composition action for the drag-and-drop
function chooseAction(targetObject, droppedObject) {
var actionCapability =
targetObject.getCapability('action'),
actionKey = droppedObject.hasCapability('editor') ?
'move' : 'link';
return actionCapability && actionCapability.getActions({
key: actionKey,
selectedObject: droppedObject
})[0];
}
// Choose an index for insertion in a domain object's composition
function chooseTargetIndex(id, offset, composition) {
return Math.max(
@@ -119,6 +134,10 @@ define(
function insert(id, target, indexOffset) {
var myId = swimlane.domainObject.getId();
return doMutate(target, function (model) {
model.composition =
model.composition.filter(function (compId) {
return compId !== id;
});
model.composition.splice(
chooseTargetIndex(myId, indexOffset, model.composition),
0,
@@ -127,17 +146,22 @@ define(
});
}
// Check if a compose action is allowed for the object in this
// swimlane (we handle the link differently to set the index,
// but check for the existence of the action to invole the
// relevant policies.)
function allowsCompose(swimlane, domainObject) {
var actionCapability =
swimlane.domainObject.getCapability('action');
return actionCapability && actionCapability.getActions({
key: 'compose',
selectedObject: domainObject
}).length > 0;
function canDrop(targetObject, droppedObject) {
return isReorder(targetObject, droppedObject) ||
!!chooseAction(targetObject, droppedObject);
}
function drop(domainObject, targetObject, indexOffset) {
function changeIndex() {
var id = domainObject.getId();
return insert(id, targetObject, indexOffset);
}
return isReorder(targetObject, domainObject) ?
changeIndex() :
chooseAction(targetObject, domainObject)
.perform().then(changeIndex);
}
return {
@@ -152,7 +176,7 @@ define(
return inEditMode() &&
!pathContains(swimlane, id) &&
!contains(swimlane, id) &&
allowsCompose(swimlane, domainObject);
canDrop(swimlane.domainObject, domainObject);
},
/**
* Check if a drop-after should be allowed for this swimlane,
@@ -167,7 +191,7 @@ define(
return inEditMode() &&
target &&
!pathContains(target, id) &&
allowsCompose(target, domainObject);
canDrop(target.domainObject, domainObject);
},
/**
* Drop the provided domain object into a timeline. This is
@@ -190,11 +214,7 @@ define(
Number.POSITIVE_INFINITY;
if (swimlane.highlight() || swimlane.highlightBottom()) {
// Remove the domain object from its original location...
return asPromise(remove(domainObject)).then(function () {
// ...then insert it at its new location.
insert(id, dropTarget, dropIndexOffset);
});
return drop(domainObject, dropTarget, dropIndexOffset);
}
}
};
@@ -202,4 +222,4 @@ define(
return TimelineSwimlaneDropHandler;
}
);
);

View File

@@ -79,12 +79,18 @@ define(
),
draggedSwimlane = dndService.getData(
SwimlaneDragConstants.TIMELINE_SWIMLANE_DRAG_TYPE
);
),
droppedObject = draggedSwimlane ?
draggedSwimlane.domainObject :
dndService.getData(
SwimlaneDragConstants.MCT_EXTENDED_DRAG_TYPE
);
if (id) {
event.stopPropagation();
e.preventDefault();
// Delegate the drop to the swimlane itself
swimlane.drop(id, (draggedSwimlane || {}).domainObject);
swimlane.drop(id, droppedObject);
}
// Clear the swimlane highlights
@@ -94,21 +100,37 @@ define(
function link(scope, element, attrs) {
// Lookup swimlane by evaluating this attribute
function swimlane() {
function lookupSwimlane() {
return scope.$eval(attrs.mctSwimlaneDrop);
}
// Handle dragover
element.on('dragover', function (e) {
dragOver(e, element, swimlane());
var swimlane = lookupSwimlane(),
highlight = swimlane.highlight(),
highlightBottom = swimlane.highlightBottom();
dragOver(e, element, swimlane);
if (highlightBottom !== swimlane.highlightBottom() ||
highlight !== swimlane.highlight()) {
scope.$apply();
}
});
// Handle drops
element.on('drop', function (e) {
drop(e, element, swimlane());
drop(e, element, lookupSwimlane());
scope.$apply();
});
// Clear highlights when drag leaves this swimlane
element.on('dragleave', function () {
swimlane().highlight(false);
swimlane().highlightBottom(false);
var swimlane = lookupSwimlane(),
wasHighlighted = swimlane.highlight() ||
swimlane.highlightBottom();
swimlane.highlight(false);
swimlane.highlightBottom(false);
if (wasHighlighted) {
scope.$apply();
}
});
}

View File

@@ -0,0 +1,74 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define(
['../../src/actions/CompositionColumn'],
function (CompositionColumn) {
describe("CompositionColumn", function () {
var testIndex,
column;
beforeEach(function () {
testIndex = 3;
column = new CompositionColumn(testIndex);
});
it("includes a one-based index in its name", function () {
expect(column.name().indexOf(String(testIndex + 1)))
.not.toEqual(-1);
});
describe("value", function () {
var mockDomainObject,
testModel;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
'domainObject',
[ 'getId', 'getModel', 'getCapability' ]
);
testModel = {
composition: [ 'a', 'b', 'c', 'd', 'e', 'f' ]
};
mockDomainObject.getModel.andReturn(testModel);
});
it("returns a corresponding identifier", function () {
expect(column.value(mockDomainObject))
.toEqual(testModel.composition[testIndex]);
});
it("returns nothing when composition is exceeded", function () {
testModel.composition = [ 'foo' ];
expect(column.value(mockDomainObject)).toEqual("");
});
it("returns nothing when composition is absent", function () {
delete testModel.composition;
expect(column.value(mockDomainObject)).toEqual("");
});
});
});
}
);

View File

@@ -0,0 +1,153 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define(
['../../src/actions/ExportTimelineAsCSVAction'],
function (ExportTimelineAsCSVAction) {
describe("ExportTimelineAsCSVAction", function () {
var mockExportService,
mockNotificationService,
mockNotification,
mockDomainObject,
mockType,
testContext,
testType,
action;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
'domainObject',
[ 'getId', 'getModel', 'getCapability', 'hasCapability' ]
);
mockType = jasmine.createSpyObj('type', [ 'instanceOf' ]);
mockExportService = jasmine.createSpyObj(
'exportService',
[ 'exportCSV' ]
);
mockNotificationService = jasmine.createSpyObj(
'notificationService',
[ 'notify', 'error' ]
);
mockNotification = jasmine.createSpyObj(
'notification',
[ 'dismiss' ]
);
mockNotificationService.notify.andReturn(mockNotification);
mockDomainObject.hasCapability.andReturn(true);
mockDomainObject.getCapability.andReturn(mockType);
mockType.instanceOf.andCallFake(function (type) {
return type === testType;
});
testContext = { domainObject: mockDomainObject };
action = new ExportTimelineAsCSVAction(
mockExportService,
mockNotificationService,
testContext
);
});
it("is applicable to timelines", function () {
testType = 'timeline';
expect(ExportTimelineAsCSVAction.appliesTo(testContext))
.toBe(true);
});
it("is not applicable to non-timelines", function () {
testType = 'folder';
expect(ExportTimelineAsCSVAction.appliesTo(testContext))
.toBe(false);
});
describe("when performed", function () {
var testPromise,
mockCallback;
beforeEach(function () {
mockCallback = jasmine.createSpy('callback');
// White-boxy; we know most work is delegated
// to the associated Task, so stub out that interaction.
spyOn(action.task, "run").andCallFake(function () {
return new Promise(function (resolve, reject) {
testPromise = {
resolve: resolve,
reject: reject
};
});
});
action.perform().then(mockCallback);
});
it("shows a notification", function () {
expect(mockNotificationService.notify)
.toHaveBeenCalled();
});
it("starts an export task", function () {
expect(action.task.run).toHaveBeenCalled();
});
describe("and completed", function () {
beforeEach(function () {
testPromise.resolve();
waitsFor(function () {
return mockCallback.calls.length > 0;
});
});
it("dismisses the displayed notification", function () {
expect(mockNotification.dismiss)
.toHaveBeenCalled();
});
it("shows no error messages", function () {
expect(mockNotificationService.error)
.not.toHaveBeenCalled();
});
});
describe("and an error occurs", function () {
beforeEach(function () {
testPromise.reject();
waitsFor(function () {
return mockCallback.calls.length > 0;
});
});
it("dismisses the displayed notification", function () {
expect(mockNotification.dismiss)
.toHaveBeenCalled();
});
it("shows an error message", function () {
expect(mockNotificationService.error)
.toHaveBeenCalledWith(jasmine.any(String));
});
});
});
});
}
);

View File

@@ -0,0 +1,79 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define(
['../../src/actions/ExportTimelineAsCSVTask'],
function (ExportTimelineAsCSVTask) {
'use strict';
// Note that most responsibility is delegated to helper
// classes, so testing here is minimal.
describe("EXportTimelineAsCSVTask", function () {
var mockExportService,
mockDomainObject,
task;
beforeEach(function () {
mockExportService = jasmine.createSpyObj(
'exportService',
[ 'exportCSV' ]
);
mockDomainObject = jasmine.createSpyObj(
'domainObject',
[
'getCapability',
'useCapability',
'hasCapability',
'getId',
'getModel'
]
);
mockDomainObject.getId.andReturn('mock');
mockDomainObject.getModel.andReturn({});
task = new ExportTimelineAsCSVTask(
mockExportService,
mockDomainObject
);
});
describe("when run", function () {
var mockCallback;
beforeEach(function () {
mockCallback = jasmine.createSpy('callback');
task.run().then(mockCallback);
waitsFor(function () {
return mockCallback.calls.length > 0;
});
});
it("exports to CSV", function () {
expect(mockExportService.exportCSV)
.toHaveBeenCalled();
});
});
});
}
);

View File

@@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -19,38 +19,41 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
/**
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../src/NameColumn"],
function (NameColumn) {
describe("A name column", function () {
var mockDomainObject,
column;
['../../src/actions/IdColumn'],
function (IdColumn) {
describe("IdColumn", function () {
var column;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[ "getModel" ]
);
mockDomainObject.getModel.andReturn({
name: "Test object name"
column = new IdColumn();
});
it("has a name", function () {
expect(column.name()).toEqual(jasmine.any(String));
});
describe("value", function () {
var mockDomainObject,
testId;
beforeEach(function () {
testId = "foo";
mockDomainObject = jasmine.createSpyObj(
'domainObject',
[ 'getId', 'getModel', 'getCapability' ]
);
mockDomainObject.getId.andReturn(testId);
});
column = new NameColumn();
});
it("reports a column header", function () {
expect(column.getTitle()).toEqual("Name");
});
it("looks up name from an object's model", function () {
expect(column.getValue(mockDomainObject).text)
.toEqual("Test object name");
it("provides a domain object's identifier", function () {
expect(column.value(mockDomainObject))
.toEqual(testId);
});
});
});
}
);
);

View File

@@ -0,0 +1,76 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define(
['../../src/actions/MetadataColumn'],
function (MetadataColumn) {
describe("MetadataColumn", function () {
var testName,
column;
beforeEach(function () {
testName = 'Foo';
column = new MetadataColumn(testName);
});
it("reports its property name", function () {
expect(column.name()).toEqual(testName);
});
describe("value", function () {
var mockDomainObject,
testMetadata,
testIndex;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
'domainObject',
[ 'getId', 'getModel', 'getCapability', 'useCapability' ]
);
testMetadata = [
{ name: "Something else", value: 123 },
{ value: 456 },
{ name: "And something else", value: 789 }
];
testIndex = 1;
testMetadata[testIndex].name = testName;
mockDomainObject.useCapability.andCallFake(function (c) {
return (c === 'metadata') && testMetadata;
});
});
it("returns a corresponding value", function () {
expect(column.value(mockDomainObject))
.toEqual(testMetadata[testIndex].value);
});
it("returns nothing when no such property is present", function () {
testMetadata[testIndex].name = "Not " + testName;
expect(column.value(mockDomainObject)).toEqual("");
});
});
});
}
);

View File

@@ -0,0 +1,76 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define(
['../../src/actions/ModeColumn'],
function (ModeColumn) {
describe("ModeColumn", function () {
var testIndex,
column;
beforeEach(function () {
testIndex = 3;
column = new ModeColumn(testIndex);
});
it("includes a one-based index in its name", function () {
expect(column.name().indexOf(String(testIndex + 1)))
.not.toEqual(-1);
});
describe("value", function () {
var mockDomainObject,
testModel;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
'domainObject',
[ 'getId', 'getModel', 'getCapability' ]
);
testModel = {
relationships: {
modes: [ 'a', 'b', 'c', 'd', 'e', 'f' ]
}
};
mockDomainObject.getModel.andReturn(testModel);
});
it("returns a corresponding identifier", function () {
expect(column.value(mockDomainObject))
.toEqual(testModel.relationships.modes[testIndex]);
});
it("returns nothing when relationships are exceeded", function () {
testModel.relationships.modes = [ 'foo' ];
expect(column.value(mockDomainObject)).toEqual("");
});
it("returns nothing when mode relationships are absent", function () {
delete testModel.relationships.modes;
expect(column.value(mockDomainObject)).toEqual("");
});
});
});
}
);

View File

@@ -0,0 +1,129 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define(
['../../src/actions/TimelineColumnizer'],
function (TimelineColumnizer) {
describe("TimelineColumnizer", function () {
var mockDomainObjects,
testMetadata,
exporter;
function makeMockDomainObject(model, index) {
var mockDomainObject = jasmine.createSpyObj(
'domainObject-' + index,
[
'getId',
'getCapability',
'useCapability',
'hasCapability',
'getModel'
]
);
mockDomainObject.getId.andReturn('id-' + index);
mockDomainObject.getModel.andReturn(model);
mockDomainObject.useCapability.andCallFake(function (c) {
return c === 'metadata' && [];
});
return mockDomainObject;
}
beforeEach(function () {
var mockTimespan = jasmine.createSpyObj(
'timespan',
[ 'getStart', 'getEnd' ]
);
testMetadata = [
{ name: "abc", value: 123 },
{ name: "xyz", value: 456 }
];
mockDomainObjects = [
{ composition: [ 'a', 'b', 'c' ] },
{ relationships: { modes: [ 'x', 'y' ] } },
{ }
].map(makeMockDomainObject);
mockDomainObjects[1].hasCapability.andCallFake(function (c) {
return c === 'timespan';
});
mockDomainObjects[1].useCapability.andCallFake(function (c) {
return c === 'timespan' ? Promise.resolve(mockTimespan) :
c === 'metadata' ? [] : undefined;
});
mockDomainObjects[2].useCapability.andCallFake(function (c) {
return c === 'metadata' && testMetadata;
});
exporter = new TimelineColumnizer(mockDomainObjects);
});
describe("rows", function () {
var rows;
beforeEach(function () {
exporter.rows().then(function (r) {
rows = r;
});
waitsFor(function () {
return rows !== undefined;
});
});
it("include one row per domain object", function () {
expect(rows.length).toEqual(mockDomainObjects.length);
});
it("includes identifiers for each domain object", function () {
rows.forEach(function (row, index) {
var id = mockDomainObjects[index].getId();
expect(row.indexOf(id)).not.toEqual(-1);
});
});
});
describe("headers", function () {
var headers;
beforeEach(function () {
headers = exporter.headers();
});
it("contains all metadata properties", function () {
testMetadata.forEach(function (property) {
expect(headers.indexOf(property.name))
.not.toEqual(-1);
});
});
it("contains timespan properties", function () {
expect(headers.indexOf("Start")).not.toEqual(-1);
expect(headers.indexOf("End")).not.toEqual(-1);
});
});
});
}
);

View File

@@ -0,0 +1,137 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define([
"../../src/actions/TimelineTraverser"
], function (TimelineTraverser) {
'use strict';
describe("TimelineTraverser", function () {
var testModels,
mockDomainObjects,
traverser;
function addMockDomainObject(id) {
var mockDomainObject = jasmine.createSpyObj(
'domainObject-' + id,
[
'getId',
'getCapability',
'useCapability',
'hasCapability',
'getModel'
]
),
mockRelationships,
model = testModels[id];
mockDomainObject.getId.andReturn(id);
mockDomainObject.getModel.andReturn(model);
mockDomainObject.hasCapability.andCallFake(function (c) {
return c === 'composition' ? !!model.composition :
c === 'relationship' ? !!model.relationships :
false;
});
if (!!model.composition) {
mockDomainObject.useCapability.andCallFake(function (c) {
return c === 'composition' &&
Promise.resolve(model.composition.map(function (id) {
return mockDomainObjects[id];
}));
});
}
if (!!model.relationships) {
mockRelationships = jasmine.createSpyObj(
'relationship',
['getRelatedObjects']
);
mockRelationships.getRelatedObjects.andCallFake(function (k) {
var ids = model.relationships[k] || [];
return Promise.resolve(ids.map(function (id) {
return mockDomainObjects[id];
}));
});
mockDomainObject.getCapability.andCallFake(function (c) {
return c === 'relationship' && mockRelationships;
});
}
mockDomainObjects[id] = mockDomainObject;
}
beforeEach(function () {
testModels = {
a: { composition: [ 'b', 'c' ]},
b: { composition: [ 'c' ] },
c: { relationships: { modes: [ 'd' ] } },
d: {},
unreachable: {}
};
mockDomainObjects = {};
Object.keys(testModels).forEach(addMockDomainObject);
traverser = new TimelineTraverser(mockDomainObjects.a);
});
describe("buildObjectList", function () {
var objects;
function contains(id) {
return objects.some(function (object) {
return object.getId() === id;
});
}
beforeEach(function () {
traverser.buildObjectList().then(function (objectList) {
objects = objectList;
});
waitsFor(function () {
return objects !== undefined;
});
});
it("includes the object originally passed in", function () {
expect(contains('a')).toBe(true);
});
it("includes objects reachable via composition", function () {
expect(contains('b')).toBe(true);
expect(contains('c')).toBe(true);
});
it("includes objects reachable via relationships", function () {
expect(contains('d')).toBe(true);
});
it("does not include unreachable objects", function () {
expect(contains('unreachable')).toBe(false);
});
});
});
});

View File

@@ -0,0 +1,94 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2009-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web 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 Web 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.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,window,afterEach*/
define(
['../../src/actions/TimespanColumn', '../../src/TimelineFormatter'],
function (TimespanColumn, TimelineFormatter) {
describe("TimespanColumn", function () {
var testTimes,
mockTimespan,
mockDomainObject,
column;
beforeEach(function () {
testTimes = {
start: 101000,
end: 987654321
};
mockTimespan = jasmine.createSpyObj(
'timespan',
[ 'getStart', 'getEnd' ]
);
mockDomainObject = jasmine.createSpyObj(
'domainObject',
[ 'useCapability', 'hasCapability' ]
);
mockTimespan.getStart.andReturn(testTimes.start);
mockTimespan.getEnd.andReturn(testTimes.end);
mockDomainObject.useCapability.andCallFake(function (c) {
return c === 'timespan' && Promise.resolve(mockTimespan);
});
mockDomainObject.hasCapability.andCallFake(function (c) {
return c === 'timespan';
});
});
[ "start", "end" ].forEach(function (bound) {
describe("when referring to " + bound + " times", function () {
var name = bound.charAt(0).toUpperCase() + bound.slice(1);
beforeEach(function () {
column = new TimespanColumn(bound === "start");
});
it("is named \"" + name + "\"", function () {
expect(column.name()).toEqual(name);
});
describe("value", function () {
var testFormatter,
value;
beforeEach(function () {
value = undefined;
testFormatter = new TimelineFormatter();
column.value(mockDomainObject).then(function (v) {
value = v;
});
waitsFor(function () {
return value !== undefined;
});
});
it("returns a formatted " + bound + " time", function () {
var expected =
testFormatter.format(testTimes[bound]);
expect(value).toEqual(expected);
});
});
});
});
});
}
);

View File

@@ -30,12 +30,14 @@ define(
mockCapabilities,
testModel,
mockPromise,
testModes,
decorator;
beforeEach(function () {
mockSwimlane = {};
mockCapabilities = {};
testModel = {};
testModes = ['a', 'b', 'c'];
mockSelection = jasmine.createSpyObj('selection', ['select', 'get']);
@@ -133,6 +135,22 @@ define(
expect(mockCapabilities.persistence.persist).toHaveBeenCalled();
});
it("does not mutate modes when unchanged", function () {
testModel.relationships = { modes: testModes };
decorator.modes(testModes);
expect(mockCapabilities.mutation.mutate).not.toHaveBeenCalled();
expect(testModel.relationships.modes).toEqual(testModes);
});
it("does mutate modes when changed", function () {
var testModes2 = ['d', 'e', 'f'];
testModel.relationships = { modes: testModes };
decorator.modes(testModes2);
expect(mockCapabilities.mutation.mutate).toHaveBeenCalled();
mockCapabilities.mutation.mutate.mostRecentCall.args[0](testModel);
expect(testModel.relationships.modes).toBe(testModes2);
});
it("does not provide a 'remove' method with no parent", function () {
expect(decorator.remove).not.toEqual(jasmine.any(Function));
});

View File

@@ -29,9 +29,13 @@ define(
mockOtherObject,
mockActionCapability,
mockPersistence,
mockContext,
mockAction,
handler;
beforeEach(function () {
var mockPromise = jasmine.createSpyObj('promise', ['then']);
mockSwimlane = jasmine.createSpyObj(
"swimlane",
[ "highlight", "highlightBottom" ]
@@ -58,6 +62,11 @@ define(
[ "getId", "getCapability", "useCapability", "hasCapability" ]
);
mockAction = jasmine.createSpyObj('action', ['perform']);
mockAction.perform.andReturn(mockPromise);
mockPromise.then.andCallFake(function (callback) {
callback();
});
mockOtherObject = jasmine.createSpyObj(
"domainObject",
@@ -65,20 +74,34 @@ define(
);
mockActionCapability = jasmine.createSpyObj("action", ["perform", "getActions"]);
mockPersistence = jasmine.createSpyObj("persistence", ["persist"]);
mockContext = jasmine.createSpyObj('context', [ 'getParent' ]);
mockActionCapability.getActions.andReturn([{}]);
mockActionCapability.getActions.andReturn([mockAction]);
mockSwimlane.parent.domainObject.getId.andReturn('a');
mockSwimlane.domainObject.getId.andReturn('b');
mockSwimlane.children[0].domainObject.getId.andReturn('c');
mockOtherObject.getId.andReturn('d');
mockSwimlane.domainObject.getCapability.andCallFake(function (c) {
return {
action: mockActionCapability,
persistence: mockPersistence
}[c];
});
mockOtherObject.getCapability.andReturn(mockActionCapability);
mockSwimlane.parent.domainObject.getCapability.andCallFake(function (c) {
return {
action: mockActionCapability,
persistence: mockPersistence
}[c];
});
mockOtherObject.getCapability.andCallFake(function (c) {
return {
action: mockActionCapability,
context: mockContext
}[c];
});
mockContext.getParent.andReturn(mockOtherObject);
mockSwimlane.domainObject.hasCapability.andReturn(true);
@@ -87,13 +110,17 @@ define(
it("disallows drop outside of edit mode", function () {
// Verify precondition
expect(handler.allowDropIn('d')).toBeTruthy();
expect(handler.allowDropAfter('d')).toBeTruthy();
expect(handler.allowDropIn('d', mockSwimlane.domainObject))
.toBeTruthy();
expect(handler.allowDropAfter('d', mockSwimlane.domainObject))
.toBeTruthy();
// Act as if we're not in edit mode
mockSwimlane.domainObject.hasCapability.andReturn(false);
// Now, they should be disallowed
expect(handler.allowDropIn('d')).toBeFalsy();
expect(handler.allowDropAfter('d')).toBeFalsy();
expect(handler.allowDropIn('d', mockSwimlane.domainObject))
.toBeFalsy();
expect(handler.allowDropAfter('d', mockSwimlane.domainObject))
.toBeFalsy();
// Verify that editor capability was really checked for
expect(mockSwimlane.domainObject.hasCapability)
@@ -101,8 +128,9 @@ define(
});
it("disallows dropping of parents", function () {
expect(handler.allowDropIn('a')).toBeFalsy();
expect(handler.allowDropAfter('a')).toBeFalsy();
var mockParent = mockSwimlane.parent.domainObject;
expect(handler.allowDropIn('a', mockParent)).toBeFalsy();
expect(handler.allowDropAfter('a', mockParent)).toBeFalsy();
});
it("does not drop when no highlight state is present", function () {
@@ -119,7 +147,7 @@ define(
it("inserts into when highlighted", function () {
var testModel = { composition: [ 'c' ] };
mockSwimlane.highlight.andReturn(true);
handler.drop('d');
handler.drop('d', mockOtherObject);
// Should have mutated
expect(mockSwimlane.domainObject.useCapability)
.toHaveBeenCalledWith("mutation", jasmine.any(Function));
@@ -131,24 +159,11 @@ define(
expect(mockPersistence.persist).toHaveBeenCalled();
});
it("removes objects before insertion, if provided", function () {
var testModel = { composition: [ 'c' ] };
mockSwimlane.highlight.andReturn(true);
handler.drop('d', mockOtherObject);
// Should have invoked a remove action
expect(mockActionCapability.perform)
.toHaveBeenCalledWith('remove');
// Verify that mutator still ran as expected
mockSwimlane.domainObject.useCapability.mostRecentCall
.args[1](testModel);
expect(testModel.composition).toEqual(['c', 'd']);
});
it("inserts after as a peer when highlighted at the bottom", function () {
var testModel = { composition: [ 'x', 'b', 'y' ] };
mockSwimlane.highlightBottom.andReturn(true);
mockSwimlane.expanded = false;
handler.drop('d');
handler.drop('d', mockOtherObject);
// Should have mutated
expect(mockSwimlane.parent.domainObject.useCapability)
.toHaveBeenCalledWith("mutation", jasmine.any(Function));
@@ -162,7 +177,7 @@ define(
var testModel = { composition: [ 'c' ] };
mockSwimlane.highlightBottom.andReturn(true);
mockSwimlane.expanded = true;
handler.drop('d');
handler.drop('d', mockOtherObject);
// Should have mutated
expect(mockSwimlane.domainObject.useCapability)
.toHaveBeenCalledWith("mutation", jasmine.any(Function));
@@ -177,7 +192,7 @@ define(
mockSwimlane.highlightBottom.andReturn(true);
mockSwimlane.expanded = true;
mockSwimlane.children = [];
handler.drop('d');
handler.drop('d', mockOtherObject);
// Should have mutated
expect(mockSwimlane.parent.domainObject.useCapability)
.toHaveBeenCalledWith("mutation", jasmine.any(Function));
@@ -187,6 +202,38 @@ define(
expect(testModel.composition).toEqual([ 'x', 'b', 'd', 'y']);
});
it("allows reordering within a parent", function () {
var testModel = { composition: [ 'x', 'b', 'y', 'd' ] };
mockSwimlane.highlightBottom.andReturn(true);
mockSwimlane.expanded = true;
mockSwimlane.children = [];
mockContext.getParent
.andReturn(mockSwimlane.parent.domainObject);
handler.drop('d', mockOtherObject);
waitsFor(function () {
return mockSwimlane.parent.domainObject.useCapability
.calls.length > 0;
});
runs(function () {
mockSwimlane.parent.domainObject.useCapability.mostRecentCall
.args[1](testModel);
expect(testModel.composition).toEqual([ 'x', 'b', 'd', 'y']);
});
});
it("does not invoke an action when reordering", function () {
mockSwimlane.highlightBottom.andReturn(true);
mockSwimlane.expanded = true;
mockSwimlane.children = [];
mockContext.getParent
.andReturn(mockSwimlane.parent.domainObject);
handler.drop('d', mockOtherObject);
expect(mockAction.perform).not.toHaveBeenCalled();
});
});
}
);
);

View File

@@ -52,7 +52,7 @@ define(
'dndService',
['setData', 'getData', 'removeData']
);
mockScope = jasmine.createSpyObj('$scope', ['$eval']);
mockScope = jasmine.createSpyObj('$scope', ['$eval', '$apply']);
mockElement = jasmine.createSpyObj('element', ['on']);
testAttrs = { mctSwimlaneDrop: "mockSwimlane" };
mockSwimlane = jasmine.createSpyObj(
@@ -115,6 +115,7 @@ define(
expect(mockSwimlane.highlight).toHaveBeenCalledWith(true);
expect(mockSwimlane.highlightBottom).toHaveBeenCalledWith(false);
expect(mockScope.$apply).toHaveBeenCalled();
});
it("updates bottom highlights on drag over", function () {
@@ -125,6 +126,7 @@ define(
expect(mockSwimlane.highlight).toHaveBeenCalledWith(false);
expect(mockSwimlane.highlightBottom).toHaveBeenCalledWith(true);
expect(mockScope.$apply).toHaveBeenCalled();
});
it("respects swimlane's allowDropIn response", function () {
@@ -154,12 +156,20 @@ define(
it("notifies swimlane on drop", function () {
handlers.drop(testEvent);
expect(mockSwimlane.drop).toHaveBeenCalledWith('abc', 'someDomainObject');
expect(mockScope.$apply).toHaveBeenCalled();
});
it("invokes preventDefault on drop", function () {
handlers.drop(testEvent);
expect(testEvent.preventDefault).toHaveBeenCalled();
});
it("clears highlights when drag leaves", function () {
mockSwimlane.highlight.andReturn(true);
handlers.dragleave();
expect(mockSwimlane.highlight).toHaveBeenCalledWith(false);
expect(mockSwimlane.highlightBottom).toHaveBeenCalledWith(false);
expect(mockScope.$apply).toHaveBeenCalled();
});
});
}