[Time] Encode time settings in URL (#1620)

* [Time Conductor] Skeleton class for URL handling

#1550

* [Time Conductor] Scaffold in param handling

* [Time Conductor] Finish sketching in URL handler

* [Time Conductor] Usage correct constants for bounds

* [Time Conductor] Use start/end for bounds/deltas

* [Time] Move URL handler

Per discussion, https://github.com/nasa/openmct/issues/1550#issuecomment-308849449

* [Time] Rename URL handler

* [Time] Rename URL handler script

* [Time] Use Time API from URL handler

* [Time] Utilize Time API

* [Time] Listen for changes, synchronize URL

* [Time] Move URL handler into adapter

...as it uses Angular constructs.

* [Time] Wire URL handler into adapter bundle

* [Time] Pass correct constructor arguments to URL handler

* [Time] Begin debugging URL handling

* [Time] Clarify and correct logic for bounds/deltas in URL

* [Time] Define timeSystem

* [Time] Pass start/end into time API as numbers

...instead of strings.

* [Time] Avoid creating redundant objects

* [Time] Restructure fixed vs clock logic

* [Time] Stop clock correctly for fixed mode

* [Time] Fix hasBounds/hasDeltas logic

...given that both inputs will be strings when read from search params,
_.isFinite was doomed to return false.

* [Time] Check for complete information

...before updating Time API to reflect search state. Additionally,
flatten logic to ease readability.

* [Time] Begin testing TimeSettingsURLHandler

* [Time] Test fixed mode query string updates

* [Time] Test realtime mode query string updates

* [Time] Test update of time API from query params

* [Time] Add missing semicolons

Satisfies JSHint; fixes #1550
This commit is contained in:
Victor Woeltjen 2017-06-21 13:23:18 -05:00 committed by Pete Richards
parent 1d7d56d5e7
commit 0713941812
3 changed files with 314 additions and 0 deletions

View File

@ -32,6 +32,7 @@ define([
'./policies/AdapterCompositionPolicy',
'./policies/AdaptedViewPolicy',
'./runs/AlternateCompositionInitializer',
'./runs/TimeSettingsURLHandler',
'text!./templates/adapted-view-template.html'
], function (
legacyRegistry,
@ -45,6 +46,7 @@ define([
AdapterCompositionPolicy,
AdaptedViewPolicy,
AlternateCompositionInitializer,
TimeSettingsURLHandler,
adaptedViewTemplate
) {
legacyRegistry.register('src/adapter', {
@ -121,6 +123,16 @@ define([
{
implementation: AlternateCompositionInitializer,
depends: ["openmct"]
},
{
implementation: function (openmct, $location, $rootScope) {
return new TimeSettingsURLHandler(
openmct.time,
$location,
$rootScope
);
},
depends: ["openmct", "$location", "$rootScope"]
}
],
views: [

View File

@ -0,0 +1,117 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
// Parameter names in query string
var SEARCH = {
MODE: 'tc.mode',
TIME_SYSTEM: 'tc.timeSystem',
START_BOUND: 'tc.startBound',
END_BOUND: 'tc.endBound',
START_DELTA: 'tc.startDelta',
END_DELTA: 'tc.endDelta'
};
var TIME_EVENTS = ['bounds', 'timeSystem', 'clock', 'clockOffsets'];
/**
* Communicates settings from the URL to the time API,
* and vice versa.
*/
function TimeSettingsURLHandler(time, $location, $rootScope) {
this.time = time;
this.$location = $location;
$rootScope.$on('$locationChangeSuccess', this.updateTime.bind(this));
TIME_EVENTS.forEach(function (event) {
this.time.on(event, this.updateQueryParams.bind(this));
}, this);
this.updateTime(); // Initialize
}
TimeSettingsURLHandler.prototype.updateQueryParams = function () {
var clock = this.time.clock();
var fixed = !clock;
var mode = fixed ? 'fixed' : clock.key;
var timeSystem = this.time.timeSystem().key;
this.$location.search(SEARCH.MODE, mode);
this.$location.search(SEARCH.TIME_SYSTEM, timeSystem);
if (fixed) {
var bounds = this.time.bounds();
this.$location.search(SEARCH.START_BOUND, bounds.start);
this.$location.search(SEARCH.END_BOUND, bounds.end);
this.$location.search(SEARCH.START_DELTA, null);
this.$location.search(SEARCH.END_DELTA, null);
} else {
var deltas = this.time.clockOffsets();
this.$location.search(SEARCH.START_BOUND, null);
this.$location.search(SEARCH.END_BOUND, null);
this.$location.search(SEARCH.START_DELTA, -deltas.start);
this.$location.search(SEARCH.END_DELTA, deltas.end);
}
};
TimeSettingsURLHandler.prototype.updateTime = function () {
var searchParams = this.$location.search();
var mode = searchParams[SEARCH.MODE];
var timeSystem = searchParams[SEARCH.TIME_SYSTEM];
var clockOffsets = {
start: -searchParams[SEARCH.START_DELTA],
end: +searchParams[SEARCH.END_DELTA]
};
var bounds = {
start: +searchParams[SEARCH.START_BOUND],
end: +searchParams[SEARCH.END_BOUND]
};
var fixed = (mode === 'fixed');
var clock = fixed ? undefined : mode;
var hasDeltas =
!isNaN(parseInt(searchParams[SEARCH.START_DELTA], 0xA)) &&
!isNaN(parseInt(searchParams[SEARCH.END_DELTA], 0xA));
var hasBounds =
!isNaN(parseInt(searchParams[SEARCH.START_BOUND], 0xA)) &&
!isNaN(parseInt(searchParams[SEARCH.END_BOUND], 0xA));
if (fixed && timeSystem && hasBounds) {
this.time.timeSystem(timeSystem, bounds);
this.time.stopClock();
}
if (!fixed && clock && hasDeltas) {
this.time.clock(clock, clockOffsets);
this.time.timeSystem(timeSystem);
}
if (hasDeltas && !fixed) {
this.time.clockOffsets(clockOffsets);
}
if (hasBounds && fixed) {
this.time.bounds(bounds);
}
};
return TimeSettingsURLHandler;
});

View File

@ -0,0 +1,185 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./TimeSettingsURLHandler'], function (TimeSettingsURLHandler) {
describe("TimeSettingsURLHandler", function () {
var time;
var $location;
var $rootScope;
var search;
var handler;
beforeEach(function () {
time = jasmine.createSpyObj('time', [
'on',
'bounds',
'clockOffsets',
'timeSystem',
'clock',
'stopClock'
]);
$location = jasmine.createSpyObj('$location', [
'search'
]);
$rootScope = jasmine.createSpyObj('$rootScope', [
'$on'
]);
time.timeSystem.andReturn({ key: 'test-time-system' });
search = {};
$location.search.andCallFake(function (key, value) {
if (arguments.length === 0) {
return search;
}
if (value === null) {
delete search[key];
} else {
search[key] = String(value);
}
return this;
});
handler = new TimeSettingsURLHandler(
time,
$location,
$rootScope
);
});
['bounds', 'timeSystem', 'clock', 'clockOffsets'].forEach(function (event) {
it("listens for " + event + " time events", function () {
expect(time.on)
.toHaveBeenCalledWith(event, jasmine.any(Function));
});
describe("when " + event + " time event occurs with no clock", function () {
var expected;
beforeEach(function () {
expected = {
'tc.mode': 'fixed',
'tc.timeSystem': 'test-time-system',
'tc.startBound': '123',
'tc.endBound': '456'
};
time.clock.andReturn(undefined);
time.bounds.andReturn({ start: 123, end: 456 });
time.on.calls.forEach(function (call) {
if (call.args[0] === event) {
call.args[1]();
}
});
});
it("updates query parameters for fixed mode", function () {
expect(search).toEqual(expected);
});
});
describe("when " + event + " time event occurs with a clock", function () {
var expected;
beforeEach(function () {
expected = {
'tc.mode': 'clocky',
'tc.timeSystem': 'test-time-system',
'tc.startDelta': '123',
'tc.endDelta': '456'
};
time.clock.andReturn({ key: 'clocky' });
time.clockOffsets.andReturn({ start: -123, end: 456 });
time.on.calls.forEach(function (call) {
if (call.args[0] === event) {
call.args[1]();
}
});
});
it("updates query parameters for realtime mode", function () {
expect(search).toEqual(expected);
});
});
});
it("listens for location changes", function () {
expect($rootScope.$on)
.toHaveBeenCalledWith('$locationChangeSuccess', jasmine.any(Function));
});
[false, true].forEach(function (fixed) {
var name = fixed ? "fixed-time" : "real-time";
var suffix = fixed ? 'Bound' : 'Delta';
describe("when " + name + " location changes occur", function () {
beforeEach(function () {
search['tc.mode'] = fixed ? 'fixed' : 'clocky';
search['tc.timeSystem'] = 'some-time-system';
search['tc.start' + suffix] = '12321';
search['tc.end' + suffix] = '32123';
$rootScope.$on.mostRecentCall.args[1]();
});
if (fixed) {
var bounds = { start: 12321, end: 32123 };
it("stops the clock", function () {
expect(time.stopClock).toHaveBeenCalled();
});
it("sets the bounds", function () {
expect(time.bounds).toHaveBeenCalledWith(bounds);
});
it("sets the time system with bounds", function () {
expect(time.timeSystem).toHaveBeenCalledWith(
search['tc.timeSystem'],
bounds
);
});
} else {
var clockOffsets = { start: -12321, end: 32123 };
it("sets the clock", function () {
expect(time.stopClock).not.toHaveBeenCalled();
expect(time.clock).toHaveBeenCalledWith(
search['tc.mode'],
clockOffsets
);
});
it("sets clock offsets", function () {
expect(time.clockOffsets)
.toHaveBeenCalledWith(clockOffsets);
});
it("sets the time system without bounds", function () {
expect(time.timeSystem).toHaveBeenCalledWith(
search['tc.timeSystem']
);
});
}
});
});
});
});