mirror of
https://github.com/nasa/openmct.git
synced 2025-06-29 04:03:03 +00:00
Compare commits
51 Commits
api-tutori
...
v0.10.2
Author | SHA1 | Date | |
---|---|---|---|
f9c93ca022 | |||
ee0fa0451a | |||
e86e955682 | |||
9913fb48f5 | |||
71c362f016 | |||
fa6e8fd5f9 | |||
23b64951f3 | |||
99590d18f7 | |||
86b31bc040 | |||
d52bfed1df | |||
44d6456de1 | |||
beeefe517a | |||
d02f4041b2 | |||
9fd75ff91e | |||
026ece3956 | |||
1d6880c283 | |||
8ddad9bf4c | |||
f167022eea | |||
b1266abf01 | |||
5a2d1a746d | |||
4f0e3fdf85 | |||
be9f56107c | |||
787f3815df | |||
35d7d9b380 | |||
dc577d4c24 | |||
8f9308de01 | |||
8f7a5e113b | |||
9820f9d9c5 | |||
56ff98cce7 | |||
dade6b2254 | |||
e9cac6eff3 | |||
5689279954 | |||
f9fd97230f | |||
536e2290b8 | |||
73b922facf | |||
ba0d9a186b | |||
80f5cb756d | |||
d7f566088f | |||
a3bcaea7f9 | |||
23c71b7218 | |||
463f7ccf65 | |||
87fe407739 | |||
bb4f1ce7cd | |||
0cc2ba7595 | |||
8162429106 | |||
ed519d89d7 | |||
0e4f6185b8 | |||
1ced47fc2c | |||
f16a107105 | |||
f683ca44a2 | |||
546cde56a8 |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "0.10.2-SNAPSHOT",
|
"version": "0.10.2",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.13.1",
|
"express": "^4.13.1",
|
||||||
|
@ -27,6 +27,7 @@ define([
|
|||||||
"./src/MenuArrowController",
|
"./src/MenuArrowController",
|
||||||
"./src/navigation/NavigationService",
|
"./src/navigation/NavigationService",
|
||||||
"./src/navigation/NavigateAction",
|
"./src/navigation/NavigateAction",
|
||||||
|
"./src/navigation/OrphanNavigationHandler",
|
||||||
"./src/windowing/NewTabAction",
|
"./src/windowing/NewTabAction",
|
||||||
"./src/windowing/FullscreenAction",
|
"./src/windowing/FullscreenAction",
|
||||||
"./src/windowing/WindowTitler",
|
"./src/windowing/WindowTitler",
|
||||||
@ -47,6 +48,7 @@ define([
|
|||||||
MenuArrowController,
|
MenuArrowController,
|
||||||
NavigationService,
|
NavigationService,
|
||||||
NavigateAction,
|
NavigateAction,
|
||||||
|
OrphanNavigationHandler,
|
||||||
NewTabAction,
|
NewTabAction,
|
||||||
FullscreenAction,
|
FullscreenAction,
|
||||||
WindowTitler,
|
WindowTitler,
|
||||||
@ -253,6 +255,14 @@ define([
|
|||||||
"$rootScope",
|
"$rootScope",
|
||||||
"$document"
|
"$document"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"implementation": OrphanNavigationHandler,
|
||||||
|
"depends": [
|
||||||
|
"throttle",
|
||||||
|
"topic",
|
||||||
|
"navigationService"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"licenses": [
|
"licenses": [
|
||||||
|
@ -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;
|
||||||
|
});
|
@ -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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -67,10 +67,17 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onCancel() {
|
function onCancel() {
|
||||||
return self.persistenceCapability.refresh().then(function (result) {
|
if (self.domainObject.getModel().persisted !== undefined) {
|
||||||
|
//Fetch clean model from persistence
|
||||||
|
return self.persistenceCapability.refresh().then(function (result) {
|
||||||
|
self.persistPending = false;
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
self.persistPending = false;
|
self.persistPending = false;
|
||||||
return result;
|
//Model is undefined in persistence, so return undefined.
|
||||||
});
|
return self.$q.when(undefined);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.transactionService.isActive()) {
|
if (this.transactionService.isActive()) {
|
||||||
|
@ -57,6 +57,15 @@ define(
|
|||||||
);
|
);
|
||||||
mockPersistence.persist.andReturn(fastPromise());
|
mockPersistence.persist.andReturn(fastPromise());
|
||||||
mockPersistence.refresh.andReturn(fastPromise());
|
mockPersistence.refresh.andReturn(fastPromise());
|
||||||
|
|
||||||
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
|
"domainObject",
|
||||||
|
[
|
||||||
|
"getModel"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
mockDomainObject.getModel.andReturn({persisted: 1});
|
||||||
|
|
||||||
capability = new TransactionalPersistenceCapability(mockQ, mockTransactionService, mockPersistence, mockDomainObject);
|
capability = new TransactionalPersistenceCapability(mockQ, mockTransactionService, mockPersistence, mockDomainObject);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -78,6 +87,20 @@ define(
|
|||||||
expect(mockPersistence.refresh).toHaveBeenCalled();
|
expect(mockPersistence.refresh).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("if transaction is active, cancel call is queued that refreshes model when appropriate", function () {
|
||||||
|
mockTransactionService.isActive.andReturn(true);
|
||||||
|
capability.persist();
|
||||||
|
expect(mockTransactionService.addToTransaction).toHaveBeenCalled();
|
||||||
|
|
||||||
|
mockDomainObject.getModel.andReturn({});
|
||||||
|
mockTransactionService.addToTransaction.mostRecentCall.args[1]();
|
||||||
|
expect(mockPersistence.refresh).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
mockDomainObject.getModel.andReturn({persisted: 1});
|
||||||
|
mockTransactionService.addToTransaction.mostRecentCall.args[1]();
|
||||||
|
expect(mockPersistence.refresh).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("persist call is only added to transaction once", function () {
|
it("persist call is only added to transaction once", function () {
|
||||||
mockTransactionService.isActive.andReturn(true);
|
mockTransactionService.isActive.andReturn(true);
|
||||||
capability.persist();
|
capability.persist();
|
||||||
|
@ -288,8 +288,9 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
|
|||||||
|
|
||||||
.left {
|
.left {
|
||||||
padding-right: $interiorMarginLg;
|
padding-right: $interiorMarginLg;
|
||||||
.l-back:not(.s-status-editing) {
|
.l-back {
|
||||||
margin-right: $interiorMarginLg;
|
margin-right: $interiorMarginLg;
|
||||||
|
&.s-status-editing { display: none; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,12 @@ define([
|
|||||||
"name": "Export Timeline as CSV",
|
"name": "Export Timeline as CSV",
|
||||||
"category": "contextual",
|
"category": "contextual",
|
||||||
"implementation": ExportTimelineAsCSVAction,
|
"implementation": ExportTimelineAsCSVAction,
|
||||||
"depends": ["exportService", "notificationService"]
|
"depends": [
|
||||||
|
"$log",
|
||||||
|
"exportService",
|
||||||
|
"notificationService",
|
||||||
|
"resources[]"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"constants": [
|
"constants": [
|
||||||
@ -467,6 +472,7 @@ define([
|
|||||||
"implementation": TimelineZoomController,
|
"implementation": TimelineZoomController,
|
||||||
"depends": [
|
"depends": [
|
||||||
"$scope",
|
"$scope",
|
||||||
|
"$timeout",
|
||||||
"TIMELINE_ZOOM_CONFIGURATION"
|
"TIMELINE_ZOOM_CONFIGURATION"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -128,7 +128,7 @@
|
|||||||
<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'"
|
<mct-include key="'timeline-ticks'"
|
||||||
parameters="{
|
parameters="{
|
||||||
fullWidth: timelineController.width(zoomController),
|
fullWidth: zoomController.width(timelineController.end()),
|
||||||
start: scroll.x,
|
start: scroll.x,
|
||||||
width: scroll.width,
|
width: scroll.width,
|
||||||
step: zoomController.toPixels(zoomController.zoom()),
|
step: zoomController.toPixels(zoomController.zoom()),
|
||||||
@ -141,7 +141,7 @@
|
|||||||
mct-scroll-x="scroll.x"
|
mct-scroll-x="scroll.x"
|
||||||
mct-scroll-y="scroll.y">
|
mct-scroll-y="scroll.y">
|
||||||
<div class="l-width-control"
|
<div class="l-width-control"
|
||||||
ng-style="{ width: timelineController.width(zoomController) + 'px' }">
|
ng-style="{ width: zoomController.width(timelineController.end()) + 'px' }">
|
||||||
<div class="t-swimlane s-swimlane l-swimlane"
|
<div class="t-swimlane s-swimlane l-swimlane"
|
||||||
ng-repeat="swimlane in timelineController.swimlanes()"
|
ng-repeat="swimlane in timelineController.swimlanes()"
|
||||||
ng-class="{
|
ng-class="{
|
||||||
@ -197,7 +197,7 @@
|
|||||||
<div mct-scroll-x="scroll.x"
|
<div mct-scroll-x="scroll.x"
|
||||||
class="t-pane-r-scroll-h-control l-scroll-control s-scroll-control">
|
class="t-pane-r-scroll-h-control l-scroll-control s-scroll-control">
|
||||||
<div class="l-width-control"
|
<div class="l-width-control"
|
||||||
ng-style="{ width: timelineController.width(zoomController) + 'px' }">
|
ng-style="{ width: zoomController.width(timelineController.end()) + 'px' }">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,11 +27,15 @@ define([], function () {
|
|||||||
* in a domain object's composition.
|
* in a domain object's composition.
|
||||||
* @param {number} index the zero-based index of the composition
|
* @param {number} index the zero-based index of the composition
|
||||||
* element associated with this column
|
* 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
|
||||||
* @constructor
|
* @constructor
|
||||||
* @implements {platform/features/timeline.TimelineCSVColumn}
|
* @implements {platform/features/timeline.TimelineCSVColumn}
|
||||||
*/
|
*/
|
||||||
function CompositionColumn(index) {
|
function CompositionColumn(index, idMap) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
|
this.idMap = idMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
CompositionColumn.prototype.name = function () {
|
CompositionColumn.prototype.name = function () {
|
||||||
@ -41,7 +45,9 @@ define([], function () {
|
|||||||
CompositionColumn.prototype.value = function (domainObject) {
|
CompositionColumn.prototype.value = function (domainObject) {
|
||||||
var model = domainObject.getModel(),
|
var model = domainObject.getModel(),
|
||||||
composition = model.composition || [];
|
composition = model.composition || [];
|
||||||
return (composition[this.index]) || "";
|
|
||||||
|
return composition.length > this.index ?
|
||||||
|
this.idMap[composition[this.index]] : "";
|
||||||
};
|
};
|
||||||
|
|
||||||
return CompositionColumn;
|
return CompositionColumn;
|
||||||
|
@ -27,14 +27,23 @@ define(["./ExportTimelineAsCSVTask"], function (ExportTimelineAsCSVTask) {
|
|||||||
*
|
*
|
||||||
* @param exportService the service used to perform the CSV export
|
* @param exportService the service used to perform the CSV export
|
||||||
* @param notificationService the service used to show notifications
|
* @param notificationService the service used to show notifications
|
||||||
|
* @param {Array} resources an array of `resources` extensions
|
||||||
* @param context the Action's context
|
* @param context the Action's context
|
||||||
* @implements {Action}
|
* @implements {Action}
|
||||||
* @constructor
|
* @constructor
|
||||||
* @memberof {platform/features/timeline}
|
* @memberof {platform/features/timeline}
|
||||||
*/
|
*/
|
||||||
function ExportTimelineAsCSVAction(exportService, notificationService, context) {
|
function ExportTimelineAsCSVAction(
|
||||||
|
$log,
|
||||||
|
exportService,
|
||||||
|
notificationService,
|
||||||
|
resources,
|
||||||
|
context
|
||||||
|
) {
|
||||||
|
this.$log = $log;
|
||||||
this.task = new ExportTimelineAsCSVTask(
|
this.task = new ExportTimelineAsCSVTask(
|
||||||
exportService,
|
exportService,
|
||||||
|
resources,
|
||||||
context.domainObject
|
context.domainObject
|
||||||
);
|
);
|
||||||
this.notificationService = notificationService;
|
this.notificationService = notificationService;
|
||||||
@ -45,13 +54,15 @@ define(["./ExportTimelineAsCSVTask"], function (ExportTimelineAsCSVTask) {
|
|||||||
notification = notificationService.notify({
|
notification = notificationService.notify({
|
||||||
title: "Exporting CSV",
|
title: "Exporting CSV",
|
||||||
unknownProgress: true
|
unknownProgress: true
|
||||||
});
|
}),
|
||||||
|
$log = this.$log;
|
||||||
|
|
||||||
return this.task.run()
|
return this.task.run()
|
||||||
.then(function () {
|
.then(function () {
|
||||||
notification.dismiss();
|
notification.dismiss();
|
||||||
})
|
})
|
||||||
.catch(function () {
|
.catch(function (err) {
|
||||||
|
$log.warn(err);
|
||||||
notification.dismiss();
|
notification.dismiss();
|
||||||
notificationService.error("Error exporting CSV");
|
notificationService.error("Error exporting CSV");
|
||||||
});
|
});
|
||||||
|
@ -35,11 +35,13 @@ define([
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @memberof {platform/features/timeline}
|
* @memberof {platform/features/timeline}
|
||||||
* @param exportService the service used to export as CSV
|
* @param exportService the service used to export as CSV
|
||||||
|
* @param resources the `resources` extension category
|
||||||
* @param {DomainObject} domainObject the timeline being exported
|
* @param {DomainObject} domainObject the timeline being exported
|
||||||
*/
|
*/
|
||||||
function ExportTimelineAsCSVTask(exportService, domainObject) {
|
function ExportTimelineAsCSVTask(exportService, resources, domainObject) {
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.exportService = exportService;
|
this.exportService = exportService;
|
||||||
|
this.resources = resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,9 +52,10 @@ define([
|
|||||||
*/
|
*/
|
||||||
ExportTimelineAsCSVTask.prototype.run = function () {
|
ExportTimelineAsCSVTask.prototype.run = function () {
|
||||||
var exportService = this.exportService;
|
var exportService = this.exportService;
|
||||||
|
var resources = this.resources;
|
||||||
|
|
||||||
function doExport(objects) {
|
function doExport(objects) {
|
||||||
var exporter = new TimelineColumnizer(objects),
|
var exporter = new TimelineColumnizer(objects, resources),
|
||||||
options = { headers: exporter.headers() };
|
options = { headers: exporter.headers() };
|
||||||
return exporter.rows().then(function (rows) {
|
return exporter.rows().then(function (rows) {
|
||||||
return exportService.exportCSV(rows, options);
|
return exportService.exportCSV(rows, options);
|
||||||
|
@ -23,19 +23,23 @@
|
|||||||
define([], function () {
|
define([], function () {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A column showing domain object identifiers.
|
* A column showing identifying domain objects.
|
||||||
* @constructor
|
* @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}
|
* @implements {platform/features/timeline.TimelineCSVColumn}
|
||||||
*/
|
*/
|
||||||
function IdColumn() {
|
function IdColumn(idMap) {
|
||||||
|
this.idMap = idMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
IdColumn.prototype.name = function () {
|
IdColumn.prototype.name = function () {
|
||||||
return "Identifier";
|
return "Index";
|
||||||
};
|
};
|
||||||
|
|
||||||
IdColumn.prototype.value = function (domainObject) {
|
IdColumn.prototype.value = function (domainObject) {
|
||||||
return domainObject.getId();
|
return this.idMap[domainObject.getId()];
|
||||||
};
|
};
|
||||||
|
|
||||||
return IdColumn;
|
return IdColumn;
|
||||||
|
@ -27,10 +27,14 @@ define([], function () {
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @param {number} index the zero-based index of the composition
|
* @param {number} index the zero-based index of the composition
|
||||||
* element associated with this column
|
* 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}
|
* @implements {platform/features/timeline.TimelineCSVColumn}
|
||||||
*/
|
*/
|
||||||
function ModeColumn(index) {
|
function ModeColumn(index, idMap) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
|
this.idMap = idMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
ModeColumn.prototype.name = function () {
|
ModeColumn.prototype.name = function () {
|
||||||
@ -39,8 +43,9 @@ define([], function () {
|
|||||||
|
|
||||||
ModeColumn.prototype.value = function (domainObject) {
|
ModeColumn.prototype.value = function (domainObject) {
|
||||||
var model = domainObject.getModel(),
|
var model = domainObject.getModel(),
|
||||||
composition = (model.relationships || {}).modes || [];
|
modes = (model.relationships || {}).modes || [];
|
||||||
return (composition[this.index]) || "";
|
return modes.length > this.index ?
|
||||||
|
this.idMap[modes[this.index]] : "";
|
||||||
};
|
};
|
||||||
|
|
||||||
return ModeColumn;
|
return ModeColumn;
|
||||||
|
@ -25,13 +25,15 @@ define([
|
|||||||
"./ModeColumn",
|
"./ModeColumn",
|
||||||
"./CompositionColumn",
|
"./CompositionColumn",
|
||||||
"./MetadataColumn",
|
"./MetadataColumn",
|
||||||
"./TimespanColumn"
|
"./TimespanColumn",
|
||||||
|
"./UtilizationColumn"
|
||||||
], function (
|
], function (
|
||||||
IdColumn,
|
IdColumn,
|
||||||
ModeColumn,
|
ModeColumn,
|
||||||
CompositionColumn,
|
CompositionColumn,
|
||||||
MetadataColumn,
|
MetadataColumn,
|
||||||
TimespanColumn
|
TimespanColumn,
|
||||||
|
UtilizationColumn
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,15 +65,17 @@ define([
|
|||||||
*
|
*
|
||||||
* @param {DomainObject[]} domainObjects the objects to include
|
* @param {DomainObject[]} domainObjects the objects to include
|
||||||
* in the exported data
|
* in the exported data
|
||||||
|
* @param {Array} resources an array of `resources` extensions
|
||||||
* @constructor
|
* @constructor
|
||||||
* @memberof {platform/features/timeline}
|
* @memberof {platform/features/timeline}
|
||||||
*/
|
*/
|
||||||
function TimelineColumnizer(domainObjects) {
|
function TimelineColumnizer(domainObjects, resources) {
|
||||||
var maxComposition = 0,
|
var maxComposition = 0,
|
||||||
maxRelationships = 0,
|
maxRelationships = 0,
|
||||||
columnNames = {},
|
columnNames = {},
|
||||||
columns = [],
|
columns = [],
|
||||||
foundTimespan = false,
|
foundTimespan = false,
|
||||||
|
idMap,
|
||||||
i;
|
i;
|
||||||
|
|
||||||
function addMetadataProperty(property) {
|
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) {
|
domainObjects.forEach(function (domainObject) {
|
||||||
var model = domainObject.getModel(),
|
var model = domainObject.getModel(),
|
||||||
@ -113,12 +122,16 @@ define([
|
|||||||
columns.push(new TimespanColumn(false));
|
columns.push(new TimespanColumn(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resources.forEach(function (resource) {
|
||||||
|
columns.push(new UtilizationColumn(resource));
|
||||||
|
});
|
||||||
|
|
||||||
for (i = 0; i < maxComposition; i += 1) {
|
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) {
|
for (i = 0; i < maxRelationships; i += 1) {
|
||||||
columns.push(new ModeColumn(i));
|
columns.push(new ModeColumn(i, idMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.domainObjects = domainObjects;
|
this.domainObjects = domainObjects;
|
||||||
|
72
platform/features/timeline/src/actions/UtilizationColumn.js
Normal file
72
platform/features/timeline/src/actions/UtilizationColumn.js
Normal file
@ -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;
|
||||||
|
});
|
@ -193,6 +193,13 @@ define(
|
|||||||
* @returns {Promise.<string[]>} a promise for resource identifiers
|
* @returns {Promise.<string[]>} a promise for resource identifiers
|
||||||
*/
|
*/
|
||||||
resources: promiseResourceKeys,
|
resources: promiseResourceKeys,
|
||||||
|
/**
|
||||||
|
* Get the resource utilization associated with this object
|
||||||
|
* directly, not including any resource utilization associated
|
||||||
|
* with contained objects.
|
||||||
|
* @returns {Promise.<Array>}
|
||||||
|
*/
|
||||||
|
internal: promiseInternalUtilization,
|
||||||
/**
|
/**
|
||||||
* Get the resource utilization associated with this
|
* Get the resource utilization associated with this
|
||||||
* object. Results are not sorted. This requires looking
|
* object. Results are not sorted. This requires looking
|
||||||
|
@ -79,15 +79,6 @@ define(
|
|||||||
graphPopulator.populate(swimlanePopulator.get());
|
graphPopulator.populate(swimlanePopulator.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get pixel width for right pane, using zoom controller
|
|
||||||
function width(zoomController) {
|
|
||||||
var start = swimlanePopulator.start(),
|
|
||||||
end = swimlanePopulator.end();
|
|
||||||
return zoomController.toPixels(zoomController.duration(
|
|
||||||
Math.max(end - start, MINIMUM_DURATION)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh resource graphs
|
// Refresh resource graphs
|
||||||
function refresh() {
|
function refresh() {
|
||||||
if (graphPopulator) {
|
if (graphPopulator) {
|
||||||
@ -121,10 +112,10 @@ define(
|
|||||||
// Expose active set of swimlanes
|
// Expose active set of swimlanes
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
* Get the width, in pixels, of the timeline area
|
* Get the end of the displayed timeline, in milliseconds.
|
||||||
* @returns {number} width, in pixels
|
* @returns {number} the end of the displayed timeline
|
||||||
*/
|
*/
|
||||||
width: width,
|
end: swimlanePopulator.end.bind(swimlanePopulator),
|
||||||
/**
|
/**
|
||||||
* Get the swimlanes which should currently be displayed.
|
* Get the swimlanes which should currently be displayed.
|
||||||
* @returns {TimelineSwimlane[]} the swimlanes
|
* @returns {TimelineSwimlane[]} the swimlanes
|
||||||
|
@ -22,27 +22,17 @@
|
|||||||
define(
|
define(
|
||||||
[],
|
[],
|
||||||
function () {
|
function () {
|
||||||
|
var PADDING = 0.25;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controls the pan-zoom state of a timeline view.
|
* Controls the pan-zoom state of a timeline view.
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function TimelineZoomController($scope, ZOOM_CONFIGURATION) {
|
function TimelineZoomController($scope, $timeout, ZOOM_CONFIGURATION) {
|
||||||
// Prefer to start with the middle index
|
// Prefer to start with the middle index
|
||||||
var zoomLevels = ZOOM_CONFIGURATION.levels || [1000],
|
var zoomLevels = ZOOM_CONFIGURATION.levels || [1000],
|
||||||
zoomIndex = Math.floor(zoomLevels.length / 2),
|
zoomIndex = Math.floor(zoomLevels.length / 2),
|
||||||
tickWidth = ZOOM_CONFIGURATION.width || 200,
|
tickWidth = ZOOM_CONFIGURATION.width || 200;
|
||||||
bounds = { x: 0, width: tickWidth },
|
|
||||||
duration = 86400000; // Default duration in view
|
|
||||||
|
|
||||||
// Round a duration to a larger value, to ensure space for editing
|
|
||||||
function roundDuration(value) {
|
|
||||||
// Ensure there's always an extra day or so
|
|
||||||
var tickCount = bounds.width / tickWidth,
|
|
||||||
sz = zoomLevels[zoomLevels.length - 1] * tickCount;
|
|
||||||
value *= 1.25; // Add 25% padding to start
|
|
||||||
return Math.ceil(value / sz) * sz;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toMillis(pixels) {
|
function toMillis(pixels) {
|
||||||
return (pixels / tickWidth) * zoomLevels[zoomIndex];
|
return (pixels / tickWidth) * zoomLevels[zoomIndex];
|
||||||
@ -63,14 +53,20 @@ define(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setScroll(x) {
|
||||||
|
$timeout(function () {
|
||||||
|
$scope.scroll.x = x;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
function initializeZoomFromTimespan(timespan) {
|
function initializeZoomFromTimespan(timespan) {
|
||||||
var timelineDuration = timespan.getDuration();
|
var timelineDuration = timespan.getDuration();
|
||||||
zoomIndex = 0;
|
zoomIndex = 0;
|
||||||
while (toMillis(bounds.width) < timelineDuration &&
|
while (toMillis($scope.scroll.width) < timelineDuration &&
|
||||||
zoomIndex < zoomLevels.length - 1) {
|
zoomIndex < zoomLevels.length - 1) {
|
||||||
zoomIndex += 1;
|
zoomIndex += 1;
|
||||||
}
|
}
|
||||||
bounds.x = toPixels(timespan.getStart());
|
setScroll(toPixels(timespan.getStart()));
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeZoom() {
|
function initializeZoom() {
|
||||||
@ -80,9 +76,6 @@ define(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.$watch("scroll", function (scroll) {
|
|
||||||
bounds = scroll;
|
|
||||||
});
|
|
||||||
$scope.$watch("domainObject", initializeZoom);
|
$scope.$watch("domainObject", initializeZoom);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -100,9 +93,10 @@ define(
|
|||||||
zoom: function (amount) {
|
zoom: function (amount) {
|
||||||
// Update the zoom level if called with an argument
|
// Update the zoom level if called with an argument
|
||||||
if (arguments.length > 0 && !isNaN(amount)) {
|
if (arguments.length > 0 && !isNaN(amount)) {
|
||||||
|
var bounds = $scope.scroll;
|
||||||
var center = this.toMillis(bounds.x + bounds.width / 2);
|
var center = this.toMillis(bounds.x + bounds.width / 2);
|
||||||
setZoomLevel(zoomIndex + amount);
|
setZoomLevel(zoomIndex + amount);
|
||||||
bounds.x = this.toPixels(center) - bounds.width / 2;
|
setScroll(this.toPixels(center) - bounds.width / 2);
|
||||||
}
|
}
|
||||||
return zoomLevels[zoomIndex];
|
return zoomLevels[zoomIndex];
|
||||||
},
|
},
|
||||||
@ -124,16 +118,14 @@ define(
|
|||||||
*/
|
*/
|
||||||
toMillis: toMillis,
|
toMillis: toMillis,
|
||||||
/**
|
/**
|
||||||
* Get or set the current displayed duration. If used as a
|
* Get the pixel width necessary to fit the specified
|
||||||
* setter, this will typically be rounded up to ensure extra
|
* timestamp, expressed as an offset in milliseconds from
|
||||||
* space is available at the right.
|
* the start of the timeline.
|
||||||
* @returns {number} duration, in milliseconds
|
* @param {number} timestamp the time to display
|
||||||
*/
|
*/
|
||||||
duration: function (value) {
|
width: function (timestamp) {
|
||||||
if (arguments.length > 0) {
|
var pixels = Math.ceil(toPixels(timestamp * (1 + PADDING)));
|
||||||
duration = roundDuration(value);
|
return Math.max($scope.scroll.width, pixels);
|
||||||
}
|
|
||||||
return duration;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -23,13 +23,20 @@
|
|||||||
define(
|
define(
|
||||||
['../../src/actions/CompositionColumn'],
|
['../../src/actions/CompositionColumn'],
|
||||||
function (CompositionColumn) {
|
function (CompositionColumn) {
|
||||||
|
var TEST_IDS = ['a', 'b', 'c', 'd', 'e', 'f'];
|
||||||
|
|
||||||
describe("CompositionColumn", function () {
|
describe("CompositionColumn", function () {
|
||||||
var testIndex,
|
var testIndex,
|
||||||
|
testIdMap,
|
||||||
column;
|
column;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
testIndex = 3;
|
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 () {
|
it("includes a one-based index in its name", function () {
|
||||||
@ -46,15 +53,13 @@ define(
|
|||||||
'domainObject',
|
'domainObject',
|
||||||
['getId', 'getModel', 'getCapability']
|
['getId', 'getModel', 'getCapability']
|
||||||
);
|
);
|
||||||
testModel = {
|
testModel = { composition: TEST_IDS };
|
||||||
composition: ['a', 'b', 'c', 'd', 'e', 'f']
|
|
||||||
};
|
|
||||||
mockDomainObject.getModel.andReturn(testModel);
|
mockDomainObject.getModel.andReturn(testModel);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns a corresponding identifier", function () {
|
it("returns a corresponding value from the map", function () {
|
||||||
expect(column.value(mockDomainObject))
|
expect(column.value(mockDomainObject))
|
||||||
.toEqual(testModel.composition[testIndex]);
|
.toEqual(testIdMap[testModel.composition[testIndex]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns nothing when composition is exceeded", function () {
|
it("returns nothing when composition is exceeded", function () {
|
||||||
|
@ -24,7 +24,8 @@ define(
|
|||||||
['../../src/actions/ExportTimelineAsCSVAction'],
|
['../../src/actions/ExportTimelineAsCSVAction'],
|
||||||
function (ExportTimelineAsCSVAction) {
|
function (ExportTimelineAsCSVAction) {
|
||||||
describe("ExportTimelineAsCSVAction", function () {
|
describe("ExportTimelineAsCSVAction", function () {
|
||||||
var mockExportService,
|
var mockLog,
|
||||||
|
mockExportService,
|
||||||
mockNotificationService,
|
mockNotificationService,
|
||||||
mockNotification,
|
mockNotification,
|
||||||
mockDomainObject,
|
mockDomainObject,
|
||||||
@ -39,6 +40,13 @@ define(
|
|||||||
['getId', 'getModel', 'getCapability', 'hasCapability']
|
['getId', 'getModel', 'getCapability', 'hasCapability']
|
||||||
);
|
);
|
||||||
mockType = jasmine.createSpyObj('type', ['instanceOf']);
|
mockType = jasmine.createSpyObj('type', ['instanceOf']);
|
||||||
|
|
||||||
|
mockLog = jasmine.createSpyObj('$log', [
|
||||||
|
'warn',
|
||||||
|
'error',
|
||||||
|
'info',
|
||||||
|
'debug'
|
||||||
|
]);
|
||||||
mockExportService = jasmine.createSpyObj(
|
mockExportService = jasmine.createSpyObj(
|
||||||
'exportService',
|
'exportService',
|
||||||
['exportCSV']
|
['exportCSV']
|
||||||
@ -63,8 +71,10 @@ define(
|
|||||||
testContext = { domainObject: mockDomainObject };
|
testContext = { domainObject: mockDomainObject };
|
||||||
|
|
||||||
action = new ExportTimelineAsCSVAction(
|
action = new ExportTimelineAsCSVAction(
|
||||||
|
mockLog,
|
||||||
mockExportService,
|
mockExportService,
|
||||||
mockNotificationService,
|
mockNotificationService,
|
||||||
|
[],
|
||||||
testContext
|
testContext
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -129,8 +139,11 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("and an error occurs", function () {
|
describe("and an error occurs", function () {
|
||||||
|
var testError;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
testPromise.reject();
|
testError = { someProperty: "some value" };
|
||||||
|
testPromise.reject(testError);
|
||||||
waitsFor(function () {
|
waitsFor(function () {
|
||||||
return mockCallback.calls.length > 0;
|
return mockCallback.calls.length > 0;
|
||||||
});
|
});
|
||||||
@ -145,6 +158,10 @@ define(
|
|||||||
expect(mockNotificationService.error)
|
expect(mockNotificationService.error)
|
||||||
.toHaveBeenCalledWith(jasmine.any(String));
|
.toHaveBeenCalledWith(jasmine.any(String));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("logs the root cause", function () {
|
||||||
|
expect(mockLog.warn).toHaveBeenCalledWith(testError);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -52,6 +52,7 @@ define(
|
|||||||
|
|
||||||
task = new ExportTimelineAsCSVTask(
|
task = new ExportTimelineAsCSVTask(
|
||||||
mockExportService,
|
mockExportService,
|
||||||
|
[],
|
||||||
mockDomainObject
|
mockDomainObject
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -24,10 +24,12 @@ define(
|
|||||||
['../../src/actions/IdColumn'],
|
['../../src/actions/IdColumn'],
|
||||||
function (IdColumn) {
|
function (IdColumn) {
|
||||||
describe("IdColumn", function () {
|
describe("IdColumn", function () {
|
||||||
var column;
|
var testIdMap,
|
||||||
|
column;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
column = new IdColumn();
|
testIdMap = { "foo": "bar" };
|
||||||
|
column = new IdColumn(testIdMap);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("has a name", function () {
|
it("has a name", function () {
|
||||||
@ -47,9 +49,9 @@ define(
|
|||||||
mockDomainObject.getId.andReturn(testId);
|
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))
|
expect(column.value(mockDomainObject))
|
||||||
.toEqual(testId);
|
.toEqual(testIdMap[testId]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -23,13 +23,20 @@
|
|||||||
define(
|
define(
|
||||||
['../../src/actions/ModeColumn'],
|
['../../src/actions/ModeColumn'],
|
||||||
function (ModeColumn) {
|
function (ModeColumn) {
|
||||||
|
var TEST_IDS = ['a', 'b', 'c', 'd', 'e', 'f'];
|
||||||
|
|
||||||
describe("ModeColumn", function () {
|
describe("ModeColumn", function () {
|
||||||
var testIndex,
|
var testIndex,
|
||||||
|
testIdMap,
|
||||||
column;
|
column;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
testIndex = 3;
|
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 () {
|
it("includes a one-based index in its name", function () {
|
||||||
@ -48,15 +55,15 @@ define(
|
|||||||
);
|
);
|
||||||
testModel = {
|
testModel = {
|
||||||
relationships: {
|
relationships: {
|
||||||
modes: ['a', 'b', 'c', 'd', 'e', 'f']
|
modes: TEST_IDS
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
mockDomainObject.getModel.andReturn(testModel);
|
mockDomainObject.getModel.andReturn(testModel);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns a corresponding identifier", function () {
|
it("returns a corresponding value from the map", function () {
|
||||||
expect(column.value(mockDomainObject))
|
expect(column.value(mockDomainObject))
|
||||||
.toEqual(testModel.relationships.modes[testIndex]);
|
.toEqual(testIdMap[testModel.relationships.modes[testIndex]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns nothing when relationships are exceeded", function () {
|
it("returns nothing when relationships are exceeded", function () {
|
||||||
|
@ -75,7 +75,7 @@ define(
|
|||||||
return c === 'metadata' && testMetadata;
|
return c === 'metadata' && testMetadata;
|
||||||
});
|
});
|
||||||
|
|
||||||
exporter = new TimelineColumnizer(mockDomainObjects);
|
exporter = new TimelineColumnizer(mockDomainObjects, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("rows", function () {
|
describe("rows", function () {
|
||||||
@ -94,13 +94,6 @@ define(
|
|||||||
it("include one row per domain object", function () {
|
it("include one row per domain object", function () {
|
||||||
expect(rows.length).toEqual(mockDomainObjects.length);
|
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 () {
|
describe("headers", function () {
|
||||||
|
@ -214,23 +214,6 @@ define(
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reports full scrollable width using zoom controller", function () {
|
|
||||||
var mockZoom = jasmine.createSpyObj('zoom', ['toPixels', 'duration']);
|
|
||||||
mockZoom.toPixels.andReturn(54321);
|
|
||||||
mockZoom.duration.andReturn(12345);
|
|
||||||
|
|
||||||
// Initially populate
|
|
||||||
fireWatch('domainObject', mockDomainObject);
|
|
||||||
|
|
||||||
expect(controller.width(mockZoom)).toEqual(54321);
|
|
||||||
// Verify interactions; we took zoom's duration for our start/end,
|
|
||||||
// and converted it to pixels.
|
|
||||||
// First, check that we used the start/end (from above)
|
|
||||||
expect(mockZoom.duration).toHaveBeenCalledWith(12321 - 42);
|
|
||||||
// Next, verify that the result was passed to toPixels
|
|
||||||
expect(mockZoom.toPixels).toHaveBeenCalledWith(12345);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("provides drag handles", function () {
|
it("provides drag handles", function () {
|
||||||
// TimelineDragPopulator et al are tested for these,
|
// TimelineDragPopulator et al are tested for these,
|
||||||
// so just verify that handles are indeed exposed.
|
// so just verify that handles are indeed exposed.
|
||||||
|
@ -28,6 +28,7 @@ define(
|
|||||||
describe("The timeline zoom state controller", function () {
|
describe("The timeline zoom state controller", function () {
|
||||||
var testConfiguration,
|
var testConfiguration,
|
||||||
mockScope,
|
mockScope,
|
||||||
|
mockTimeout,
|
||||||
controller;
|
controller;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
@ -37,8 +38,11 @@ define(
|
|||||||
};
|
};
|
||||||
mockScope = jasmine.createSpyObj("$scope", ['$watch']);
|
mockScope = jasmine.createSpyObj("$scope", ['$watch']);
|
||||||
mockScope.commit = jasmine.createSpy('commit');
|
mockScope.commit = jasmine.createSpy('commit');
|
||||||
|
mockScope.scroll = { x: 0, width: 1000 };
|
||||||
|
mockTimeout = jasmine.createSpy('$timeout');
|
||||||
controller = new TimelineZoomController(
|
controller = new TimelineZoomController(
|
||||||
mockScope,
|
mockScope,
|
||||||
|
mockTimeout,
|
||||||
testConfiguration
|
testConfiguration
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -47,12 +51,6 @@ define(
|
|||||||
expect(controller.zoom()).toEqual(2000);
|
expect(controller.zoom()).toEqual(2000);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("allows duration to be changed", function () {
|
|
||||||
var initial = controller.duration();
|
|
||||||
controller.duration(initial * 3.33);
|
|
||||||
expect(controller.duration() > initial).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles time-to-pixel conversions", function () {
|
it("handles time-to-pixel conversions", function () {
|
||||||
var zoomLevel = controller.zoom();
|
var zoomLevel = controller.zoom();
|
||||||
expect(controller.toPixels(zoomLevel)).toEqual(12321);
|
expect(controller.toPixels(zoomLevel)).toEqual(12321);
|
||||||
@ -70,11 +68,6 @@ define(
|
|||||||
expect(controller.zoom()).toEqual(3500);
|
expect(controller.zoom()).toEqual(3500);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("observes scroll bounds", function () {
|
|
||||||
expect(mockScope.$watch)
|
|
||||||
.toHaveBeenCalledWith("scroll", jasmine.any(Function));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when watches have fired", function () {
|
describe("when watches have fired", function () {
|
||||||
var mockDomainObject,
|
var mockDomainObject,
|
||||||
mockPromise,
|
mockPromise,
|
||||||
@ -115,6 +108,10 @@ define(
|
|||||||
mockScope.$watch.calls.forEach(function (call) {
|
mockScope.$watch.calls.forEach(function (call) {
|
||||||
call.args[1](mockScope[call.args[0]]);
|
call.args[1](mockScope[call.args[0]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mockTimeout.calls.forEach(function (call) {
|
||||||
|
call.args[0]();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("zooms to fit the timeline", function () {
|
it("zooms to fit the timeline", function () {
|
||||||
@ -125,6 +122,27 @@ define(
|
|||||||
expect(Math.round(controller.toMillis(x2)))
|
expect(Math.round(controller.toMillis(x2)))
|
||||||
.toBeGreaterThan(testEnd);
|
.toBeGreaterThan(testEnd);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("provides a width which is not less than scroll area width", function () {
|
||||||
|
var testPixel = mockScope.scroll.width / 4,
|
||||||
|
testMillis = controller.toMillis(testPixel);
|
||||||
|
expect(controller.width(testMillis))
|
||||||
|
.not.toBeLessThan(mockScope.scroll.width);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides a width with some margin past timestamp", function () {
|
||||||
|
var testPixel = mockScope.scroll.width * 4,
|
||||||
|
testMillis = controller.toMillis(testPixel);
|
||||||
|
expect(controller.width(testMillis))
|
||||||
|
.toBeGreaterThan(controller.toPixels(testMillis));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("provides a width which does not greatly exceed timestamp", function () {
|
||||||
|
var testPixel = mockScope.scroll.width * 4,
|
||||||
|
testMillis = controller.toMillis(testPixel);
|
||||||
|
expect(controller.width(testMillis))
|
||||||
|
.toBeLessThan(controller.toPixels(testMillis * 2));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user