diff --git a/platform/commonUI/browse/bundle.js b/platform/commonUI/browse/bundle.js
index 443660cc8b..eccf3e96d7 100644
--- a/platform/commonUI/browse/bundle.js
+++ b/platform/commonUI/browse/bundle.js
@@ -27,6 +27,7 @@ define([
"./src/MenuArrowController",
"./src/navigation/NavigationService",
"./src/navigation/NavigateAction",
+ "./src/navigation/OrphanNavigationHandler",
"./src/windowing/NewTabAction",
"./src/windowing/FullscreenAction",
"./src/windowing/WindowTitler",
@@ -47,6 +48,7 @@ define([
MenuArrowController,
NavigationService,
NavigateAction,
+ OrphanNavigationHandler,
NewTabAction,
FullscreenAction,
WindowTitler,
@@ -253,6 +255,14 @@ define([
"$rootScope",
"$document"
]
+ },
+ {
+ "implementation": OrphanNavigationHandler,
+ "depends": [
+ "throttle",
+ "topic",
+ "navigationService"
+ ]
}
],
"licenses": [
diff --git a/platform/commonUI/browse/src/navigation/OrphanNavigationHandler.js b/platform/commonUI/browse/src/navigation/OrphanNavigationHandler.js
new file mode 100644
index 0000000000..00b3182e42
--- /dev/null
+++ b/platform/commonUI/browse/src/navigation/OrphanNavigationHandler.js
@@ -0,0 +1,75 @@
+/*****************************************************************************
+ * 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 () {
+
+ /**
+ * Navigates away from orphan objects whenever they are detected.
+ *
+ * An orphan object is an object whose apparent parent does not
+ * actually contain it. This may occur in certain circumstances, such
+ * as when persistence succeeds for a newly-created object but fails
+ * for its parent.
+ *
+ * @param throttle the `throttle` service
+ * @param topic the `topic` service
+ * @param navigationService the `navigationService`
+ * @constructor
+ */
+ function OrphanNavigationHandler(throttle, topic, navigationService) {
+ var throttledCheckNavigation;
+
+ function getParent(domainObject) {
+ var context = domainObject.getCapability('context');
+ return context.getParent();
+ }
+
+ function isOrphan(domainObject) {
+ var parent = getParent(domainObject),
+ composition = parent.getModel().composition,
+ id = domainObject.getId();
+ return !composition || (composition.indexOf(id) === -1);
+ }
+
+ function navigateToParent(domainObject) {
+ var parent = getParent(domainObject);
+ return parent.getCapability('action').perform('navigate');
+ }
+
+ function checkNavigation() {
+ var navigatedObject = navigationService.getNavigation();
+ if (navigatedObject.hasCapability('context') &&
+ isOrphan(navigatedObject)) {
+ if (!navigatedObject.getCapability('editor').isEditContextRoot()) {
+ navigateToParent(navigatedObject);
+ }
+ }
+ }
+
+ throttledCheckNavigation = throttle(checkNavigation);
+
+ navigationService.addListener(throttledCheckNavigation);
+ topic('mutation').listen(throttledCheckNavigation);
+ }
+
+ return OrphanNavigationHandler;
+});
diff --git a/platform/commonUI/browse/test/navigation/OrphanNavigationHandlerSpec.js b/platform/commonUI/browse/test/navigation/OrphanNavigationHandlerSpec.js
new file mode 100644
index 0000000000..4f71feedbd
--- /dev/null
+++ b/platform/commonUI/browse/test/navigation/OrphanNavigationHandlerSpec.js
@@ -0,0 +1,180 @@
+/*****************************************************************************
+* 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/navigation/OrphanNavigationHandler'
+], function (OrphanNavigationHandler) {
+ describe("OrphanNavigationHandler", function () {
+ var mockTopic,
+ mockThrottle,
+ mockMutationTopic,
+ mockNavigationService,
+ mockDomainObject,
+ mockParentObject,
+ mockContext,
+ mockActionCapability,
+ mockEditor,
+ testParentModel,
+ testId,
+ mockThrottledFns;
+
+ beforeEach(function () {
+ testId = 'some-identifier';
+
+ mockThrottledFns = [];
+ testParentModel = {};
+
+ mockTopic = jasmine.createSpy('topic');
+ mockThrottle = jasmine.createSpy('throttle');
+ mockNavigationService = jasmine.createSpyObj('navigationService', [
+ 'getNavigation',
+ 'addListener'
+ ]);
+ mockMutationTopic = jasmine.createSpyObj('mutationTopic', [
+ 'listen'
+ ]);
+ mockDomainObject = jasmine.createSpyObj('domainObject', [
+ 'getId',
+ 'getCapability',
+ 'getModel',
+ 'hasCapability'
+ ]);
+ mockParentObject = jasmine.createSpyObj('domainObject', [
+ 'getId',
+ 'getCapability',
+ 'getModel',
+ 'hasCapability'
+ ]);
+ mockContext = jasmine.createSpyObj('context', ['getParent']);
+ mockActionCapability = jasmine.createSpyObj('action', ['perform']);
+ mockEditor = jasmine.createSpyObj('editor', ['isEditContextRoot']);
+
+ mockThrottle.andCallFake(function (fn) {
+ var mockThrottledFn =
+ jasmine.createSpy('throttled-' + mockThrottledFns.length);
+ mockThrottledFn.andCallFake(fn);
+ mockThrottledFns.push(mockThrottledFn);
+ return mockThrottledFn;
+ });
+ mockTopic.andCallFake(function (k) {
+ return k === 'mutation' && mockMutationTopic;
+ });
+ mockDomainObject.getId.andReturn(testId);
+ mockDomainObject.getCapability.andCallFake(function (c) {
+ return {
+ context: mockContext,
+ editor: mockEditor
+ }[c];
+ });
+ mockDomainObject.hasCapability.andCallFake(function (c) {
+ return !!mockDomainObject.getCapability(c);
+ });
+ mockParentObject.getModel.andReturn(testParentModel);
+ mockParentObject.getCapability.andCallFake(function (c) {
+ return {
+ action: mockActionCapability
+ }[c];
+ });
+ mockContext.getParent.andReturn(mockParentObject);
+ mockNavigationService.getNavigation.andReturn(mockDomainObject);
+ mockEditor.isEditContextRoot.andReturn(false);
+
+ return new OrphanNavigationHandler(
+ mockThrottle,
+ mockTopic,
+ mockNavigationService
+ );
+ });
+
+
+ it("listens for mutation with a throttled function", function () {
+ expect(mockMutationTopic.listen)
+ .toHaveBeenCalledWith(jasmine.any(Function));
+ expect(mockThrottledFns.indexOf(
+ mockMutationTopic.listen.mostRecentCall.args[0]
+ )).not.toEqual(-1);
+ });
+
+ it("listens for navigation changes with a throttled function", function () {
+ expect(mockNavigationService.addListener)
+ .toHaveBeenCalledWith(jasmine.any(Function));
+ expect(mockThrottledFns.indexOf(
+ mockNavigationService.addListener.mostRecentCall.args[0]
+ )).not.toEqual(-1);
+ });
+
+ [false, true].forEach(function (isOrphan) {
+ var prefix = isOrphan ? "" : "non-";
+ describe("for " + prefix + "orphan objects", function () {
+ beforeEach(function () {
+ testParentModel.composition = isOrphan ? [] : [testId];
+ });
+
+ [false, true].forEach(function (isEditRoot) {
+ var caseName = isEditRoot ?
+ "that are being edited" : "that are not being edited";
+
+ function itNavigatesAsExpected() {
+ if (isOrphan && !isEditRoot) {
+ it("navigates to the parent", function () {
+ expect(mockActionCapability.perform)
+ .toHaveBeenCalledWith('navigate');
+ });
+ } else {
+ it("does nothing", function () {
+ expect(mockActionCapability.perform)
+ .not.toHaveBeenCalled();
+ });
+ }
+ }
+
+ describe(caseName, function () {
+ beforeEach(function () {
+ mockEditor.isEditContextRoot.andReturn(isEditRoot);
+ });
+
+ describe("when navigation changes", function () {
+ beforeEach(function () {
+ mockNavigationService.addListener.mostRecentCall
+ .args[0](mockDomainObject);
+ });
+
+ itNavigatesAsExpected();
+ });
+
+ describe("when mutation occurs", function () {
+ beforeEach(function () {
+ mockMutationTopic.listen.mostRecentCall
+ .args[0](mockParentObject);
+ });
+
+ itNavigatesAsExpected();
+ });
+
+ });
+ });
+ });
+ });
+
+ });
+});
+
diff --git a/platform/commonUI/edit/bundle.js b/platform/commonUI/edit/bundle.js
index a07c5bb230..b8906107a0 100644
--- a/platform/commonUI/edit/bundle.js
+++ b/platform/commonUI/edit/bundle.js
@@ -253,7 +253,7 @@ define([
},
{
"category": "navigation",
- "message": "There are unsaved changes.",
+ "message": "Continuing will cause the loss of any unsaved changes.",
"implementation": EditNavigationPolicy
},
{
diff --git a/platform/commonUI/general/res/sass/user-environ/_layout.scss b/platform/commonUI/general/res/sass/user-environ/_layout.scss
index 9e8e9aaae1..8537c85520 100644
--- a/platform/commonUI/general/res/sass/user-environ/_layout.scss
+++ b/platform/commonUI/general/res/sass/user-environ/_layout.scss
@@ -288,8 +288,9 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
.left {
padding-right: $interiorMarginLg;
- .l-back:not(.s-status-editing) {
+ .l-back {
margin-right: $interiorMarginLg;
+ &.s-status-editing { display: none; }
}
}
}
diff --git a/platform/features/timeline/bundle.js b/platform/features/timeline/bundle.js
index eb66456ce4..ae7a283fc5 100644
--- a/platform/features/timeline/bundle.js
+++ b/platform/features/timeline/bundle.js
@@ -91,7 +91,12 @@ define([
"name": "Export Timeline as CSV",
"category": "contextual",
"implementation": ExportTimelineAsCSVAction,
- "depends": ["exportService", "notificationService"]
+ "depends": [
+ "$log",
+ "exportService",
+ "notificationService",
+ "resources[]"
+ ]
}
],
"constants": [
diff --git a/platform/features/timeline/res/sass/_activities.scss b/platform/features/timeline/res/sass/_activities.scss
index 5e729432f7..cfe69d6d01 100644
--- a/platform/features/timeline/res/sass/_activities.scss
+++ b/platform/features/timeline/res/sass/_activities.scss
@@ -1,16 +1,22 @@
.l-timeline-gantt {
+ min-width: 2px;
+ overflow: hidden;
position: absolute;
top: $timelineSwimlaneGanttVM; bottom: $timelineSwimlaneGanttVM;
.bar {
@include ellipsize();
height: $activityBarH;
- line-height: $activityBarH + 2;
+ line-height: $activityBarH;
padding: 0 $interiorMargin;
span {
- display: inline;
+ $iconW: 20px;
+ @include absPosDefault();
+ display: block;
&.s-activity-type {
+ right: auto; width: $iconW;
+ text-align: center;
&.timeline {
&:before {
content:"S";
@@ -23,7 +29,9 @@
}
}
&.s-title {
- text-shadow: rgba(black, 0.1) 0 1px 2px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ left: $iconW;
}
&.duration {
left: auto;
@@ -52,6 +60,10 @@
}
}
}
+ &.sm .bar span {
+ // Hide icon and label if width is too small
+ display: none;
+ }
}
.edit-mode .s-timeline-gantt,
@@ -59,7 +71,7 @@
.handle {
cursor: col-resize;
&.mid {
- cursor: move;
+ cursor: ew-resize;
}
}
}
\ No newline at end of file
diff --git a/platform/features/timeline/res/sass/_timeline-thematic.scss b/platform/features/timeline/res/sass/_timeline-thematic.scss
index 967a07462f..d4ab58b6e7 100644
--- a/platform/features/timeline/res/sass/_timeline-thematic.scss
+++ b/platform/features/timeline/res/sass/_timeline-thematic.scss
@@ -32,20 +32,10 @@
}
.s-timeline-gantt {
- $br: $controlCr;
.bar {
color: $colorGanttBarFg;
@include activityBg($colorGanttBarBg);
- border-radius: $br;
box-shadow: $shdwGanttBar;
- &.expanded {
- @include border-top-radius($br);
- @include border-bottom-radius(0);
- }
- &.leaf {
- @include border-top-radius(0);
- @include border-bottom-radius($br);
- }
.s-toggle {
color: $colorGanttToggle;
}
diff --git a/platform/features/timeline/res/sass/_timelines.scss b/platform/features/timeline/res/sass/_timelines.scss
index 251008a64c..006fccfbd9 100644
--- a/platform/features/timeline/res/sass/_timelines.scss
+++ b/platform/features/timeline/res/sass/_timelines.scss
@@ -52,6 +52,9 @@
// Tree area with item title
right: auto; // Set this to auto and uncomment width below when additional tabular columns are added
width: $timelineTabularTitleW;
+ .l-swimlanes-holder {
+ bottom: $scrollbarTrackSize;
+ }
}
&.l-tabular-r {
// Start, end, duration, activity modes columns
@@ -67,6 +70,7 @@
&.l-timeline-gantt {
.l-swimlanes-holder {
@include scrollV(scroll);
+ bottom: $scrollbarTrackSize;
}
}
&.l-timeline-resource-legend {
diff --git a/platform/features/timeline/res/templates/activity-gantt.html b/platform/features/timeline/res/templates/activity-gantt.html
index 69d1d8984a..1615431e91 100644
--- a/platform/features/timeline/res/templates/activity-gantt.html
+++ b/platform/features/timeline/res/templates/activity-gantt.html
@@ -20,6 +20,7 @@
at runtime from the About dialog for additional information.
-->
this.index ?
+ this.idMap[composition[this.index]] : "";
};
return CompositionColumn;
diff --git a/platform/features/timeline/src/actions/ExportTimelineAsCSVAction.js b/platform/features/timeline/src/actions/ExportTimelineAsCSVAction.js
index f71422358c..5b0e007e23 100644
--- a/platform/features/timeline/src/actions/ExportTimelineAsCSVAction.js
+++ b/platform/features/timeline/src/actions/ExportTimelineAsCSVAction.js
@@ -27,14 +27,23 @@ define(["./ExportTimelineAsCSVTask"], function (ExportTimelineAsCSVTask) {
*
* @param exportService the service used to perform the CSV export
* @param notificationService the service used to show notifications
+ * @param {Array} resources an array of `resources` extensions
* @param context the Action's context
* @implements {Action}
* @constructor
* @memberof {platform/features/timeline}
*/
- function ExportTimelineAsCSVAction(exportService, notificationService, context) {
+ function ExportTimelineAsCSVAction(
+ $log,
+ exportService,
+ notificationService,
+ resources,
+ context
+ ) {
+ this.$log = $log;
this.task = new ExportTimelineAsCSVTask(
exportService,
+ resources,
context.domainObject
);
this.notificationService = notificationService;
@@ -45,13 +54,15 @@ define(["./ExportTimelineAsCSVTask"], function (ExportTimelineAsCSVTask) {
notification = notificationService.notify({
title: "Exporting CSV",
unknownProgress: true
- });
+ }),
+ $log = this.$log;
return this.task.run()
.then(function () {
notification.dismiss();
})
- .catch(function () {
+ .catch(function (err) {
+ $log.warn(err);
notification.dismiss();
notificationService.error("Error exporting CSV");
});
diff --git a/platform/features/timeline/src/actions/ExportTimelineAsCSVTask.js b/platform/features/timeline/src/actions/ExportTimelineAsCSVTask.js
index b8d796b3c4..d026edff3a 100644
--- a/platform/features/timeline/src/actions/ExportTimelineAsCSVTask.js
+++ b/platform/features/timeline/src/actions/ExportTimelineAsCSVTask.js
@@ -35,11 +35,13 @@ define([
* @constructor
* @memberof {platform/features/timeline}
* @param exportService the service used to export as CSV
+ * @param resources the `resources` extension category
* @param {DomainObject} domainObject the timeline being exported
*/
- function ExportTimelineAsCSVTask(exportService, domainObject) {
+ function ExportTimelineAsCSVTask(exportService, resources, domainObject) {
this.domainObject = domainObject;
this.exportService = exportService;
+ this.resources = resources;
}
/**
@@ -50,9 +52,10 @@ define([
*/
ExportTimelineAsCSVTask.prototype.run = function () {
var exportService = this.exportService;
+ var resources = this.resources;
function doExport(objects) {
- var exporter = new TimelineColumnizer(objects),
+ var exporter = new TimelineColumnizer(objects, resources),
options = { headers: exporter.headers() };
return exporter.rows().then(function (rows) {
return exportService.exportCSV(rows, options);
diff --git a/platform/features/timeline/src/actions/IdColumn.js b/platform/features/timeline/src/actions/IdColumn.js
index 38c8b9264e..9148ef6a8b 100644
--- a/platform/features/timeline/src/actions/IdColumn.js
+++ b/platform/features/timeline/src/actions/IdColumn.js
@@ -23,19 +23,23 @@
define([], function () {
/**
- * A column showing domain object identifiers.
+ * A column showing identifying domain objects.
* @constructor
+ * @param idMap an object containing key value pairs, where keys
+ * are domain object identifiers and values are whatever
+ * should appear in CSV output in their place
* @implements {platform/features/timeline.TimelineCSVColumn}
*/
- function IdColumn() {
+ function IdColumn(idMap) {
+ this.idMap = idMap;
}
IdColumn.prototype.name = function () {
- return "Identifier";
+ return "Index";
};
IdColumn.prototype.value = function (domainObject) {
- return domainObject.getId();
+ return this.idMap[domainObject.getId()];
};
return IdColumn;
diff --git a/platform/features/timeline/src/actions/ModeColumn.js b/platform/features/timeline/src/actions/ModeColumn.js
index fe2063566d..05eec7a30a 100644
--- a/platform/features/timeline/src/actions/ModeColumn.js
+++ b/platform/features/timeline/src/actions/ModeColumn.js
@@ -27,10 +27,14 @@ define([], function () {
* @constructor
* @param {number} index the zero-based index of the composition
* element associated with this column
+ * @param idMap an object containing key value pairs, where keys
+ * are domain object identifiers and values are whatever
+ * should appear in CSV output in their place
* @implements {platform/features/timeline.TimelineCSVColumn}
*/
- function ModeColumn(index) {
+ function ModeColumn(index, idMap) {
this.index = index;
+ this.idMap = idMap;
}
ModeColumn.prototype.name = function () {
@@ -39,8 +43,9 @@ define([], function () {
ModeColumn.prototype.value = function (domainObject) {
var model = domainObject.getModel(),
- composition = (model.relationships || {}).modes || [];
- return (composition[this.index]) || "";
+ modes = (model.relationships || {}).modes || [];
+ return modes.length > this.index ?
+ this.idMap[modes[this.index]] : "";
};
return ModeColumn;
diff --git a/platform/features/timeline/src/actions/TimelineColumnizer.js b/platform/features/timeline/src/actions/TimelineColumnizer.js
index f24fa20eee..92e54b1860 100644
--- a/platform/features/timeline/src/actions/TimelineColumnizer.js
+++ b/platform/features/timeline/src/actions/TimelineColumnizer.js
@@ -25,13 +25,15 @@ define([
"./ModeColumn",
"./CompositionColumn",
"./MetadataColumn",
- "./TimespanColumn"
+ "./TimespanColumn",
+ "./UtilizationColumn"
], function (
IdColumn,
ModeColumn,
CompositionColumn,
MetadataColumn,
- TimespanColumn
+ TimespanColumn,
+ UtilizationColumn
) {
/**
@@ -63,15 +65,17 @@ define([
*
* @param {DomainObject[]} domainObjects the objects to include
* in the exported data
+ * @param {Array} resources an array of `resources` extensions
* @constructor
* @memberof {platform/features/timeline}
*/
- function TimelineColumnizer(domainObjects) {
+ function TimelineColumnizer(domainObjects, resources) {
var maxComposition = 0,
maxRelationships = 0,
columnNames = {},
columns = [],
foundTimespan = false,
+ idMap,
i;
function addMetadataProperty(property) {
@@ -82,7 +86,12 @@ define([
}
}
- columns.push(new IdColumn());
+ idMap = domainObjects.reduce(function (map, domainObject, index) {
+ map[domainObject.getId()] = index + 1;
+ return map;
+ }, {});
+
+ columns.push(new IdColumn(idMap));
domainObjects.forEach(function (domainObject) {
var model = domainObject.getModel(),
@@ -113,12 +122,16 @@ define([
columns.push(new TimespanColumn(false));
}
+ resources.forEach(function (resource) {
+ columns.push(new UtilizationColumn(resource));
+ });
+
for (i = 0; i < maxComposition; i += 1) {
- columns.push(new CompositionColumn(i));
+ columns.push(new CompositionColumn(i, idMap));
}
for (i = 0; i < maxRelationships; i += 1) {
- columns.push(new ModeColumn(i));
+ columns.push(new ModeColumn(i, idMap));
}
this.domainObjects = domainObjects;
diff --git a/platform/features/timeline/src/actions/UtilizationColumn.js b/platform/features/timeline/src/actions/UtilizationColumn.js
new file mode 100644
index 0000000000..7a92ce668e
--- /dev/null
+++ b/platform/features/timeline/src/actions/UtilizationColumn.js
@@ -0,0 +1,72 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+
+define([], function () {
+ /**
+ * A column showing utilization costs associated with activities.
+ * @constructor
+ * @param {string} key the key for the particular cost
+ * @implements {platform/features/timeline.TimelineCSVColumn}
+ */
+ function UtilizationColumn(resource) {
+ this.resource = resource;
+ }
+
+ UtilizationColumn.prototype.name = function () {
+ var units = {
+ "Kbps": "Kb",
+ "watts": "watt-seconds"
+ }[this.resource.units] || "unknown units";
+
+ return this.resource.name + " (" + units + ")";
+ };
+
+ UtilizationColumn.prototype.value = function (domainObject) {
+ var resource = this.resource;
+
+ function getCost(utilization) {
+ var seconds = (utilization.end - utilization.start) / 1000;
+ return seconds * utilization.value;
+ }
+
+ function getUtilizationValue(utilizations) {
+ utilizations = utilizations.filter(function (utilization) {
+ return utilization.key === resource.key;
+ });
+
+ if (utilizations.length === 0) {
+ return "";
+ }
+
+ return utilizations.map(getCost).reduce(function (a, b) {
+ return a + b;
+ }, 0);
+ }
+
+ return domainObject.hasCapability('utilization') ?
+ domainObject.getCapability('utilization').internal()
+ .then(getUtilizationValue) :
+ "";
+ };
+
+ return UtilizationColumn;
+});
diff --git a/platform/features/timeline/src/capabilities/UtilizationCapability.js b/platform/features/timeline/src/capabilities/UtilizationCapability.js
index 2744976615..55976f3ab5 100644
--- a/platform/features/timeline/src/capabilities/UtilizationCapability.js
+++ b/platform/features/timeline/src/capabilities/UtilizationCapability.js
@@ -193,6 +193,13 @@ define(
* @returns {Promise.
} a promise for resource identifiers
*/
resources: promiseResourceKeys,
+ /**
+ * Get the resource utilization associated with this object
+ * directly, not including any resource utilization associated
+ * with contained objects.
+ * @returns {Promise.}
+ */
+ internal: promiseInternalUtilization,
/**
* Get the resource utilization associated with this
* object. Results are not sorted. This requires looking
diff --git a/platform/features/timeline/test/actions/CompositionColumnSpec.js b/platform/features/timeline/test/actions/CompositionColumnSpec.js
index 8cf566a080..df52d08db5 100644
--- a/platform/features/timeline/test/actions/CompositionColumnSpec.js
+++ b/platform/features/timeline/test/actions/CompositionColumnSpec.js
@@ -23,13 +23,20 @@
define(
['../../src/actions/CompositionColumn'],
function (CompositionColumn) {
+ var TEST_IDS = ['a', 'b', 'c', 'd', 'e', 'f'];
+
describe("CompositionColumn", function () {
var testIndex,
+ testIdMap,
column;
beforeEach(function () {
testIndex = 3;
- column = new CompositionColumn(testIndex);
+ testIdMap = TEST_IDS.reduce(function (map, id, index) {
+ map[id] = index;
+ return map;
+ }, {});
+ column = new CompositionColumn(testIndex, testIdMap);
});
it("includes a one-based index in its name", function () {
@@ -46,15 +53,13 @@ define(
'domainObject',
['getId', 'getModel', 'getCapability']
);
- testModel = {
- composition: ['a', 'b', 'c', 'd', 'e', 'f']
- };
+ testModel = { composition: TEST_IDS };
mockDomainObject.getModel.andReturn(testModel);
});
- it("returns a corresponding identifier", function () {
+ it("returns a corresponding value from the map", function () {
expect(column.value(mockDomainObject))
- .toEqual(testModel.composition[testIndex]);
+ .toEqual(testIdMap[testModel.composition[testIndex]]);
});
it("returns nothing when composition is exceeded", function () {
diff --git a/platform/features/timeline/test/actions/ExportTimelineAsCSVActionSpec.js b/platform/features/timeline/test/actions/ExportTimelineAsCSVActionSpec.js
index e0f09c3ae6..e31f25b074 100644
--- a/platform/features/timeline/test/actions/ExportTimelineAsCSVActionSpec.js
+++ b/platform/features/timeline/test/actions/ExportTimelineAsCSVActionSpec.js
@@ -24,7 +24,8 @@ define(
['../../src/actions/ExportTimelineAsCSVAction'],
function (ExportTimelineAsCSVAction) {
describe("ExportTimelineAsCSVAction", function () {
- var mockExportService,
+ var mockLog,
+ mockExportService,
mockNotificationService,
mockNotification,
mockDomainObject,
@@ -39,6 +40,13 @@ define(
['getId', 'getModel', 'getCapability', 'hasCapability']
);
mockType = jasmine.createSpyObj('type', ['instanceOf']);
+
+ mockLog = jasmine.createSpyObj('$log', [
+ 'warn',
+ 'error',
+ 'info',
+ 'debug'
+ ]);
mockExportService = jasmine.createSpyObj(
'exportService',
['exportCSV']
@@ -63,8 +71,10 @@ define(
testContext = { domainObject: mockDomainObject };
action = new ExportTimelineAsCSVAction(
+ mockLog,
mockExportService,
mockNotificationService,
+ [],
testContext
);
});
@@ -129,8 +139,11 @@ define(
});
describe("and an error occurs", function () {
+ var testError;
+
beforeEach(function () {
- testPromise.reject();
+ testError = { someProperty: "some value" };
+ testPromise.reject(testError);
waitsFor(function () {
return mockCallback.calls.length > 0;
});
@@ -145,6 +158,10 @@ define(
expect(mockNotificationService.error)
.toHaveBeenCalledWith(jasmine.any(String));
});
+
+ it("logs the root cause", function () {
+ expect(mockLog.warn).toHaveBeenCalledWith(testError);
+ });
});
});
});
diff --git a/platform/features/timeline/test/actions/ExportTimelineAsCSVTaskSpec.js b/platform/features/timeline/test/actions/ExportTimelineAsCSVTaskSpec.js
index 0330e86397..4deab99801 100644
--- a/platform/features/timeline/test/actions/ExportTimelineAsCSVTaskSpec.js
+++ b/platform/features/timeline/test/actions/ExportTimelineAsCSVTaskSpec.js
@@ -52,6 +52,7 @@ define(
task = new ExportTimelineAsCSVTask(
mockExportService,
+ [],
mockDomainObject
);
});
diff --git a/platform/features/timeline/test/actions/IdColumnSpec.js b/platform/features/timeline/test/actions/IdColumnSpec.js
index f44d255255..80b84680a4 100644
--- a/platform/features/timeline/test/actions/IdColumnSpec.js
+++ b/platform/features/timeline/test/actions/IdColumnSpec.js
@@ -24,10 +24,12 @@ define(
['../../src/actions/IdColumn'],
function (IdColumn) {
describe("IdColumn", function () {
- var column;
+ var testIdMap,
+ column;
beforeEach(function () {
- column = new IdColumn();
+ testIdMap = { "foo": "bar" };
+ column = new IdColumn(testIdMap);
});
it("has a name", function () {
@@ -47,9 +49,9 @@ define(
mockDomainObject.getId.andReturn(testId);
});
- it("provides a domain object's identifier", function () {
+ it("provides a value mapped from domain object's identifier", function () {
expect(column.value(mockDomainObject))
- .toEqual(testId);
+ .toEqual(testIdMap[testId]);
});
});
diff --git a/platform/features/timeline/test/actions/ModeColumnSpec.js b/platform/features/timeline/test/actions/ModeColumnSpec.js
index 446e3b1030..037aa5c34f 100644
--- a/platform/features/timeline/test/actions/ModeColumnSpec.js
+++ b/platform/features/timeline/test/actions/ModeColumnSpec.js
@@ -23,13 +23,20 @@
define(
['../../src/actions/ModeColumn'],
function (ModeColumn) {
+ var TEST_IDS = ['a', 'b', 'c', 'd', 'e', 'f'];
+
describe("ModeColumn", function () {
var testIndex,
+ testIdMap,
column;
beforeEach(function () {
testIndex = 3;
- column = new ModeColumn(testIndex);
+ testIdMap = TEST_IDS.reduce(function (map, id, index) {
+ map[id] = index;
+ return map;
+ }, {});
+ column = new ModeColumn(testIndex, testIdMap);
});
it("includes a one-based index in its name", function () {
@@ -48,15 +55,15 @@ define(
);
testModel = {
relationships: {
- modes: ['a', 'b', 'c', 'd', 'e', 'f']
+ modes: TEST_IDS
}
};
mockDomainObject.getModel.andReturn(testModel);
});
- it("returns a corresponding identifier", function () {
+ it("returns a corresponding value from the map", function () {
expect(column.value(mockDomainObject))
- .toEqual(testModel.relationships.modes[testIndex]);
+ .toEqual(testIdMap[testModel.relationships.modes[testIndex]]);
});
it("returns nothing when relationships are exceeded", function () {
diff --git a/platform/features/timeline/test/actions/TimelineColumnizerSpec.js b/platform/features/timeline/test/actions/TimelineColumnizerSpec.js
index d29bb14278..980ed1e6c3 100644
--- a/platform/features/timeline/test/actions/TimelineColumnizerSpec.js
+++ b/platform/features/timeline/test/actions/TimelineColumnizerSpec.js
@@ -75,7 +75,7 @@ define(
return c === 'metadata' && testMetadata;
});
- exporter = new TimelineColumnizer(mockDomainObjects);
+ exporter = new TimelineColumnizer(mockDomainObjects, []);
});
describe("rows", function () {
@@ -94,13 +94,6 @@ define(
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 () {