Compare commits

..

2 Commits

Author SHA1 Message Date
cf5ef9fa60 Remove angular, hack around things that don't work 2021-04-05 15:09:22 -07:00
756e5d301e Removing angular 2021-04-02 18:27:09 -07:00
252 changed files with 10643 additions and 5670 deletions

View File

@ -1,43 +0,0 @@
<!--- This is for filing bugs. If you have a general question, please -->
<!--- visit https://github.com/nasa/openmct/discussions -->
---
name: Bug Report
about: File a Bug !
---
<!--- Focus on user impact in the title. Use the Summary Field to -->
<!--- describe the problem technically. -->
#### Summary
<!--- A description of the issue encountered. When possible, a description -->
<!--- of the impact of the issue. What use case does it impede?-->
#### Expected vs Current Behavior
<!--- Tell us what should have happened -->
#### Impact Check List
<!--- Please select from the following options -->
- [ ] Data loss or misrepresented data?
- [ ] Regression? Did this used to work or has it always been broken?
- [ ] Is there a workaround available?
- [ ] Does this impact a critical component?
- [ ] Is this just a visual bug?
#### Steps to Reproduce
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
1.
2.
3.
4.
#### Environment
* Open MCT Version: <!--- date of build, version, or SHA -->
* Deployment Type: <!--- npm dev? VIPER Dev? openmct-yams? -->
* OS:
* Browser:
#### Additional Information
<!--- Include any screenshots, gifs, or logs which will expedite triage -->

View File

@ -1 +0,0 @@
blank_issues_enabled: false

View File

@ -1,23 +0,0 @@
<!--- This is for filing enhancements or features. If you have a general -->
<!--- question, please visit https://github.com/nasa/openmct/discussions -->
---
name: Feature Request
about: Suggest an idea for this project
---
<!--
Thank you for suggesting an idea to make Open MCT better.
Please fill in as much of the template below as you're able.
-->
**Is your feature request related to a problem? Please describe.**
<!-- Please describe the problem you are trying to solve. -->
**Describe the solution you'd like**
<!--- Please describe the desired behavior. -->
**Describe alternatives you've considered**
<!--- Please describe alternative solutions or features you have considered. -->

View File

@ -1,12 +0,0 @@
### All Submissions:
* [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)?
* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/nasa/openmct/pulls) for the same update/change?
### Author Checklist
* [ ] Changes address original issue?
* [ ] Unit tests included and/or updated with changes?
* [ ] Command line build passes?
* [ ] Has this been smoke tested?
* [ ] Testing instructions included in associated issue?

3
API.md
View File

@ -423,14 +423,13 @@ attribute | type | flags | notes
###### Value Hints ###### Value Hints
Each telemetry value description has an object defining hints. Keys in this object represent the hint itself, and the value represents the weight of that hint. A lower weight means the hint has a higher priority. For example, multiple values could be hinted for use as the y-axis of a plot (raw, engineering), but the highest priority would be the default choice. Likewise, a table will use hints to determine the default order of columns. Each telemetry value description has an object defining hints. Keys in this this object represent the hint itself, and the value represents the weight of that hint. A lower weight means the hint has a higher priority. For example, multiple values could be hinted for use as the y-axis of a plot (raw, engineering), but the highest priority would be the default choice. Likewise, a table will use hints to determine the default order of columns.
Known hints: Known hints:
* `domain`: Values with a `domain` hint will be used for the x-axis of a plot, and tables will render columns for these values first. * `domain`: Values with a `domain` hint will be used for the x-axis of a plot, and tables will render columns for these values first.
* `range`: Values with a `range` hint will be used as the y-axis on a plot, and tables will render columns for these values after the `domain` values. * `range`: Values with a `range` hint will be used as the y-axis on a plot, and tables will render columns for these values after the `domain` values.
* `image`: Indicates that the value may be interpreted as the URL to an image file, in which case appropriate views will be made available. * `image`: Indicates that the value may be interpreted as the URL to an image file, in which case appropriate views will be made available.
* `imageDownloadName`: Indicates that the value may be interpreted as the name of the image file.
##### The Time Conductor and Telemetry ##### The Time Conductor and Telemetry

View File

@ -10,7 +10,7 @@ accept changes from external contributors.
The short version: The short version:
1. Write your contribution or describe your idea in the form of an [GitHub issue](https://github.com/nasa/openmct/issues/new/choose) or [Starting a GitHub Discussion](https://github.com/nasa/openmct/discussions) 1. Write your contribution.
2. Make sure your contribution meets code, test, and commit message 2. Make sure your contribution meets code, test, and commit message
standards as described below. standards as described below.
3. Submit a pull request from a topic branch back to `master`. Include a check 3. Submit a pull request from a topic branch back to `master`. Include a check
@ -18,7 +18,6 @@ The short version:
for review.) for review.)
4. Respond to any discussion. When the reviewer decides it's ready, they 4. Respond to any discussion. When the reviewer decides it's ready, they
will merge back `master` and fill out their own check list. will merge back `master` and fill out their own check list.
5. If you are a first-time contributor, please see [this discussion](https://github.com/nasa/openmct/discussions/3821) for further information.
## Contribution Process ## Contribution Process
@ -116,7 +115,7 @@ the pull request containing the reviewer checklist (from below) and complete
the merge back to the master branch. the merge back to the master branch.
Additionally: Additionally:
* Every pull request must link to the issue that it addresses. Eg. “Addresses #1234” or “Closes #1234”. This is the responsibility of the pull requests __author__. If no issue exists, [create one](https://github.com/nasa/openmct/issues/new/choose). * Every pull request must link to the issue that it addresses. Eg. “Addresses #1234” or “Closes #1234”. This is the responsibility of the pull requests __author__. If no issue exists, create one.
* Every __author__ must include testing instructions. These instructions should identify the areas of code affected, and some minimal test steps. If addressing a bug, reproduction steps should be included, if they were not included in the original issue. If reproduction steps were included on the original issue, and are sufficient, refer to them. * Every __author__ must include testing instructions. These instructions should identify the areas of code affected, and some minimal test steps. If addressing a bug, reproduction steps should be included, if they were not included in the original issue. If reproduction steps were included on the original issue, and are sufficient, refer to them.
* A pull request that closes an issue should say so in the description. Including the text “Closes #1234” will cause the linked issue to be automatically closed when the pull request is merged. This is the responsibility of the pull requests __author__. * A pull request that closes an issue should say so in the description. Including the text “Closes #1234” will cause the linked issue to be automatically closed when the pull request is merged. This is the responsibility of the pull requests __author__.
* When a pull request is merged, and the corresponding issue closed, the __reviewer__ must add the tag “unverified” to the original issue. This will indicate that although the issue is closed, it has not been tested yet. * When a pull request is merged, and the corresponding issue closed, the __reviewer__ must add the tag “unverified” to the original issue. This will indicate that although the issue is closed, it has not been tested yet.
@ -297,12 +296,23 @@ these standards.
Issues are tracked at https://github.com/nasa/openmct/issues. Issues are tracked at https://github.com/nasa/openmct/issues.
Issues should include:
* A short description of the issue encountered.
* A longer-form description of the issue encountered. When possible, steps to
reproduce the issue.
* When possible, a description of the impact of the issue. What use case does
it impede?
* An assessment of the severity of the issue.
Issue severity is categorized as follows (in ascending order): Issue severity is categorized as follows (in ascending order):
* _Trivial_: Minimal impact on the usefulness and functionality of the software; a "nice-to-have." Visual impact without functional impact, * _Trivial_: Minimal impact on the usefulness and functionality of the
* _Medium_: Some impairment of use, but simple workarounds exist software; a "nice-to-have."
* _Critical_: Significant loss of functionality or impairment of use. Display of telemetry data is not affected though. * _(Unspecified)_: Major loss of functionality or impairment of use.
* _Blocker_: Major functionality is impaired or lost, threatening mission success. Display of telemetry data is impaired or blocked by the bug, which could lead to loss of situational awareness. * _Critical_: Large-scale loss of functionality or impairment of use,
such that remaining utility becomes marginal.
* _Blocker_: Harmful or otherwise unacceptable behavior. Must fix.
## Check Lists ## Check Lists
@ -312,19 +322,16 @@ checklist).
### Author Checklist ### Author Checklist
[Within PR Template](.github/PULL_REQUEST_TEMPLATE.md) 1. Changes address original issue?
2. Unit tests included and/or updated with changes?
3. Command line build passes?
4. Changes have been smoke-tested?
5. Testing instructions included?
### Reviewer Checklist ### Reviewer Checklist
* [ ] Changes appear to address issue? 1. Changes appear to address issue?
* [ ] Appropriate unit tests included? 2. Appropriate unit tests included?
* [ ] Code style and in-line documentation are appropriate? 3. Code style and in-line documentation are appropriate?
* [ ] Commit messages meet standards? 4. Commit messages meet standards?
* [ ] Has associated issue been labelled `unverified`? (only applicable if this PR closes the issue) 5. Has associated issue been labelled `unverified`? (only applicable if this PR closes the issue)
* [ ] Has associated issue been labelled `bug`? (only applicable if this PR is for a bug fix)
* [ ] List of Acceptance Tests Performed.
Write out a small list of tests performed with just enough detail for another developer on the team
to execute.
i.e. ```When Clicking on Add button, new `object` appears in dropdown.```

View File

@ -44,7 +44,7 @@ The clearest examples for developing Open MCT plugins are in the
our documentation. our documentation.
We want Open MCT to be as easy to use, install, run, and develop for as We want Open MCT to be as easy to use, install, run, and develop for as
possible, and your feedback will help us get there! Feedback can be provided via [GitHub issues](https://github.com/nasa/openmct/issues/new/choose), [Starting a GitHub Discussion](https://github.com/nasa/openmct/discussions), or by emailing us at [arc-dl-openmct@mail.nasa.gov](mailto:arc-dl-openmct@mail.nasa.gov). possible, and your feedback will help us get there! Feedback can be provided via [GitHub issues](https://github.com/nasa/openmct/issues), or by emailing us at [arc-dl-openmct@mail.nasa.gov](mailto:arc-dl-openmct@mail.nasa.gov).
## Building Applications With Open MCT ## Building Applications With Open MCT

View File

@ -73,11 +73,11 @@ acceptance testing (e.g. by resolving any blockers found); any
resources not needed for this effort should be used to begin work resources not needed for this effort should be used to begin work
for the subsequent sprint. for the subsequent sprint.
| Week | Mon | Tue | Wed | Thu | Fri | | Week | Mon | Tue | Wed | Thu | Fri |
|:-----:|:-------------------------:|:------:|:---:|:----------------------------:|:-------------------------------------:| |:-----:|:-------------------------:|:------:|:---:|:----------------------------:|:-----------:|
| __1__ | Sprint plan | Tag-up | | | | | __1__ | Sprint plan | Tag-up | | | |
| __2__ | | Tag-up | | | Code freeze and sprint branch | | __2__ | | Tag-up | | | Code freeze |
| __3__ | Per-sprint testing | Triage | | _Per-sprint testing*_ | Ship and merge sprint branch to master| | __3__ | Per-sprint testing | Triage | | _Per-sprint testing*_ | Ship |
&ast; If necessary. &ast; If necessary.
@ -105,20 +105,14 @@ emphasis on testing.
that team may begin work for that sprint during the that team may begin work for that sprint during the
third week, since testing and blocker resolution is unlikely third week, since testing and blocker resolution is unlikely
to require all available resources. to require all available resources.
* Testing success criteria identified per issue (where necessary). This could be in the form of acceptance tests on the issue or detailing performance tests, for example.
* __Tag-up.__ Check in and status update among development team. * __Tag-up.__ Check in and status update among development team.
May amend plan for sprint as-needed. May amend plan for sprint as-needed.
* __Code freeze.__ Any new work from this sprint * __Code freeze.__ Any new work from this sprint
(features, bug fixes, enhancements) must be integrated by the (features, bug fixes, enhancements) must be integrated by the
end of the second week of the sprint. After code freeze, a sprint end of the second week of the sprint. After code freeze
branch will be created (and until the end of the sprint) the only (and until the end of the sprint) the only changes that should be
changes that should be merged into the sprint branch should merged into the master branch should directly address issues
directly address issues needed to pass acceptance testing. needed to pass acceptance testing.
During this time, any other feature development will continue to
be merged into the master branch for the next sprint.
* __Sprint branch merge to master.__ After acceptance testing, the sprint branch
will be merged back to the master branch. Any code conflicts that
arise will be resolved by the team.
* [__Per-release Testing.__](testing/plan.md#per-release-testing) * [__Per-release Testing.__](testing/plan.md#per-release-testing)
Structured testing with predefined Structured testing with predefined
success criteria. No release should ship without passing success criteria. No release should ship without passing
@ -132,8 +126,8 @@ emphasis on testing.
* [__Testathon.__](testing/plan.md#user-testing) * [__Testathon.__](testing/plan.md#user-testing)
Multi-user testing, involving as many users as Multi-user testing, involving as many users as
is feasible, plus development team. Open-ended; should verify is feasible, plus development team. Open-ended; should verify
completed work from this sprint using the sprint branch, test completed work from this sprint, test exploratorily for
exploratorily for regressions, et cetera. regressions, et cetera.
* [__Long-Duration Test.__](testing/plan.md#long-duration-testing) A * [__Long-Duration Test.__](testing/plan.md#long-duration-testing) A
test to verify that the software remains test to verify that the software remains
stable after running for longer durations. May include some stable after running for longer durations. May include some
@ -149,7 +143,7 @@ emphasis on testing.
Subset of Pre-release Testing Subset of Pre-release Testing
which should be performed before shipping at the end of any which should be performed before shipping at the end of any
sprint. Time is allocated for a second round of sprint. Time is allocated for a second round of
Pre-release Testing if the first round is not passed. Smoke tests collected from issues/PRs Pre-release Testing if the first round is not passed.
* __Triage.__ Team reviews issues from acceptance testing and uses * __Triage.__ Team reviews issues from acceptance testing and uses
success criteria to determine whether or not they should block success criteria to determine whether or not they should block
release, then formulates a plan to address these issues before release, then formulates a plan to address these issues before

View File

@ -19,7 +19,7 @@ Testing for Open MCT includes:
Manual, non-rigorous testing of the software and/or specific features Manual, non-rigorous testing of the software and/or specific features
of interest. Verifies that the software runs and that basic functionality of interest. Verifies that the software runs and that basic functionality
is present. The outcome of Smoke Testing should be a simplified list of Acceptance Tests which could be executed by another team member with sufficient context. is present.
### Unit Testing ### Unit Testing
@ -49,7 +49,7 @@ User testing will focus on the following activities:
* General "trying to break things." * General "trying to break things."
During user testing, users will During user testing, users will
[report issues](https://github.com/nasa/openmct/issues/new/choose) [report issues](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#issue-reporting)
as they are encountered. as they are encountered.
Desired outcomes of user testing are: Desired outcomes of user testing are:
@ -71,7 +71,7 @@ usage. After twenty-four hours, the software is evaluated for:
at the start of the test? Is it as responsive? at the start of the test? Is it as responsive?
Any defects or unexpected behavior identified during testing should be Any defects or unexpected behavior identified during testing should be
[reported as issues](https://github.com/nasa/openmct/issues/new/choose) [reported as issues](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#issue-reporting)
and reviewed for severity. and reviewed for severity.
## Test Performance ## Test Performance

View File

@ -92,8 +92,8 @@ should update (or delegate the task of updating) Open MCT version
numbers by the following process: numbers by the following process:
1. Update version number in `package.json` 1. Update version number in `package.json`
1. Checkout branch created for the last sprint that has been successfully tested. 1. Create a new branch off the `master` branch.
2. Remove a `-SNAPSHOT` suffix from the version in `package.json`. 2. Remove `-SNAPSHOT` suffix from the version in `package.json`.
3. Verify that resulting version number meets semantic versioning 3. Verify that resulting version number meets semantic versioning
requirements relative to previous stable version. Increment the requirements relative to previous stable version. Increment the
version number if necessary. version number if necessary.

View File

@ -93,36 +93,5 @@ define([
}; };
}; };
SinewaveLimitProvider.prototype.getLimits = function (domainObject) {
return {
limits: function () {
return {
WARNING: {
low: {
cssClass: "is-limit--lwr is-limit--yellow",
sin: -YELLOW.sin,
cos: -YELLOW.cos
},
high: {
cssClass: "is-limit--upr is-limit--yellow",
...YELLOW
}
},
DISTRESS: {
low: {
cssClass: "is-limit--lwr is-limit--red",
sin: -RED.sin,
cos: -RED.cos
},
high: {
cssClass: "is-limit--upr is-limit--red",
...RED
}
}
};
}
};
};
return SinewaveLimitProvider; return SinewaveLimitProvider;
}); });

View File

@ -50,16 +50,11 @@ define([
const IMAGE_DELAY = 20000; const IMAGE_DELAY = 20000;
function pointForTimestamp(timestamp, name) { function pointForTimestamp(timestamp, name) {
const url = IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length];
const urlItems = url.split('/');
const imageDownloadName = `example.imagery.${urlItems[urlItems.length - 1]}`;
return { return {
name, name: name,
utc: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY, utc: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
local: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY, local: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
url, url: IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length]
imageDownloadName
}; };
} }
@ -144,14 +139,6 @@ define([
hints: { hints: {
image: 1 image: 1
} }
},
{
name: 'Image Download Name',
key: 'imageDownloadName',
format: 'imageDownloadName',
hints: {
imageDownloadName: 1
}
} }
] ]
}; };

View File

@ -88,6 +88,7 @@
openmct.install(openmct.plugins.ExampleImagery()); openmct.install(openmct.plugins.ExampleImagery());
openmct.install(openmct.plugins.PlanLayout()); openmct.install(openmct.plugins.PlanLayout());
openmct.install(openmct.plugins.Timeline()); openmct.install(openmct.plugins.Timeline());
openmct.install(openmct.plugins.PlotVue());
openmct.install(openmct.plugins.UTCTimeSystem()); openmct.install(openmct.plugins.UTCTimeSystem());
openmct.install(openmct.plugins.AutoflowView({ openmct.install(openmct.plugins.AutoflowView({
type: "telemetry.panel" type: "telemetry.panel"

View File

@ -78,7 +78,6 @@ module.exports = (config) => {
preserveDescribeNesting: true, preserveDescribeNesting: true,
foldAll: false foldAll: false
}, },
browserConsoleLogOptions: { level: "error", format: "%b %T: %m", terminal: true },
coverageIstanbulReporter: { coverageIstanbulReporter: {
fixWebpackSourcePaths: true, fixWebpackSourcePaths: true,
dir: process.env.CIRCLE_ARTIFACTS ? dir: process.env.CIRCLE_ARTIFACTS ?

View File

@ -1,6 +1,6 @@
{ {
"name": "openmct", "name": "openmct",
"version": "1.7.3-SNAPSHOT", "version": "1.6.3-SNAPSHOT",
"description": "The Open MCT core platform", "description": "The Open MCT core platform",
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
@ -78,8 +78,7 @@
"zepto": "^1.2.0" "zepto": "^1.2.0"
}, },
"scripts": { "scripts": {
"clean": "rm -rf ./dist /node_modules; rm package-lock.json", "clean": "rm -rf ./dist",
"clean-test-lint": "npm run clean; npm install ; npm run test; npm run lint",
"start": "node app.js", "start": "node app.js",
"lint": "eslint platform example src --ext .js,.vue openmct.js", "lint": "eslint platform example src --ext .js,.vue openmct.js",
"lint:fix": "eslint platform example src --ext .js,.vue openmct.js --fix", "lint:fix": "eslint platform example src --ext .js,.vue openmct.js --fix",

View File

@ -86,7 +86,7 @@ define(
}) })
.join('/'); .join('/');
openmct.router.navigate(url); window.location.href = url;
if (isFirstViewEditable(object.useCapability('adapter'), objectPath)) { if (isFirstViewEditable(object.useCapability('adapter'), objectPath)) {
openmct.editor.edit(); openmct.editor.edit();

View File

@ -141,17 +141,11 @@ define(
if (mutationResult !== false) { if (mutationResult !== false) {
// Copy values if result was a different object // Copy values if result was a different object
// (either our clone or some other new thing) // (either our clone or some other new thing)
let modelHasChanged = _.isEqual(model, result) === false; if (model !== result) {
if (modelHasChanged) {
copyValues(model, result); copyValues(model, result);
} }
if (modelHasChanged model.modified = useTimestamp ? timestamp : now();
|| (useTimestamp !== undefined)
|| (model.modified === undefined)) {
model.modified = useTimestamp ? timestamp : now();
}
notifyListeners(model); notifyListeners(model);
} }

View File

@ -23,11 +23,13 @@
define([ define([
"moment-timezone", "moment-timezone",
"./src/indicators/ClockIndicator", "./src/indicators/ClockIndicator",
"./src/indicators/FollowIndicator",
"./src/services/TickerService", "./src/services/TickerService",
"./src/services/TimerService", "./src/services/TimerService",
"./src/controllers/ClockController", "./src/controllers/ClockController",
"./src/controllers/TimerController", "./src/controllers/TimerController",
"./src/controllers/RefreshingController", "./src/controllers/RefreshingController",
"./src/actions/FollowTimerAction",
"./src/actions/StartTimerAction", "./src/actions/StartTimerAction",
"./src/actions/RestartTimerAction", "./src/actions/RestartTimerAction",
"./src/actions/StopTimerAction", "./src/actions/StopTimerAction",
@ -37,11 +39,13 @@ define([
], function ( ], function (
MomentTimezone, MomentTimezone,
ClockIndicator, ClockIndicator,
FollowIndicator,
TickerService, TickerService,
TimerService, TimerService,
ClockController, ClockController,
TimerController, TimerController,
RefreshingController, RefreshingController,
FollowTimerAction,
StartTimerAction, StartTimerAction,
RestartTimerAction, RestartTimerAction,
StopTimerAction, StopTimerAction,
@ -140,6 +144,15 @@ define([
} }
], ],
"actions": [ "actions": [
{
"key": "timer.follow",
"implementation": FollowTimerAction,
"depends": ["timerService"],
"category": "contextual",
"name": "Follow Timer",
"cssClass": "icon-clock",
"priority": "optional"
},
{ {
"key": "timer.start", "key": "timer.start",
"implementation": StartTimerAction, "implementation": StartTimerAction,
@ -286,7 +299,10 @@ define([
} }
} }
], ],
"runs": [], "runs": [{
"implementation": FollowIndicator,
"depends": ["openmct", "timerService"]
}],
"licenses": [ "licenses": [
{ {
"name": "moment-duration-format", "name": "moment-duration-format",

View File

@ -0,0 +1,56 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, 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 () {
/**
* Designates a specific timer for following. Timelines, for example,
* use the actively followed timer to display a time-of-interest line
* and interpret time conductor bounds in the Timeline's relative
* time frame.
*
* @implements {Action}
* @memberof platform/features/clock
* @constructor
* @param {ActionContext} context the context for this action
*/
function FollowTimerAction(timerService, context) {
var domainObject =
context.domainObject
&& context.domainObject.useCapability('adapter');
this.perform =
timerService.setTimer.bind(timerService, domainObject);
}
FollowTimerAction.appliesTo = function (context) {
var model =
(context.domainObject && context.domainObject.getModel())
|| {};
return model.type === 'timer';
};
return FollowTimerAction;
}
);

View File

@ -0,0 +1,51 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
/**
* Indicator that displays the active timer, as well as its
* current state.
* @memberof platform/features/clock
*/
return function installFollowIndicator(openmct, timerService) {
var indicator = openmct.indicators.simpleIndicator();
var timer = timerService.getTimer();
setIndicatorStatus(timer);
function setIndicatorStatus(newTimer) {
if (newTimer !== undefined) {
indicator.iconClass('icon-timer');
indicator.statusClass('s-status-on');
indicator.text('Following timer ' + newTimer.name);
} else {
indicator.iconClass('icon-timer');
indicator.statusClass('s-status-disabled');
indicator.text('No timer being followed');
}
}
timerService.on('change', setIndicatorStatus);
openmct.indicators.add(indicator);
};
});

View File

@ -0,0 +1,89 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2016, 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([
"../../src/actions/FollowTimerAction"
], function (FollowTimerAction) {
var TIMER_SERVICE_METHODS =
['setTimer', 'getTimer', 'clearTimer', 'on', 'off'];
describe("The Follow Timer action", function () {
var testContext;
var testModel;
var testAdaptedObject;
beforeEach(function () {
testModel = {};
testContext = {
domainObject: jasmine.createSpyObj('domainObject', [
'getModel',
'useCapability'
])
};
testAdaptedObject = { foo: 'bar' };
testContext.domainObject.getModel.and.returnValue(testModel);
testContext.domainObject.useCapability.and.callFake(function (c) {
return c === 'adapter' && testAdaptedObject;
});
});
it("is applicable to timers", function () {
testModel.type = "timer";
expect(FollowTimerAction.appliesTo(testContext)).toBe(true);
});
it("is inapplicable to non-timers", function () {
testModel.type = "folder";
expect(FollowTimerAction.appliesTo(testContext)).toBe(false);
});
describe("when instantiated", function () {
var mockTimerService;
var action;
beforeEach(function () {
mockTimerService = jasmine.createSpyObj(
'timerService',
TIMER_SERVICE_METHODS
);
action = new FollowTimerAction(mockTimerService, testContext);
});
it("does not interact with the timer service", function () {
TIMER_SERVICE_METHODS.forEach(function (method) {
expect(mockTimerService[method]).not.toHaveBeenCalled();
});
});
describe("and performed", function () {
beforeEach(function () {
action.perform();
});
it("sets the active timer", function () {
expect(mockTimerService.setTimer)
.toHaveBeenCalledWith(testAdaptedObject);
});
});
});
});
});

View File

@ -0,0 +1,96 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2009-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
"../../src/indicators/FollowIndicator",
"../../src/services/TimerService",
"../../../../../src/MCT",
'zepto'
], function (
FollowIndicator,
TimerService,
MCT,
$
) {
xdescribe("The timer-following indicator", function () {
var timerService;
var openmct;
beforeEach(function () {
openmct = new MCT();
timerService = new TimerService(openmct);
spyOn(openmct.indicators, "add");
});
it("adds an indicator when installed", function () {
FollowIndicator(openmct, timerService);
expect(openmct.indicators.add).toHaveBeenCalled();
});
it("indicates that no timer is being followed", function () {
FollowIndicator(openmct, timerService);
var simpleIndicator = openmct.indicators.add.calls.mostRecent().args[0];
var element = simpleIndicator.element;
var text = $('.indicator-text', element).text().trim();
expect(text).toEqual('No timer being followed');
});
describe("when a timer is set", function () {
var testObject;
var simpleIndicator;
beforeEach(function () {
testObject = {
identifier: {
namespace: 'namespace',
key: 'key'
},
name: "some timer!"
};
timerService.setTimer(testObject);
FollowIndicator(openmct, timerService);
simpleIndicator = openmct.indicators.add.calls.mostRecent().args[0];
});
it("displays the timer's name", function () {
var element = simpleIndicator.element;
var text = $('.indicator-text', element).text().trim();
expect(text).toEqual('Following timer ' + testObject.name);
});
it("displays the timer's name when it changes", function () {
var secondTimer = {
identifier: {
namespace: 'namespace',
key: 'key2'
},
name: "Some other timer"
};
var element = simpleIndicator.element;
timerService.setTimer(secondTimer);
var text = $('.indicator-text', element).text().trim();
expect(text).toEqual('Following timer ' + secondTimer.name);
});
});
});
});

View File

@ -154,9 +154,7 @@ define(['zepto', 'objectUtils'], function ($, objectUtils) {
tree = JSON.stringify(tree).replace(new RegExp(oldIdKeyString, 'g'), newIdKeyString); tree = JSON.stringify(tree).replace(new RegExp(oldIdKeyString, 'g'), newIdKeyString);
return JSON.parse(tree, (key, value) => { return JSON.parse(tree, (key, value) => {
if (value !== undefined if (Object.prototype.hasOwnProperty.call(value, 'key')
&& value !== null
&& Object.prototype.hasOwnProperty.call(value, 'key')
&& Object.prototype.hasOwnProperty.call(value, 'namespace') && Object.prototype.hasOwnProperty.call(value, 'namespace')
&& value.key === oldId.key && value.key === oldId.key
&& value.namespace === oldId.namespace) { && value.namespace === oldId.namespace) {

View File

@ -0,0 +1,8 @@
# Couch DB Persistence Plugin
An adapter for using CouchDB for persistence of user-created objects. The plugin installation function takes the URL
for the CouchDB database as a parameter.
## Installation
```js
openmct.install(openmct.plugins.CouchDB('http://localhost:5984/openmct'))
```

View File

@ -0,0 +1,78 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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([
"./src/CouchPersistenceProvider",
"./src/CouchIndicator"
], function (
CouchPersistenceProvider,
CouchIndicator
) {
return {
name: "platform/persistence/couch",
definition: {
"name": "Couch Persistence",
"description": "Adapter to read and write objects using a CouchDB instance.",
"extensions": {
"components": [
{
"provides": "persistenceService",
"type": "provider",
"implementation": CouchPersistenceProvider,
"depends": [
"$http",
"$q",
"PERSISTENCE_SPACE",
"COUCHDB_PATH"
]
}
],
"constants": [
{
"key": "PERSISTENCE_SPACE",
"value": "mct"
},
{
"key": "COUCHDB_PATH",
"value": "/couch/openmct"
},
{
"key": "COUCHDB_INDICATOR_INTERVAL",
"value": 15000
}
],
"indicators": [
{
"implementation": CouchIndicator,
"depends": [
"$http",
"$interval",
"COUCHDB_PATH",
"COUCHDB_INDICATOR_INTERVAL"
]
}
]
}
}
};
});

View File

@ -0,0 +1,61 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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 () {
/**
* A CouchDocument describes domain object model in a format
* which is easily read-written to CouchDB. This includes
* Couch's _id and _rev fields, as well as a separate
* metadata field which contains a subset of information found
* in the model itself (to support search optimization with
* CouchDB views.)
* @memberof platform/persistence/couch
* @constructor
* @param {string} id the id under which to store this mode
* @param {object} model the model to store
* @param {string} rev the revision to include (or undefined,
* if no revision should be noted for couch)
* @param {boolean} whether or not to mark this document as
* deleted (see CouchDB docs for _deleted)
*/
function CouchDocument(id, model, rev, markDeleted) {
return {
"_id": id,
"_rev": rev,
"_deleted": markDeleted,
"metadata": {
"category": "domain object",
"type": model.type,
"owner": "admin",
"name": model.name,
"created": Date.now()
},
"model": model
};
}
return CouchDocument;
}
);

View File

@ -0,0 +1,119 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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 () {
// Set of connection states; changing among these states will be
// reflected in the indicator's appearance.
// CONNECTED: Everything nominal, expect to be able to read/write.
// DISCONNECTED: HTTP failed; maybe misconfigured, disconnected.
// SEMICONNECTED: Connected to the database, but it reported an error.
// PENDING: Still trying to connect, and haven't failed yet.
var CONNECTED = {
text: "Connected",
glyphClass: "ok",
statusClass: "s-status-on",
description: "Connected to the domain object database."
},
DISCONNECTED = {
text: "Disconnected",
glyphClass: "err",
statusClass: "s-status-caution",
description: "Unable to connect to the domain object database."
},
SEMICONNECTED = {
text: "Unavailable",
glyphClass: "caution",
statusClass: "s-status-caution",
description: "Database does not exist or is unavailable."
},
PENDING = {
text: "Checking connection...",
statusClass: "s-status-caution"
};
/**
* Indicator for the current CouchDB connection. Polls CouchDB
* at a regular interval (defined by bundle constants) to ensure
* that the database is available.
* @constructor
* @memberof platform/persistence/couch
* @implements {Indicator}
* @param $http Angular's $http service
* @param $interval Angular's $interval service
* @param {string} path the URL to poll to check for couch availability
* @param {number} interval the interval, in milliseconds, to poll at
*/
function CouchIndicator($http, $interval, path, interval) {
var self = this;
// Track the current connection state
this.state = PENDING;
this.$http = $http;
this.$interval = $interval;
this.path = path;
this.interval = interval;
// Callback if the HTTP request to Couch fails
function handleError() {
self.state = DISCONNECTED;
}
// Callback if the HTTP request succeeds. CouchDB may
// report an error, so check for that.
function handleResponse(response) {
var data = response.data;
self.state = data.error ? SEMICONNECTED : CONNECTED;
}
// Try to connect to CouchDB, and update the indicator.
function updateIndicator() {
$http.get(path).then(handleResponse, handleError);
}
// Update the indicator initially, and start polling.
updateIndicator();
$interval(updateIndicator, interval);
}
CouchIndicator.prototype.getCssClass = function () {
return "c-indicator--clickable icon-suitcase " + this.state.statusClass;
};
CouchIndicator.prototype.getGlyphClass = function () {
return this.state.glyphClass;
};
CouchIndicator.prototype.getText = function () {
return this.state.text;
};
CouchIndicator.prototype.getDescription = function () {
return this.state.description;
};
return CouchIndicator;
}
);

View File

@ -0,0 +1,145 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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.
*****************************************************************************/
/**
* This bundle implements a persistence service which uses CouchDB to
* store documents.
* @namespace platform/persistence/cache
*/
define(
["./CouchDocument"],
function (CouchDocument) {
// JSLint doesn't like dangling _'s, but CouchDB uses these, so
// hide this behind variables.
var REV = "_rev",
ID = "_id";
/**
* The CouchPersistenceProvider reads and writes JSON documents
* (more specifically, domain object models) to/from a CouchDB
* instance.
* @memberof platform/persistence/couch
* @constructor
* @implements {PersistenceService}
* @param $http Angular's $http service
* @param $interval Angular's $interval service
* @param {string} space the name of the persistence space being served
* @param {string} path the path to the CouchDB instance
*/
function CouchPersistenceProvider($http, $q, space, path) {
this.spaces = [space];
this.revs = {};
this.$q = $q;
this.$http = $http;
this.path = path;
}
// Pull out a list of document IDs from CouchDB's
// _all_docs response
function getIdsFromAllDocs(allDocs) {
return allDocs.rows.map(function (r) {
return r.id;
});
}
// Check the response to a create/update/delete request;
// track the rev if it's valid, otherwise return false to
// indicate that the request failed.
CouchPersistenceProvider.prototype.checkResponse = function (response) {
if (response && response.ok) {
this.revs[response.id] = response.rev;
return response.ok;
} else {
return false;
}
};
// Get a domain object model out of CouchDB's response
CouchPersistenceProvider.prototype.getModel = function (response) {
if (response && response.model) {
this.revs[response[ID]] = response[REV];
return response.model;
} else {
return undefined;
}
};
// Issue a request using $http; get back the plain JS object
// from the expected JSON response
CouchPersistenceProvider.prototype.request = function (subpath, method, value) {
return this.$http({
method: method,
url: this.path + '/' + subpath,
data: value
}).then(function (response) {
return response.data;
}, function () {
return undefined;
});
};
// Shorthand methods for GET/PUT methods
CouchPersistenceProvider.prototype.get = function (subpath) {
return this.request(subpath, "GET");
};
CouchPersistenceProvider.prototype.put = function (subpath, value) {
return this.request(subpath, "PUT", value);
};
CouchPersistenceProvider.prototype.listSpaces = function () {
return this.$q.when(this.spaces);
};
CouchPersistenceProvider.prototype.listObjects = function () {
return this.get("_all_docs").then(getIdsFromAllDocs.bind(this));
};
CouchPersistenceProvider.prototype.createObject = function (space, key, value) {
return this.put(key, new CouchDocument(key, value))
.then(this.checkResponse.bind(this));
};
CouchPersistenceProvider.prototype.readObject = function (space, key) {
return this.get(key).then(this.getModel.bind(this));
};
CouchPersistenceProvider.prototype.updateObject = function (space, key, value) {
var rev = this.revs[key];
return this.put(key, new CouchDocument(key, value, rev))
.then(this.checkResponse.bind(this));
};
CouchPersistenceProvider.prototype.deleteObject = function (space, key, value) {
var rev = this.revs[key];
return this.put(key, new CouchDocument(key, value, rev, true))
.then(this.checkResponse.bind(this));
};
return CouchPersistenceProvider;
}
);

View File

@ -0,0 +1,63 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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.
*****************************************************************************/
/**
* DomainObjectProviderSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../src/CouchDocument"],
function (CouchDocument) {
// JSLint doesn't like dangling _'s, but CouchDB uses these, so
// hide this behind variables.
var REV = "_rev",
ID = "_id",
DELETED = "_deleted";
describe("A couch document", function () {
it("includes an id", function () {
expect(new CouchDocument("testId", {})[ID])
.toEqual("testId");
});
it("includes a rev only when one is provided", function () {
expect(new CouchDocument("testId", {})[REV])
.not.toBeDefined();
expect(new CouchDocument("testId", {}, "testRev")[REV])
.toEqual("testRev");
});
it("includes the provided model", function () {
var model = { someKey: "some value" };
expect(new CouchDocument("testId", model).model)
.toEqual(model);
});
it("marks documents as deleted only on request", function () {
expect(new CouchDocument("testId", {}, "testRev")[DELETED])
.not.toBeDefined();
expect(new CouchDocument("testId", {}, "testRev", true)[DELETED])
.toBe(true);
});
});
}
);

View File

@ -0,0 +1,129 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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(
["../src/CouchIndicator"],
function (CouchIndicator) {
xdescribe("The CouchDB status indicator", function () {
var mockHttp,
mockInterval,
testPath,
testInterval,
mockPromise,
indicator;
beforeEach(function () {
mockHttp = jasmine.createSpyObj("$http", ["get"]);
mockInterval = jasmine.createSpy("$interval");
mockPromise = jasmine.createSpyObj("promise", ["then"]);
testPath = "/test/path";
testInterval = 12321; // Some number
mockHttp.get.and.returnValue(mockPromise);
indicator = new CouchIndicator(
mockHttp,
mockInterval,
testPath,
testInterval
);
});
it("polls for changes", function () {
expect(mockInterval).toHaveBeenCalledWith(
jasmine.any(Function),
testInterval
);
});
it("has a database icon", function () {
expect(indicator.getCssClass()).toEqual("icon-database s-status-caution");
});
it("consults the database at the configured path", function () {
expect(mockHttp.get).toHaveBeenCalledWith(testPath);
});
it("changes when the database connection is nominal", function () {
var initialText = indicator.getText(),
initialDescrption = indicator.getDescription(),
initialGlyphClass = indicator.getGlyphClass();
// Nominal just means getting back an object, without
// an error field.
mockPromise.then.calls.mostRecent().args[0]({ data: {} });
// Verify that these values changed;
// don't test for specific text.
expect(indicator.getText()).not.toEqual(initialText);
expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass);
expect(indicator.getDescription()).not.toEqual(initialDescrption);
// Do check for specific class
expect(indicator.getGlyphClass()).toEqual("ok");
});
it("changes when the server reports an error", function () {
var initialText = indicator.getText(),
initialDescrption = indicator.getDescription(),
initialGlyphClass = indicator.getGlyphClass();
// Nominal just means getting back an object, with
// an error field.
mockPromise.then.calls.mostRecent().args[0](
{ data: { error: "Uh oh." } }
);
// Verify that these values changed;
// don't test for specific text.
expect(indicator.getText()).not.toEqual(initialText);
expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass);
expect(indicator.getDescription()).not.toEqual(initialDescrption);
// Do check for specific class
expect(indicator.getGlyphClass()).toEqual("caution");
});
it("changes when the server cannot be reached", function () {
var initialText = indicator.getText(),
initialDescrption = indicator.getDescription(),
initialGlyphClass = indicator.getGlyphClass();
// Nominal just means getting back an object, without
// an error field.
mockPromise.then.calls.mostRecent().args[1]({ data: {} });
// Verify that these values changed;
// don't test for specific text.
expect(indicator.getText()).not.toEqual(initialText);
expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass);
expect(indicator.getDescription()).not.toEqual(initialDescrption);
// Do check for specific class
expect(indicator.getGlyphClass()).toEqual("err");
});
});
}
);

View File

@ -0,0 +1,223 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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.
*****************************************************************************/
/**
* DomainObjectProviderSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../src/CouchPersistenceProvider"],
function (CouchPersistenceProvider) {
describe("The couch persistence provider", function () {
var mockHttp,
mockQ,
testSpace = "testSpace",
testPath = "/test/db",
capture,
provider;
function mockPromise(value) {
return {
then: function (callback) {
return mockPromise(callback(value));
}
};
}
beforeEach(function () {
mockHttp = jasmine.createSpy("$http");
mockQ = jasmine.createSpyObj("$q", ["when"]);
mockQ.when.and.callFake(mockPromise);
// Capture promise results
capture = jasmine.createSpy("capture");
provider = new CouchPersistenceProvider(
mockHttp,
mockQ,
testSpace,
testPath
);
});
it("reports available spaces", function () {
provider.listSpaces().then(capture);
expect(capture).toHaveBeenCalledWith([testSpace]);
});
// General pattern of tests below is to simulate CouchDB's
// response, verify that request looks like what CouchDB
// would expect, and finally verify that CouchPersistenceProvider's
// return values match what is expected.
it("lists all available documents", function () {
mockHttp.and.returnValue(mockPromise({
data: { rows: [{ id: "a" }, { id: "b" }, { id: "c" }] }
}));
provider.listObjects().then(capture);
expect(mockHttp).toHaveBeenCalledWith({
url: "/test/db/_all_docs", // couch document listing
method: "GET",
data: undefined
});
expect(capture).toHaveBeenCalledWith(["a", "b", "c"]);
});
it("allows object creation", function () {
var model = { someKey: "some value" };
mockHttp.and.returnValue(mockPromise({
data: {
"_id": "abc",
"_rev": "xyz",
"ok": true
}
}));
provider.createObject("testSpace", "abc", model).then(capture);
expect(mockHttp).toHaveBeenCalledWith({
url: "/test/db/abc",
method: "PUT",
data: {
"_id": "abc",
"_rev": undefined,
"_deleted": undefined,
metadata: jasmine.any(Object),
model: model
}
});
expect(capture).toHaveBeenCalledWith(true);
});
it("allows object models to be read back", function () {
var model = { someKey: "some value" };
mockHttp.and.returnValue(mockPromise({
data: {
"_id": "abc",
"_rev": "xyz",
"model": model
}
}));
provider.readObject("testSpace", "abc").then(capture);
expect(mockHttp).toHaveBeenCalledWith({
url: "/test/db/abc",
method: "GET",
data: undefined
});
expect(capture).toHaveBeenCalledWith(model);
});
it("allows object update", function () {
var model = { someKey: "some value" };
// First do a read to populate rev tags...
mockHttp.and.returnValue(mockPromise({
data: {
"_id": "abc",
"_rev": "xyz",
"model": {}
}
}));
provider.readObject("testSpace", "abc");
// Now perform an update
mockHttp.and.returnValue(mockPromise({
data: {
"_id": "abc",
"_rev": "uvw",
"ok": true
}
}));
provider.updateObject("testSpace", "abc", model).then(capture);
expect(mockHttp).toHaveBeenCalledWith({
url: "/test/db/abc",
method: "PUT",
data: {
"_id": "abc",
"_rev": "xyz",
"_deleted": undefined,
metadata: jasmine.any(Object),
model: model
}
});
expect(capture).toHaveBeenCalledWith(true);
});
it("allows object deletion", function () {
// First do a read to populate rev tags...
mockHttp.and.returnValue(mockPromise({
data: {
"_id": "abc",
"_rev": "xyz",
"model": {}
}
}));
provider.readObject("testSpace", "abc");
// Now perform an update
mockHttp.and.returnValue(mockPromise({
data: {
"_id": "abc",
"_rev": "uvw",
"ok": true
}
}));
provider.deleteObject("testSpace", "abc", {}).then(capture);
expect(mockHttp).toHaveBeenCalledWith({
url: "/test/db/abc",
method: "PUT",
data: {
"_id": "abc",
"_rev": "xyz",
"_deleted": true,
metadata: jasmine.any(Object),
model: {}
}
});
expect(capture).toHaveBeenCalledWith(true);
});
it("reports failure to create objects", function () {
var model = { someKey: "some value" };
mockHttp.and.returnValue(mockPromise({
data: {
"_id": "abc",
"_rev": "xyz",
"ok": false
}
}));
provider.createObject("testSpace", "abc", model).then(capture);
expect(capture).toHaveBeenCalledWith(false);
});
it("returns undefined when objects are not found", function () {
// Act like a 404
mockHttp.and.returnValue({
then: function (success, fail) {
return mockPromise(fail());
}
});
provider.readObject("testSpace", "abc").then(capture);
expect(capture).toHaveBeenCalledWith(undefined);
});
});
}
);

View File

@ -252,7 +252,7 @@ define([
this.status = new api.StatusAPI(this); this.status = new api.StatusAPI(this);
this.router = new ApplicationRouter(this); this.router = new ApplicationRouter();
this.branding = BrandingAPI.default; this.branding = BrandingAPI.default;
@ -284,6 +284,7 @@ define([
this.install(this.plugins.ViewDatumAction()); this.install(this.plugins.ViewDatumAction());
this.install(this.plugins.ObjectInterceptors()); this.install(this.plugins.ObjectInterceptors());
this.install(this.plugins.NonEditableFolder()); this.install(this.plugins.NonEditableFolder());
this.install(this.plugins.Devices());
} }
MCT.prototype = Object.create(EventEmitter.prototype); MCT.prototype = Object.create(EventEmitter.prototype);
@ -403,39 +404,24 @@ define([
this.router.setPath('/browse/'); this.router.setPath('/browse/');
}); });
/** if (!isHeadlessMode) {
* Fired by [MCT]{@link module:openmct.MCT} when the application const appLayout = new Vue({
* is started. components: {
* @event start 'Layout': Layout.default
* @memberof module:openmct.MCT~ },
*/ provide: {
const startPromise = new Main(); openmct: this
startPromise.run(this) },
.then(function (angular) { template: '<Layout ref="layout"></Layout>'
this.$angular = angular; });
// OpenMCT Object provider doesn't operate properly unless domElement.appendChild(appLayout.$mount().$el);
// something has depended upon objectService. Cool, right?
this.$injector.get('objectService');
if (!isHeadlessMode) { this.layout = appLayout.$refs.layout;
const appLayout = new Vue({ Browse(this);
components: { }
'Layout': Layout.default
},
provide: {
openmct: this
},
template: '<Layout ref="layout"></Layout>'
});
domElement.appendChild(appLayout.$mount().$el);
this.layout = appLayout.$refs.layout; this.router.start();
Browse(this); this.emit('start');
}
this.router.start();
this.emit('start');
}.bind(this));
}; };
MCT.prototype.startHeadless = function () { MCT.prototype.startHeadless = function () {

View File

@ -36,8 +36,7 @@ define([
'./views/installLegacyViews', './views/installLegacyViews',
'./policies/LegacyCompositionPolicyAdapter', './policies/LegacyCompositionPolicyAdapter',
'./actions/LegacyActionAdapter', './actions/LegacyActionAdapter',
'./services/LegacyPersistenceAdapter', './services/LegacyPersistenceAdapter'
'./services/ExportImageService'
], function ( ], function (
ActionDialogDecorator, ActionDialogDecorator,
AdapterCapability, AdapterCapability,
@ -54,8 +53,7 @@ define([
installLegacyViews, installLegacyViews,
legacyCompositionPolicyAdapter, legacyCompositionPolicyAdapter,
LegacyActionAdapter, LegacyActionAdapter,
LegacyPersistenceAdapter, LegacyPersistenceAdapter
ExportImageService
) { ) {
return { return {
name: 'src/adapter', name: 'src/adapter',
@ -84,13 +82,6 @@ define([
"identifierService", "identifierService",
"cacheService" "cacheService"
] ]
},
{
"key": "exportImageService",
"implementation": ExportImageService,
"depends": [
"dialogService"
]
} }
], ],
components: [ components: [

View File

@ -161,22 +161,6 @@ define([
evaluate: function (datum, property) { evaluate: function (datum, property) {
return limitEvaluator.evaluate(datum, property && property.key); return limitEvaluator.evaluate(datum, property && property.key);
} }
};
};
LegacyTelemetryProvider.prototype.getLimits = function (domainObject) {
const oldObject = this.instantiate(
utils.toOldFormat(domainObject),
utils.makeKeyString(domainObject.identifier)
);
const limitEvaluator = oldObject.getCapability("limit");
return {
limits: function () {
return limitEvaluator.limits();
}
}; };
}; };

View File

@ -52,7 +52,7 @@ define([
oldStyleObject.getCapability('mutation').mutate(function () { oldStyleObject.getCapability('mutation').mutate(function () {
return utils.toOldFormat(newStyleObject); return utils.toOldFormat(newStyleObject);
}, newStyleObject.modified); });
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation); removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
}.bind(this); }.bind(this);

View File

@ -119,8 +119,7 @@ describe('The ActionCollection', () => {
afterEach(() => { afterEach(() => {
actionCollection.destroy(); actionCollection.destroy();
resetApplicationState(openmct);
return resetApplicationState(openmct);
}); });
describe("disable method invoked with action keys", () => { describe("disable method invoked with action keys", () => {

View File

@ -99,7 +99,7 @@ describe('The Actions API', () => {
}); });
afterEach(() => { afterEach(() => {
return resetApplicationState(openmct); resetApplicationState(openmct);
}); });
describe("register method", () => { describe("register method", () => {

View File

@ -215,12 +215,12 @@ define([
* @memberof {module:openmct.CompositionCollection#} * @memberof {module:openmct.CompositionCollection#}
* @name load * @name load
*/ */
CompositionCollection.prototype.load = function (abortSignal) { CompositionCollection.prototype.load = function () {
this.cleanUpMutables(); this.cleanUpMutables();
return this.provider.load(this.domainObject) return this.provider.load(this.domainObject)
.then(function (children) { .then(function (children) {
return Promise.all(children.map((c) => this.publicAPI.objects.get(c, abortSignal))); return Promise.all(children.map((c) => this.publicAPI.objects.get(c)));
}.bind(this)) }.bind(this))
.then(function (childObjects) { .then(function (childObjects) {
childObjects.forEach(c => this.add(c, true)); childObjects.forEach(c => this.add(c, true));

View File

@ -76,7 +76,7 @@ describe ('The Menu API', () => {
}); });
afterEach(() => { afterEach(() => {
return resetApplicationState(openmct); resetApplicationState(openmct);
}); });
describe("showMenu method", () => { describe("showMenu method", () => {

View File

@ -76,10 +76,7 @@ class MutableDomainObject {
} }
$set(path, value) { $set(path, value) {
_.set(this, path, value); _.set(this, path, value);
_.set(this, 'modified', Date.now());
if (path !== 'persisted' && path !== 'modified') {
_.set(this, 'modified', Date.now());
}
//Emit secret synchronization event first, so that all objects are in sync before subsequent events fired. //Emit secret synchronization event first, so that all objects are in sync before subsequent events fired.
this._globalEventEmitter.emit(qualifiedEventName(this, '$_synchronize_model'), this); this._globalEventEmitter.emit(qualifiedEventName(this, '$_synchronize_model'), this);
@ -115,11 +112,9 @@ class MutableDomainObject {
return () => this._instanceEventEmitter.off(event, callback); return () => this._instanceEventEmitter.off(event, callback);
} }
$destroy() { $destroy() {
while (this._observers.length > 0) { this._observers.forEach(observer => observer());
const observer = this._observers.pop(); delete this._globalEventEmitter;
observer(); delete this._observers;
}
this._instanceEventEmitter.emit('$_destroy'); this._instanceEventEmitter.emit('$_destroy');
} }

View File

@ -38,9 +38,6 @@ function ObjectAPI(typeRegistry, openmct) {
this.eventEmitter = new EventEmitter(); this.eventEmitter = new EventEmitter();
this.providers = {}; this.providers = {};
this.rootRegistry = new RootRegistry(); this.rootRegistry = new RootRegistry();
this.injectIdentifierService = function () {
this.identifierService = openmct.$injector.get("identifierService");
};
this.rootProvider = new RootObjectProvider(this.rootRegistry); this.rootProvider = new RootObjectProvider(this.rootRegistry);
this.cache = {}; this.cache = {};
@ -55,33 +52,16 @@ ObjectAPI.prototype.supersecretSetFallbackProvider = function (p) {
this.fallbackProvider = p; this.fallbackProvider = p;
}; };
/**
* @private
*/
ObjectAPI.prototype.getIdentifierService = function () {
// Lazily acquire identifier service
if (!this.identifierService) {
this.injectIdentifierService();
}
return this.identifierService;
};
/** /**
* Retrieve the provider for a given identifier. * Retrieve the provider for a given identifier.
* @private * @private
*/ */
ObjectAPI.prototype.getProvider = function (identifier) { ObjectAPI.prototype.getProvider = function (identifier) {
//handles the '' vs 'mct' namespace issue
const keyString = utils.makeKeyString(identifier);
const identifierService = this.getIdentifierService();
const namespace = identifierService.parse(keyString).getSpace();
if (identifier.key === 'ROOT') { if (identifier.key === 'ROOT') {
return this.rootProvider; return this.rootProvider;
} }
return this.providers[namespace] || this.fallbackProvider; return this.providers[identifier.namespace] || this.fallbackProvider;
}; };
/** /**
@ -161,7 +141,6 @@ ObjectAPI.prototype.addProvider = function (namespace, provider) {
ObjectAPI.prototype.get = function (identifier, abortSignal) { ObjectAPI.prototype.get = function (identifier, abortSignal) {
let keystring = this.makeKeyString(identifier); let keystring = this.makeKeyString(identifier);
if (this.cache[keystring] !== undefined) { if (this.cache[keystring] !== undefined) {
return this.cache[keystring]; return this.cache[keystring];
} }
@ -177,16 +156,15 @@ ObjectAPI.prototype.get = function (identifier, abortSignal) {
throw new Error('Provider does not support get!'); throw new Error('Provider does not support get!');
} }
let objectPromise = provider.get(identifier, abortSignal).then(result => { let objectPromise = provider.get(identifier, abortSignal);
this.cache[keystring] = objectPromise;
return objectPromise.then(result => {
delete this.cache[keystring]; delete this.cache[keystring];
result = this.applyGetInterceptors(identifier, result); result = this.applyGetInterceptors(identifier, result);
return result; return result;
}); });
this.cache[keystring] = objectPromise;
return objectPromise;
}; };
/** /**
@ -486,12 +464,6 @@ ObjectAPI.prototype.getOriginalPath = function (identifier, path = []) {
}); });
}; };
ObjectAPI.prototype.isObjectPathToALink = function (domainObject, objectPath) {
return objectPath !== undefined
&& objectPath.length > 1
&& domainObject.location !== this.makeKeyString(objectPath[1].identifier);
};
/** /**
* Uniquely identifies a domain object. * Uniquely identifies a domain object.
* *
@ -528,10 +500,8 @@ ObjectAPI.prototype.isObjectPathToALink = function (domainObject, objectPath) {
*/ */
function hasAlreadyBeenPersisted(domainObject) { function hasAlreadyBeenPersisted(domainObject) {
const result = domainObject.persisted !== undefined return domainObject.persisted !== undefined
&& domainObject.persisted >= domainObject.modified; && domainObject.persisted === domainObject.modified;
return result;
} }
export default ObjectAPI; export default ObjectAPI;

View File

@ -22,7 +22,7 @@ describe("The Status API", () => {
}); });
afterEach(() => { afterEach(() => {
return resetApplicationState(openmct); resetApplicationState(openmct);
}); });
describe("set function", () => { describe("set function", () => {

View File

@ -116,11 +116,9 @@ define([
* @private * @private
*/ */
DefaultMetadataProvider.prototype.typeHasTelemetry = function (domainObject) { DefaultMetadataProvider.prototype.typeHasTelemetry = function (domainObject) {
if (!this.typeService) { const type = this.openmct.types.get(domainObject.type);
this.typeService = this.openmct.$injector.get('typeService');
}
return Boolean(this.typeService.getType(domainObject.type).typeDef.telemetry); return Boolean(type.definition.telemetry);
}; };
return DefaultMetadataProvider; return DefaultMetadataProvider;

View File

@ -142,6 +142,8 @@ define([
this.metadataCache = new WeakMap(); this.metadataCache = new WeakMap();
this.formatMapCache = new WeakMap(); this.formatMapCache = new WeakMap();
this.valueFormatterCache = new WeakMap(); this.valueFormatterCache = new WeakMap();
this.formatters = new Map();
} }
/** /**
@ -413,17 +415,6 @@ define([
return _.sortBy(options, sortKeys); return _.sortBy(options, sortKeys);
}; };
/**
* @private
*/
TelemetryAPI.prototype.getFormatService = function () {
if (!this.formatService) {
this.formatService = this.openmct.$injector.get('formatService');
}
return this.formatService;
};
/** /**
* Get a value formatter for a given valueMetadata. * Get a value formatter for a given valueMetadata.
* *
@ -433,7 +424,7 @@ define([
if (!this.valueFormatterCache.has(valueMetadata)) { if (!this.valueFormatterCache.has(valueMetadata)) {
this.valueFormatterCache.set( this.valueFormatterCache.set(
valueMetadata, valueMetadata,
new TelemetryValueFormatter(valueMetadata, this.getFormatService()) new TelemetryValueFormatter(valueMetadata, this.formatters)
); );
} }
@ -447,9 +438,11 @@ define([
* @returns {Format} * @returns {Format}
*/ */
TelemetryAPI.prototype.getFormatter = function (key) { TelemetryAPI.prototype.getFormatter = function (key) {
const formatMap = this.getFormatService().formatMap; if (this.formatters.has(key)) {
return this.formatters.get(key);
return formatMap[key]; } else {
throw new Error(`Unknown type ${key}`);
}
}; };
/** /**
@ -476,12 +469,7 @@ define([
* @param {Format} format the * @param {Format} format the
*/ */
TelemetryAPI.prototype.addFormat = function (format) { TelemetryAPI.prototype.addFormat = function (format) {
this.openmct.legacyExtension('formats', { this.formatters.set(format.key, format);
key: format.key,
implementation: function () {
return format;
}
});
}; };
/** /**
@ -504,26 +492,6 @@ define([
return this.getLimitEvaluator(domainObject); return this.getLimitEvaluator(domainObject);
}; };
/**
* Get a limits for this domain object.
* Limits help you display limits and alarms of
* telemetry for display purposes without having to interact directly
* with the Limit API.
*
* This method is optional.
* If a provider does not implement this method, it is presumed
* that no limits are defined for this domain object's telemetry.
*
* @param {module:openmct.DomainObject} domainObject the domain
* object for which to get limits
* @returns {module:openmct.TelemetryAPI~LimitEvaluator}
* @method limits
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
*/
TelemetryAPI.prototype.limitDefinition = function (domainObject) {
return this.getLimits(domainObject);
};
/** /**
* Get a limit evaluator for this domain object. * Get a limit evaluator for this domain object.
* Limit Evaluators help you evaluate limit and alarm status of individual * Limit Evaluators help you evaluate limit and alarm status of individual
@ -551,42 +519,5 @@ define([
return provider.getLimitEvaluator(domainObject); return provider.getLimitEvaluator(domainObject);
}; };
/**
* Get a limit definitions for this domain object.
* Limit Definitions help you indicate limits and alarms of
* telemetry for display purposes without having to interact directly
* with the Limit API.
*
* This method is optional.
* If a provider does not implement this method, it is presumed
* that no limits are defined for this domain object's telemetry.
*
* @param {module:openmct.DomainObject} domainObject the domain
* object for which to display limits
* @returns {module:openmct.TelemetryAPI~LimitEvaluator}
* @method limits returns a limits object of
* type {
* level1: {
* low: { key1: value1, key2: value2 },
* high: { key1: value1, key2: value2 }
* },
* level2: {
* low: { key1: value1, key2: value2 },
* high: { key1: value1, key2: value2 }
* }
* }
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
*/
TelemetryAPI.prototype.getLimits = function (domainObject) {
const provider = this.findLimitEvaluator(domainObject);
if (!provider) {
return {
limits: function () {}
};
}
return provider.getLimits(domainObject);
};
return TelemetryAPI; return TelemetryAPI;
}); });

View File

@ -28,8 +28,7 @@ define([
printj printj
) { ) {
// TODO: needs reference to formatService; function TelemetryValueFormatter(valueMetadata, formatters) {
function TelemetryValueFormatter(valueMetadata, formatService) {
const numberFormatter = { const numberFormatter = {
parse: function (x) { parse: function (x) {
return Number(x); return Number(x);
@ -43,13 +42,7 @@ define([
}; };
this.valueMetadata = valueMetadata; this.valueMetadata = valueMetadata;
try { this.formatter = formatters.get(valueMetadata.format) || numberFormatter;
this.formatter = formatService
.getFormat(valueMetadata.format, valueMetadata);
} catch (e) {
// TODO: Better formatting
this.formatter = numberFormatter;
}
if (valueMetadata.format === 'enum') { if (valueMetadata.format === 'enum') {
this.formatter = {}; this.formatter = {};

View File

@ -90,6 +90,7 @@ define([
'../platform/framework/src/load/Bundle', '../platform/framework/src/load/Bundle',
'../platform/identity/bundle', '../platform/identity/bundle',
'../platform/persistence/aggregator/bundle', '../platform/persistence/aggregator/bundle',
'../platform/persistence/couch/bundle',
'../platform/persistence/elastic/bundle', '../platform/persistence/elastic/bundle',
'../platform/persistence/local/bundle', '../platform/persistence/local/bundle',
'../platform/persistence/queue/bundle', '../platform/persistence/queue/bundle',

View File

@ -45,14 +45,10 @@ export default function LADTableSetViewProvider(openmct) {
}, },
provide: { provide: {
openmct, openmct,
domainObject,
objectPath objectPath
}, },
data() { template: '<lad-table-set></lad-table-set>'
return {
domainObject
};
},
template: '<lad-table-set :domain-object="domainObject"></lad-table-set>'
}); });
}, },
destroy: function (element) { destroy: function (element) {

View File

@ -56,7 +56,7 @@ export default {
type: Object, type: Object,
required: true required: true
}, },
pathToTable: { objectPath: {
type: Array, type: Array,
required: true required: true
}, },
@ -66,19 +66,20 @@ export default {
} }
}, },
data() { data() {
let currentObjectPath = this.objectPath.slice();
currentObjectPath.unshift(this.domainObject);
return { return {
timestamp: undefined, timestamp: undefined,
value: '---', value: '---',
valueClass: '', valueClass: '',
currentObjectPath,
unit: '' unit: ''
}; };
}, },
computed: { computed: {
formattedTimestamp() { formattedTimestamp() {
return this.timestamp !== undefined ? this.getFormattedTimestamp(this.timestamp) : '---'; return this.timestamp !== undefined ? this.getFormattedTimestamp(this.timestamp) : '---';
},
objectPath() {
return [this.domainObject, ...this.pathToTable];
} }
}, },
mounted() { mounted() {
@ -181,7 +182,7 @@ export default {
}; };
}, },
showContextMenu(event) { showContextMenu(event) {
let actionCollection = this.openmct.actions.get(this.objectPath, this.getView()); let actionCollection = this.openmct.actions.get(this.currentObjectPath, this.getView());
let allActions = actionCollection.getActionsObject(); let allActions = actionCollection.getActionsObject();
let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]); let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);

View File

@ -33,10 +33,10 @@
</thead> </thead>
<tbody> <tbody>
<lad-row <lad-row
v-for="ladRow in items" v-for="item in items"
:key="ladRow.key" :key="item.key"
:domain-object="ladRow.domainObject" :domain-object="item.domainObject"
:path-to-table="objectPath" :object-path="objectPath"
:has-units="hasUnits" :has-units="hasUnits"
/> />
</tbody> </tbody>

View File

@ -43,10 +43,9 @@
</td> </td>
</tr> </tr>
<lad-row <lad-row
v-for="ladRow in ladTelemetryObjects[ladTable.key]" v-for="telemetryObject in ladTelemetryObjects[ladTable.key]"
:key="ladRow.key" :key="telemetryObject.key"
:domain-object="ladRow.domainObject" :domain-object="telemetryObject.domainObject"
:path-to-table="ladTable.objectPath"
:has-units="hasUnits" :has-units="hasUnits"
/> />
</template> </template>
@ -61,13 +60,7 @@ export default {
components: { components: {
LadRow LadRow
}, },
inject: ['openmct', 'objectPath'], inject: ['openmct', 'domainObject'],
props: {
domainObject: {
type: Object,
required: true
}
},
data() { data() {
return { return {
ladTableObjects: [], ladTableObjects: [],
@ -113,7 +106,6 @@ export default {
let ladTable = {}; let ladTable = {};
ladTable.domainObject = domainObject; ladTable.domainObject = domainObject;
ladTable.key = this.openmct.objects.makeKeyString(domainObject.identifier); ladTable.key = this.openmct.objects.makeKeyString(domainObject.identifier);
ladTable.objectPath = [domainObject, ...this.objectPath];
this.$set(this.ladTelemetryObjects, ladTable.key, []); this.$set(this.ladTelemetryObjects, ladTable.key, []);
this.ladTableObjects.push(ladTable); this.ladTableObjects.push(ladTable);

View File

@ -292,11 +292,6 @@ describe("The LAD Table Set", () => {
}); });
afterEach(() => { afterEach(() => {
openmct.time.timeSystem('utc', {
start: 0,
end: 1
});
return resetApplicationState(openmct); return resetApplicationState(openmct);
}); });

View File

@ -19,6 +19,10 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import {
getAllSearchParams,
setAllSearchParams
} from 'utils/openmctLocation';
const TIME_EVENTS = ['timeSystem', 'clock', 'clockOffsets']; const TIME_EVENTS = ['timeSystem', 'clock', 'clockOffsets'];
const SEARCH_MODE = 'tc.mode'; const SEARCH_MODE = 'tc.mode';
@ -45,8 +49,9 @@ export default class URLTimeSettingsSynchronizer {
} }
initialize() { initialize() {
this.openmct.router.on('change:params', this.updateTimeSettings); this.updateTimeSettings();
window.addEventListener('hashchange', this.updateTimeSettings);
TIME_EVENTS.forEach(event => { TIME_EVENTS.forEach(event => {
this.openmct.time.on(event, this.setUrlFromTimeApi); this.openmct.time.on(event, this.setUrlFromTimeApi);
}); });
@ -54,8 +59,7 @@ export default class URLTimeSettingsSynchronizer {
} }
destroy() { destroy() {
this.openmct.router.off('change:params', this.updateTimeSettings); window.removeEventListener('hashchange', this.updateTimeSettings);
this.openmct.off('start', this.initialize); this.openmct.off('start', this.initialize);
this.openmct.off('destroy', this.destroy); this.openmct.off('destroy', this.destroy);
@ -66,18 +70,22 @@ export default class URLTimeSettingsSynchronizer {
} }
updateTimeSettings() { updateTimeSettings() {
let timeParameters = this.parseParametersFromUrl(); // Prevent from triggering self
if (!this.isUrlUpdateInProgress) {
let timeParameters = this.parseParametersFromUrl();
if (this.areTimeParametersValid(timeParameters)) { if (this.areTimeParametersValid(timeParameters)) {
this.setTimeApiFromUrl(timeParameters); this.setTimeApiFromUrl(timeParameters);
this.openmct.router.setLocationFromUrl(); } else {
this.setUrlFromTimeApi();
}
} else { } else {
this.setUrlFromTimeApi(); this.isUrlUpdateInProgress = false;
} }
} }
parseParametersFromUrl() { parseParametersFromUrl() {
let searchParams = this.openmct.router.getAllSearchParams(); let searchParams = getAllSearchParams();
let mode = searchParams.get(SEARCH_MODE); let mode = searchParams.get(SEARCH_MODE);
let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM); let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
@ -140,7 +148,7 @@ export default class URLTimeSettingsSynchronizer {
} }
setUrlFromTimeApi() { setUrlFromTimeApi() {
let searchParams = this.openmct.router.getAllSearchParams(); let searchParams = getAllSearchParams();
let clock = this.openmct.time.clock(); let clock = this.openmct.time.clock();
let bounds = this.openmct.time.bounds(); let bounds = this.openmct.time.bounds();
let clockOffsets = this.openmct.time.clockOffsets(); let clockOffsets = this.openmct.time.clockOffsets();
@ -168,7 +176,8 @@ export default class URLTimeSettingsSynchronizer {
} }
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key); searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
this.openmct.router.setAllSearchParams(searchParams); this.isUrlUpdateInProgress = true;
setAllSearchParams(searchParams);
} }
areTimeParametersValid(timeParameters) { areTimeParametersValid(timeParameters) {

View File

@ -25,118 +25,306 @@ import {
} from 'utils/testing'; } from 'utils/testing';
describe("The URLTimeSettingsSynchronizer", () => { describe("The URLTimeSettingsSynchronizer", () => {
let appHolder;
let openmct; let openmct;
let resolveFunction; let testClock;
let oldHash;
beforeEach((done) => { beforeEach((done) => {
openmct = createOpenMct(); openmct = createOpenMct();
openmct.install(openmct.plugins.MyItems());
openmct.install(openmct.plugins.LocalTimeSystem()); openmct.install(openmct.plugins.LocalTimeSystem());
openmct.install(openmct.plugins.UTCTimeSystem()); testClock = jasmine.createSpyObj("testClock", ["start", "stop", "tick", "currentValue", "on", "off"]);
testClock.key = "test-clock";
testClock.currentValue.and.returnValue(0);
openmct.time.addClock(testClock);
openmct.on('start', done); openmct.on('start', done);
openmct.startHeadless();
appHolder = document.createElement("div");
openmct.start(appHolder);
}); });
afterEach(() => { afterEach(() => resetApplicationState(openmct));
openmct.time.stopClock();
openmct.router.removeListener('change:hash', resolveFunction);
appHolder = undefined; describe("realtime mode", () => {
openmct = undefined; it("when the clock is set via the time API, it is immediately reflected in the URL", () => {
resolveFunction = undefined; //Test expected initial conditions
return resetApplicationState(openmct);
});
it("initial clock is set to fixed is reflected in URL", (done) => {
resolveFunction = () => {
oldHash = window.location.hash;
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true); expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
openmct.router.removeListener('change:hash', resolveFunction);
done();
};
openmct.router.on('change:hash', resolveFunction);
});
it("when the clock is set via the time API, it is reflected in the URL", (done) => {
let success;
resolveFunction = () => {
openmct.time.clock('local', { openmct.time.clock('local', {
start: -1000,
end: 100
});
expect(window.location.hash.includes('tc.mode=local')).toBe(true);
//Test that expected initial conditions are no longer true
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
});
it("when offsets are set via the time API, they are immediately reflected in the URL", () => {
//Test expected initial conditions
expect(window.location.hash.includes('tc.startDelta')).toBe(false);
expect(window.location.hash.includes('tc.endDelta')).toBe(false);
openmct.time.clock('local', {
start: -1000,
end: 100
});
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
openmct.time.clockOffsets({
start: -2000, start: -2000,
end: 200 end: 200
}); });
expect(window.location.hash.includes('tc.startDelta=2000')).toBe(true);
expect(window.location.hash.includes('tc.endDelta=200')).toBe(true);
const hasStartDelta = window.location.hash.includes('tc.startDelta=2000'); //Test that expected initial conditions are no longer true
const hasEndDelta = window.location.hash.includes('tc.endDelta=200'); expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
const hasLocalClock = window.location.hash.includes('tc.mode=local'); });
success = hasStartDelta && hasEndDelta && hasLocalClock; describe("when set in the url", () => {
if (success) { it("will change from fixed to realtime mode when the mode changes", () => {
expect(success).toBe(true); expectLocationToBeInFixedMode();
openmct.router.removeListener('change:hash', resolveFunction); return switchToRealtimeMode().then(() => {
done(); let clock = openmct.time.clock();
}
};
openmct.router.on('change:hash', resolveFunction); expect(clock).toBeDefined();
expect(clock.key).toBe('local');
});
});
it("the clock is correctly set in the API from the URL parameters", () => {
return switchToRealtimeMode().then(() => {
let resolveFunction;
return new Promise((resolve) => {
resolveFunction = resolve;
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
//detected in the API.
openmct.time.on('clock', resolveFunction);
let hash = window.location.hash;
hash = hash.replace('tc.mode=local', 'tc.mode=test-clock');
window.location.hash = hash;
}).then(() => {
let clock = openmct.time.clock();
expect(clock).toBeDefined();
expect(clock.key).toBe('test-clock');
openmct.time.off('clock', resolveFunction);
});
});
});
it("the clock offsets are correctly set in the API from the URL parameters", () => {
return switchToRealtimeMode().then(() => {
let resolveFunction;
return new Promise((resolve) => {
resolveFunction = resolve;
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
//detected in the API.
openmct.time.on('clockOffsets', resolveFunction);
let hash = window.location.hash;
hash = hash.replace('tc.startDelta=1000', 'tc.startDelta=2000');
hash = hash.replace('tc.endDelta=100', 'tc.endDelta=200');
window.location.hash = hash;
}).then(() => {
let clockOffsets = openmct.time.clockOffsets();
expect(clockOffsets).toBeDefined();
expect(clockOffsets.start).toBe(-2000);
expect(clockOffsets.end).toBe(200);
openmct.time.off('clockOffsets', resolveFunction);
});
});
});
it("the time system is correctly set in the API from the URL parameters", () => {
return switchToRealtimeMode().then(() => {
let resolveFunction;
return new Promise((resolve) => {
resolveFunction = resolve;
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
//detected in the API.
openmct.time.on('timeSystem', resolveFunction);
let hash = window.location.hash;
hash = hash.replace('tc.timeSystem=utc', 'tc.timeSystem=local');
window.location.hash = hash;
}).then(() => {
let timeSystem = openmct.time.timeSystem();
expect(timeSystem).toBeDefined();
expect(timeSystem.key).toBe('local');
openmct.time.off('timeSystem', resolveFunction);
});
});
});
});
});
describe("fixed timespan mode", () => {
beforeEach(() => {
openmct.time.stopClock();
openmct.time.timeSystem('utc', {
start: 0,
end: 1
});
});
it("when bounds are set via the time API, they are immediately reflected in the URL", () => {
//Test expected initial conditions
expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
openmct.time.bounds({
start: 10,
end: 20
});
expect(window.location.hash.includes('tc.startBound=10')).toBe(true);
expect(window.location.hash.includes('tc.endBound=20')).toBe(true);
//Test that expected initial conditions are no longer true
expect(window.location.hash.includes('tc.startBound=0')).toBe(false);
expect(window.location.hash.includes('tc.endBound=1')).toBe(false);
});
it("when time system is set via the time API, it is immediately reflected in the URL", () => {
//Test expected initial conditions
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(true);
openmct.time.timeSystem('local', {
start: 20,
end: 30
});
expect(window.location.hash.includes('tc.timeSystem=local')).toBe(true);
//Test that expected initial conditions are no longer true
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(false);
});
describe("when set in the url", () => {
it("time system changes are reflected in the API", () => {
let resolveFunction;
return new Promise((resolve) => {
let timeSystem = openmct.time.timeSystem();
resolveFunction = resolve;
expect(timeSystem.key).toBe('utc');
window.location.hash = window.location.hash.replace('tc.timeSystem=utc', 'tc.timeSystem=local');
openmct.time.on('timeSystem', resolveFunction);
}).then(() => {
let timeSystem = openmct.time.timeSystem();
expect(timeSystem.key).toBe('local');
openmct.time.off('timeSystem', resolveFunction);
});
});
it("mode can be changed from realtime to fixed", () => {
return switchToRealtimeMode().then(() => {
expectLocationToBeInRealtimeMode();
expect(openmct.time.clock()).toBeDefined();
}).then(switchToFixedMode).then(() => {
let clock = openmct.time.clock();
expect(clock).not.toBeDefined();
});
});
it("bounds are correctly set in the API from the URL parameters", () => {
let resolveFunction;
expectLocationToBeInFixedMode();
return new Promise((resolve) => {
resolveFunction = resolve;
openmct.time.on('bounds', resolveFunction);
let hash = window.location.hash;
hash = hash.replace('tc.startBound=0', 'tc.startBound=222')
.replace('tc.endBound=1', 'tc.endBound=333');
window.location.hash = hash;
}).then(() => {
let bounds = openmct.time.bounds();
expect(bounds).toBeDefined();
expect(bounds.start).toBe(222);
expect(bounds.end).toBe(333);
});
});
it("bounds are correctly set in the API from the URL parameters where only the end bound changes", () => {
let resolveFunction;
expectLocationToBeInFixedMode();
return new Promise((resolve) => {
resolveFunction = resolve;
openmct.time.on('bounds', resolveFunction);
let hash = window.location.hash;
hash = hash.replace('tc.endBound=1', 'tc.endBound=333');
window.location.hash = hash;
}).then(() => {
let bounds = openmct.time.bounds();
expect(bounds).toBeDefined();
expect(bounds.start).toBe(0);
expect(bounds.end).toBe(333);
});
});
});
}); });
it("when the clock mode is set to local, it is reflected in the URL", (done) => { function setRealtimeLocationParameters() {
let success; let hash = window.location.hash.toString()
.replace('tc.mode=fixed', 'tc.mode=local')
.replace('tc.startBound=0', 'tc.startDelta=1000')
.replace('tc.endBound=1', 'tc.endDelta=100');
resolveFunction = () => { window.location.hash = hash;
let hash = window.location.hash; }
hash = hash.replace('tc.mode=fixed', 'tc.mode=local');
window.location.hash = hash;
success = window.location.hash.includes('tc.mode=local'); function setFixedLocationParameters() {
if (success) { let hash = window.location.hash.toString()
expect(success).toBe(true); .replace('tc.mode=local', 'tc.mode=fixed')
done(); .replace('tc.timeSystem=utc', 'tc.timeSystem=local')
} .replace('tc.startDelta=1000', 'tc.startBound=50')
}; .replace('tc.endDelta=100', 'tc.endBound=60');
openmct.router.on('change:hash', resolveFunction); window.location.hash = hash;
}); }
it("when the clock mode is set to local, it is reflected in the URL", (done) => { function switchToRealtimeMode() {
let success; let resolveFunction;
resolveFunction = () => { return new Promise((resolve) => {
let hash = window.location.hash; resolveFunction = resolve;
openmct.time.on('clock', resolveFunction);
setRealtimeLocationParameters();
}).then(() => {
openmct.time.off('clock', resolveFunction);
});
}
hash = hash.replace('tc.mode=fixed', 'tc.mode=local'); function switchToFixedMode() {
window.location.hash = hash; let resolveFunction;
success = window.location.hash.includes('tc.mode=local');
if (success) {
expect(success).toBe(true);
done();
}
};
openmct.router.on('change:hash', resolveFunction); return new Promise((resolve) => {
}); resolveFunction = resolve;
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
//detected in the API.
openmct.time.on('clock', resolveFunction);
setFixedLocationParameters();
}).then(() => {
openmct.time.off('clock', resolveFunction);
});
}
it("reset hash", (done) => { function expectLocationToBeInRealtimeMode() {
let success; expect(window.location.hash.includes('tc.mode=local')).toBe(true);
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
}
window.location.hash = oldHash; function expectLocationToBeInFixedMode() {
resolveFunction = () => { expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
success = window.location.hash === oldHash; expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
if (success) { expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
expect(success).toBe(true); expect(window.location.hash.includes('tc.mode=local')).toBe(false);
done(); }
}
};
openmct.router.on('change:hash', resolveFunction);
});
}); });

View File

@ -82,9 +82,7 @@ export default class Condition extends EventEmitter {
if (this.isAnyOrAllTelemetry(criterion)) { if (this.isAnyOrAllTelemetry(criterion)) {
criterion.updateResult(datum, this.conditionManager.telemetryObjects); criterion.updateResult(datum, this.conditionManager.telemetryObjects);
} else { } else {
if (criterion.usesTelemetry(datum.id)) { criterion.updateResult(datum);
criterion.updateResult(datum);
}
} }
}); });
@ -104,7 +102,7 @@ export default class Condition extends EventEmitter {
isTelemetryUsed(id) { isTelemetryUsed(id) {
return this.criteria.some(criterion => { return this.criteria.some(criterion => {
return this.isAnyOrAllTelemetry(criterion) || criterion.usesTelemetry(id); return this.isAnyOrAllTelemetry(criterion) || criterion.telemetryObjectIdAsString === id;
}); });
} }
@ -272,11 +270,11 @@ export default class Condition extends EventEmitter {
} }
} }
requestLADConditionResult(options) { requestLADConditionResult() {
let latestTimestamp; let latestTimestamp;
let criteriaResults = {}; let criteriaResults = {};
const criteriaRequests = this.criteria const criteriaRequests = this.criteria
.map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects, options)); .map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects));
return Promise.all(criteriaRequests) return Promise.all(criteriaRequests)
.then(results => { .then(results => {

View File

@ -282,7 +282,7 @@ export default class ConditionManager extends EventEmitter {
return currentCondition; return currentCondition;
} }
requestLADConditionSetOutput(options) { requestLADConditionSetOutput() {
if (!this.conditions.length) { if (!this.conditions.length) {
return Promise.resolve([]); return Promise.resolve([]);
} }
@ -291,7 +291,7 @@ export default class ConditionManager extends EventEmitter {
let latestTimestamp; let latestTimestamp;
let conditionResults = {}; let conditionResults = {};
const conditionRequests = this.conditions const conditionRequests = this.conditions
.map(condition => condition.requestLADConditionResult(options)); .map(condition => condition.requestLADConditionResult());
return Promise.all(conditionRequests) return Promise.all(conditionRequests)
.then((results) => { .then((results) => {

View File

@ -40,10 +40,10 @@ export default class ConditionSetTelemetryProvider {
return domainObject.type === 'conditionSet'; return domainObject.type === 'conditionSet';
} }
request(domainObject, options) { request(domainObject) {
let conditionManager = this.getConditionManager(domainObject); let conditionManager = this.getConditionManager(domainObject);
return conditionManager.requestLADConditionSetOutput(options) return conditionManager.requestLADConditionSetOutput()
.then(latestOutput => { .then(latestOutput => {
return latestOutput; return latestOutput;
}); });
@ -52,9 +52,7 @@ export default class ConditionSetTelemetryProvider {
subscribe(domainObject, callback) { subscribe(domainObject, callback) {
let conditionManager = this.getConditionManager(domainObject); let conditionManager = this.getConditionManager(domainObject);
conditionManager.on('conditionSetResultUpdated', (data) => { conditionManager.on('conditionSetResultUpdated', callback);
callback(data);
});
return this.destroyConditionManager.bind(this, this.openmct.objects.makeKeyString(domainObject.identifier)); return this.destroyConditionManager.bind(this, this.openmct.objects.makeKeyString(domainObject.identifier));
} }

View File

@ -35,7 +35,6 @@ export default class StyleRuleManager extends EventEmitter {
if (styleConfiguration) { if (styleConfiguration) {
this.initialize(styleConfiguration); this.initialize(styleConfiguration);
if (styleConfiguration.conditionSetIdentifier) { if (styleConfiguration.conditionSetIdentifier) {
this.openmct.time.on("bounds", this.refreshData.bind(this));
this.subscribeToConditionSet(); this.subscribeToConditionSet();
} else { } else {
this.applyStaticStyle(); this.applyStaticStyle();
@ -84,25 +83,6 @@ export default class StyleRuleManager extends EventEmitter {
}); });
} }
refreshData(bounds, isTick) {
if (!isTick) {
let options = {
start: bounds.start,
end: bounds.end,
size: 1,
strategy: 'latest'
};
this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => {
this.openmct.telemetry.request(conditionSetDomainObject, options)
.then(output => {
if (output && output.length) {
this.handleConditionSetResultUpdated(output[0]);
}
});
});
}
}
updateObjectStyleConfig(styleConfiguration) { updateObjectStyleConfig(styleConfiguration) {
if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) { if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) {
this.initialize(styleConfiguration || {}); this.initialize(styleConfiguration || {});
@ -180,14 +160,10 @@ export default class StyleRuleManager extends EventEmitter {
destroy() { destroy() {
if (this.stopProvidingTelemetry) { if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry(); this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry; delete this.stopProvidingTelemetry;
} }
this.openmct.time.off("bounds", this.refreshData);
this.openmct.editor.off('isEditing', this.toggleSubscription);
this.conditionSetIdentifier = undefined; this.conditionSetIdentifier = undefined;
} }

View File

@ -52,6 +52,7 @@
<div class="c-inspect-styles__content c-inspect-styles__condition-set"> <div class="c-inspect-styles__content c-inspect-styles__condition-set">
<a v-if="conditionSetDomainObject" <a v-if="conditionSetDomainObject"
class="c-object-label icon-conditional" class="c-object-label icon-conditional"
:href="navigateToPath"
@click="navigateOrPreview" @click="navigateOrPreview"
> >
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span> <span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
@ -285,8 +286,6 @@ export default {
if (this.openmct.editor.isEditing()) { if (this.openmct.editor.isEditing()) {
event.preventDefault(); event.preventDefault();
this.previewAction.invoke(this.objectPath); this.previewAction.invoke(this.objectPath);
} else {
this.openmct.router.navigate(this.navigateToPath);
} }
}, },
removeConditionSet() { removeConditionSet() {

View File

@ -66,6 +66,7 @@
<div class="c-inspect-styles__content c-inspect-styles__condition-set"> <div class="c-inspect-styles__content c-inspect-styles__condition-set">
<a v-if="conditionSetDomainObject" <a v-if="conditionSetDomainObject"
class="c-object-label" class="c-object-label"
:href="navigateToPath"
@click="navigateOrPreview" @click="navigateOrPreview"
> >
<span class="c-object-label__type-icon icon-conditional"></span> <span class="c-object-label__type-icon icon-conditional"></span>
@ -308,8 +309,6 @@ export default {
if (this.openmct.editor.isEditing()) { if (this.openmct.editor.isEditing()) {
event.preventDefault(); event.preventDefault();
this.previewAction.invoke(this.objectPath); this.previewAction.invoke(this.objectPath);
} else {
this.openmct.router.navigate(this.navigateToPath);
} }
}, },
isItemType(type, item) { isItemType(type, item) {
@ -345,11 +344,6 @@ export default {
const layoutItem = selectionItem[0].context.layoutItem; const layoutItem = selectionItem[0].context.layoutItem;
const isChildItem = selectionItem.length > 1; const isChildItem = selectionItem.length > 1;
if (!item && !layoutItem) {
// cases where selection is used for table cells
return;
}
if (!isChildItem) { if (!isChildItem) {
domainObject = item; domainObject = item;
itemStyle = getApplicableStylesForItem(item); itemStyle = getApplicableStylesForItem(item);

View File

@ -147,16 +147,12 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry); this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry);
} }
requestLAD(telemetryObjects, requestOptions) { requestLAD(telemetryObjects) {
let options = { const options = {
strategy: 'latest', strategy: 'latest',
size: 1 size: 1
}; };
if (requestOptions !== undefined) {
options = Object.assign(options, requestOptions);
}
if (!this.isValid()) { if (!this.isValid()) {
return this.formatData({}, telemetryObjects); return this.formatData({}, telemetryObjects);
} }

View File

@ -58,10 +58,6 @@ export default class TelemetryCriterion extends EventEmitter {
} }
} }
usesTelemetry(id) {
return this.telemetryObjectIdAsString && (this.telemetryObjectIdAsString === id);
}
subscribeForStaleData() { subscribeForStaleData() {
if (this.stalenessSubscription) { if (this.stalenessSubscription) {
this.stalenessSubscription.clear(); this.stalenessSubscription.clear();
@ -137,16 +133,12 @@ export default class TelemetryCriterion extends EventEmitter {
} }
} }
requestLAD(telemetryObjects, requestOptions) { requestLAD() {
let options = { const options = {
strategy: 'latest', strategy: 'latest',
size: 1 size: 1
}; };
if (requestOptions !== undefined) {
options = Object.assign(options, requestOptions);
}
if (!this.isValid()) { if (!this.isValid()) {
return { return {
id: this.id, id: this.id,

View File

@ -104,7 +104,7 @@ export function getConsolidatedStyleValues(multipleItemStyles) {
const properties = Object.keys(styleProps); const properties = Object.keys(styleProps);
properties.forEach((property) => { properties.forEach((property) => {
const values = aggregatedStyleValues[property]; const values = aggregatedStyleValues[property];
if (values && values.length) { if (values.length) {
if (values.every(value => value === values[0])) { if (values.every(value => value === values[0])) {
styleValues[property] = values[0]; styleValues[property] = values[0];
} else { } else {

View File

@ -46,7 +46,7 @@ xdescribe("the plugin", () => {
}); });
afterEach(() => { afterEach(() => {
return resetApplicationState(openmct); resetApplicationState(openmct);
}); });
it('installs the new folder action', () => { it('installs the new folder action', () => {

View File

@ -0,0 +1,31 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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.
*****************************************************************************/
export default function plugin() {
return function install(openmct) {
openmct.on('start', () => {
const body = document.getElementsByTagName('body')[0];
body.classList.add('desktop');
body.classList.add('portrait');
});
};
}

View File

@ -235,7 +235,7 @@ define(['lodash'], function (_) {
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`, message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
buttons: [ buttons: [
{ {
label: 'OK', label: 'Ok',
emphasis: 'true', emphasis: 'true',
callback: function () { callback: function () {
removeItem(getAllTypes(selection)); removeItem(getAllTypes(selection));

View File

@ -147,7 +147,7 @@ export default {
this.mutablePromise.then(() => { this.mutablePromise.then(() => {
this.openmct.objects.destroyMutable(this.domainObject); this.openmct.objects.destroyMutable(this.domainObject);
}); });
} else if (this.domainObject.isMutable) { } else {
this.openmct.objects.destroyMutable(this.domainObject); this.openmct.objects.destroyMutable(this.domainObject);
} }
}, },

View File

@ -240,7 +240,7 @@ export default {
this.mutablePromise.then(() => { this.mutablePromise.then(() => {
this.openmct.objects.destroyMutable(this.domainObject); this.openmct.objects.destroyMutable(this.domainObject);
}); });
} else if (this.domainObject.isMutable) { } else {
this.openmct.objects.destroyMutable(this.domainObject); this.openmct.objects.destroyMutable(this.domainObject);
} }
}, },
@ -269,12 +269,7 @@ export default {
}, },
subscribeToObject() { subscribeToObject() {
this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) { this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) {
const key = this.openmct.time.timeSystem().key; if (this.openmct.time.clock() !== undefined) {
const datumTimeStamp = datum[key];
if (this.openmct.time.clock() !== undefined
|| (datumTimeStamp
&& (this.openmct.time.bounds().end >= datumTimeStamp))
) {
this.updateView(datum); this.updateView(datum);
} }
}.bind(this)); }.bind(this));

View File

@ -112,7 +112,7 @@ describe("The Duplicate Action plugin", () => {
}); });
afterEach(() => { afterEach(() => {
return resetApplicationState(openmct); resetApplicationState(openmct);
}); });
it("should be defined", () => { it("should be defined", () => {

View File

@ -90,12 +90,14 @@ export default {
this.composition.load(); this.composition.load();
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters); this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
this.unobserveGlobalFilters = this.openmct.objects.observe(this.providedObject, 'configuration.globalFilters', this.updateGlobalFilters); this.unobserveGlobalFilters = this.openmct.objects.observe(this.providedObject, 'configuration.globalFilters', this.updateGlobalFilters);
this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject);
}, },
beforeDestroy() { beforeDestroy() {
this.composition.off('add', this.addChildren); this.composition.off('add', this.addChildren);
this.composition.off('remove', this.removeChildren); this.composition.off('remove', this.removeChildren);
this.unobserve(); this.unobserve();
this.unobserveGlobalFilters(); this.unobserveGlobalFilters();
this.unobserveAllMutation();
}, },
methods: { methods: {
addChildren(domainObject) { addChildren(domainObject) {
@ -156,28 +158,25 @@ export default {
}, },
getGlobalFiltersToRemove(keyString) { getGlobalFiltersToRemove(keyString) {
let filtersToRemove = new Set(); let filtersToRemove = new Set();
const child = this.children[keyString];
if (child && child.metadataWithFilters) {
const metadataWithFilters = child.metadataWithFilters;
metadataWithFilters.forEach(metadatum => {
let keepFilter = false;
Object.keys(this.children).forEach(childKeyString => {
if (childKeyString !== keyString) {
let filterMatched = this.children[childKeyString].metadataWithFilters.some(childMetadatum => childMetadatum.key === metadatum.key);
if (filterMatched) { this.children[keyString].metadataWithFilters.forEach(metadatum => {
keepFilter = true; let keepFilter = false;
Object.keys(this.children).forEach(childKeyString => {
if (childKeyString !== keyString) {
let filterMatched = this.children[childKeyString].metadataWithFilters.some(childMetadatum => childMetadatum.key === metadatum.key);
return; if (filterMatched) {
} keepFilter = true;
return;
} }
});
if (!keepFilter) {
filtersToRemove.add(metadatum.key);
} }
}); });
}
if (!keepFilter) {
filtersToRemove.add(metadatum.key);
}
});
return Array.from(filtersToRemove); return Array.from(filtersToRemove);
}, },

View File

@ -97,7 +97,7 @@ function ToolbarProvider(openmct) {
message: `This action will remove this frame from this Flexible Layout. Do you want to continue?`, message: `This action will remove this frame from this Flexible Layout. Do you want to continue?`,
buttons: [ buttons: [
{ {
label: 'OK', label: 'Ok',
emphasis: 'true', emphasis: 'true',
callback: function () { callback: function () {
deleteFrameAction(primary.context.frameId); deleteFrameAction(primary.context.frameId);
@ -162,7 +162,7 @@ function ToolbarProvider(openmct) {
message: 'This action will permanently delete this container from this Flexible Layout', message: 'This action will permanently delete this container from this Flexible Layout',
buttons: [ buttons: [
{ {
label: 'OK', label: 'Ok',
emphasis: 'true', emphasis: 'true',
callback: function () { callback: function () {
removeContainer(containerId); removeContainer(containerId);

View File

@ -5,7 +5,7 @@
'is-alias': item.isAlias === true, 'is-alias': item.isAlias === true,
'c-grid-item--unknown': item.type.cssClass === undefined || item.type.cssClass.indexOf('unknown') !== -1 'c-grid-item--unknown': item.type.cssClass === undefined || item.type.cssClass.indexOf('unknown') !== -1
}, statusClass]" }, statusClass]"
@click="navigate" :href="objectLink"
> >
<div <div
class="c-grid-item__type-icon" class="c-grid-item__type-icon"
@ -49,17 +49,11 @@ import statusListener from './status-listener';
export default { export default {
mixins: [contextMenuGesture, objectLink, statusListener], mixins: [contextMenuGesture, objectLink, statusListener],
inject: ['openmct'],
props: { props: {
item: { item: {
type: Object, type: Object,
required: true required: true
} }
},
methods: {
navigate() {
this.openmct.router.navigate(this.objectLink);
}
} }
}; };
</script> </script>

View File

@ -11,7 +11,7 @@
ref="objectLink" ref="objectLink"
class="c-object-label" class="c-object-label"
:class="[statusClass]" :class="[statusClass]"
@click="navigate" :href="objectLink"
> >
<div <div
class="c-object-label__type-icon c-list-item__name__type-icon" class="c-object-label__type-icon c-list-item__name__type-icon"
@ -45,7 +45,6 @@ import statusListener from './status-listener';
export default { export default {
mixins: [contextMenuGesture, objectLink, statusListener], mixins: [contextMenuGesture, objectLink, statusListener],
inject: ['openmct'],
props: { props: {
item: { item: {
type: Object, type: Object,
@ -57,7 +56,7 @@ export default {
return moment(timestamp).format(format); return moment(timestamp).format(format);
}, },
navigate() { navigate() {
this.openmct.router.navigate(this.objectLink); this.$refs.objectLink.click();
} }
} }
}; };

View File

@ -19,10 +19,6 @@
margin: 0 $interiorMargin $interiorMargin 0; margin: 0 $interiorMargin $interiorMargin 0;
} }
} }
body.mobile & {
flex: 1 0 auto;
}
} }
/******************************* GRID ITEMS */ /******************************* GRID ITEMS */

View File

@ -29,6 +29,13 @@ define([
) { ) {
return function plugin() { return function plugin() {
return function install(openmct) { return function install(openmct) {
openmct.types.addType('folder', {
name: "Folder",
key: "folder",
description: "Create folders to organize other objects or links to objects without the ability to edit it's properties.",
cssClass: "icon-folder",
creatable: true
});
openmct.objectViews.addProvider(new FolderGridView(openmct)); openmct.objectViews.addProvider(new FolderGridView(openmct));
openmct.objectViews.addProvider(new FolderListView(openmct)); openmct.objectViews.addProvider(new FolderListView(openmct));
}; };

View File

@ -41,7 +41,7 @@ export default class GoToOriginalAction {
.slice(1) .slice(1)
.join('/'); .join('/');
this._openmct.router.navigate(url); window.location.href = url;
}); });
} }
appliesTo(objectPath) { appliesTo(objectPath) {

View File

@ -47,6 +47,7 @@ describe("the plugin", () => {
}); });
describe('when invoked', () => { describe('when invoked', () => {
beforeEach(() => { beforeEach(() => {
mockObjectPath = [{ mockObjectPath = [{
name: 'mock folder', name: 'mock folder',
@ -62,15 +63,11 @@ describe("the plugin", () => {
key: 'test' key: 'test'
} }
})); }));
goToFolderAction.invoke(mockObjectPath); goToFolderAction.invoke(mockObjectPath);
}); });
it('goes to the original location', (done) => { it('goes to the original location', () => {
setTimeout(() => { expect(window.location.href).toContain('context.html#/browse/?tc.mode=fixed&tc.startBound=0&tc.endBound=1&tc.timeSystem=utc');
expect(window.location.href).toContain('context.html#/browse/?tc.mode=fixed&tc.startBound=0&tc.endBound=1&tc.timeSystem=utc');
done();
}, 1500);
}); });
}); });
}); });

View File

@ -23,7 +23,7 @@
<template> <template>
<div <div
class="c-compass" class="c-compass"
:style="`width: ${ sizedImageDimensions.width }px; height: ${ sizedImageDimensions.height }px`" :style="compassDimensionsStyle"
> >
<CompassHUD <CompassHUD
v-if="hasCameraFieldOfView" v-if="hasCameraFieldOfView"
@ -34,7 +34,6 @@
<CompassRose <CompassRose
v-if="hasCameraFieldOfView" v-if="hasCameraFieldOfView"
:heading="heading" :heading="heading"
:sized-image-width="sizedImageDimensions.width"
:sun-heading="sunHeading" :sun-heading="sunHeading"
:camera-angle-of-view="cameraAngleOfView" :camera-angle-of-view="cameraAngleOfView"
:camera-pan="cameraPan" :camera-pan="cameraPan"
@ -78,20 +77,6 @@ export default {
} }
}, },
computed: { computed: {
sizedImageDimensions() {
let sizedImageDimensions = {};
if ((this.containerWidth / this.containerHeight) > this.naturalAspectRatio) {
// container is wider than image
sizedImageDimensions.width = this.containerHeight * this.naturalAspectRatio;
sizedImageDimensions.height = this.containerHeight;
} else {
// container is taller than image
sizedImageDimensions.width = this.containerWidth;
sizedImageDimensions.height = this.containerWidth * this.naturalAspectRatio;
}
return sizedImageDimensions;
},
hasCameraFieldOfView() { hasCameraFieldOfView() {
return this.cameraPan !== undefined && this.cameraAngleOfView > 0; return this.cameraPan !== undefined && this.cameraAngleOfView > 0;
}, },
@ -109,6 +94,25 @@ export default {
}, },
cameraAngleOfView() { cameraAngleOfView() {
return CAMERA_ANGLE_OF_VIEW; return CAMERA_ANGLE_OF_VIEW;
},
compassDimensionsStyle() {
const containerAspectRatio = this.containerWidth / this.containerHeight;
let width;
let height;
if (containerAspectRatio < this.naturalAspectRatio) {
width = '100%';
height = `${ this.containerWidth / this.naturalAspectRatio }px`;
} else {
width = `${ this.containerHeight * this.naturalAspectRatio }px`;
height = '100%';
}
return {
width: width,
height: height
};
} }
}, },
methods: { methods: {

View File

@ -22,134 +22,129 @@
<template> <template>
<div <div
class="w-direction-rose" class="c-direction-rose"
:class="compassRoseSizingClasses" @click="toggleLockCompass"
> >
<div <div
class="c-direction-rose" class="c-nsew"
@click="toggleLockCompass" :style="compassRoseStyle"
> >
<div <svg
class="c-nsew" class="c-nsew__minor-ticks"
:style="compassRoseStyle" viewBox="0 0 100 100"
> >
<svg <rect
class="c-nsew__minor-ticks" class="c-nsew__tick c-tick-ne"
viewBox="0 0 100 100" x="49"
> y="0"
<rect width="2"
class="c-nsew__tick c-tick-ne" height="5"
x="49" />
y="0" <rect
width="2" class="c-nsew__tick c-tick-se"
height="5" x="95"
/> y="49"
<rect width="5"
class="c-nsew__tick c-tick-se" height="2"
x="95" />
y="49" <rect
width="5" class="c-nsew__tick c-tick-sw"
height="2" x="49"
/> y="95"
<rect width="2"
class="c-nsew__tick c-tick-sw" height="5"
x="49" />
y="95" <rect
width="2" class="c-nsew__tick c-tick-nw"
height="5" x="0"
/> y="49"
<rect width="5"
class="c-nsew__tick c-tick-nw" height="2"
x="0" />
y="49"
width="5"
height="2"
/>
</svg> </svg>
<svg <svg
class="c-nsew__ticks" class="c-nsew__ticks"
viewBox="0 0 100 100" viewBox="0 0 100 100"
> >
<polygon <polygon
class="c-nsew__tick c-tick-n" class="c-nsew__tick c-tick-n"
points="50,0 60,10 40,10" points="50,0 57,5 43,5"
/> />
<rect <rect
class="c-nsew__tick c-tick-e" class="c-nsew__tick c-tick-e"
x="95" x="95"
y="49" y="49"
width="5" width="5"
height="2" height="2"
/> />
<rect <rect
class="c-nsew__tick c-tick-w" class="c-nsew__tick c-tick-w"
x="0" x="0"
y="49" y="49"
width="5" width="5"
height="2" height="2"
/> />
<rect <rect
class="c-nsew__tick c-tick-s" class="c-nsew__tick c-tick-s"
x="49" x="49"
y="95" y="95"
width="2" width="2"
height="5" height="5"
/> />
<text <text
class="c-nsew__label c-label-n" class="c-nsew__label c-label-n"
text-anchor="middle" text-anchor="middle"
:transform="northTextTransform" :transform="northTextTransform"
>N</text> >N</text>
<text <text
class="c-nsew__label c-label-e" class="c-nsew__label c-label-e"
text-anchor="middle" text-anchor="middle"
:transform="eastTextTransform" :transform="eastTextTransform"
>E</text> >E</text>
<text <text
class="c-nsew__label c-label-w" class="c-nsew__label c-label-w"
text-anchor="middle" text-anchor="middle"
:transform="southTextTransform" :transform="southTextTransform"
>W</text> >W</text>
<text <text
class="c-nsew__label c-label-s" class="c-nsew__label c-label-s"
text-anchor="middle" text-anchor="middle"
:transform="westTextTransform" :transform="westTextTransform"
>S</text> >S</text>
</svg> </svg>
</div>
<div
v-if="hasHeading"
class="c-spacecraft-body"
:style="headingStyle"
>
</div>
<div
v-if="hasSunHeading"
class="c-sun"
:style="sunHeadingStyle"
></div>
<div
class="c-cam-field"
:style="cameraPanStyle"
>
<div class="cam-field-half cam-field-half-l">
<div
class="cam-field-area"
:style="cameraFOVStyleLeftHalf"
></div>
</div> </div>
<div class="cam-field-half cam-field-half-r">
<div <div
v-if="hasHeading" class="cam-field-area"
class="c-spacecraft-body" :style="cameraFOVStyleRightHalf"
:style="headingStyle" ></div>
>
</div>
<div
v-if="hasSunHeading"
class="c-sun"
:style="sunHeadingStyle"
></div>
<div
class="c-cam-field"
:style="cameraPanStyle"
>
<div class="cam-field-half cam-field-half-l">
<div
class="cam-field-area"
:style="cameraFOVStyleLeftHalf"
></div>
</div>
<div class="cam-field-half cam-field-half-r">
<div
class="cam-field-area"
:style="cameraFOVStyleRightHalf"
></div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -160,10 +155,6 @@ import { rotate } from './utils';
export default { export default {
props: { props: {
sizedImageWidth: {
type: Number,
required: true
},
heading: { heading: {
type: Number, type: Number,
required: true required: true
@ -186,24 +177,12 @@ export default {
} }
}, },
computed: { computed: {
compassRoseSizingClasses() { north() {
let compassRoseSizingClasses = ''; return this.lockCompass ? rotate(-this.cameraPan) : 0;
if (this.sizedImageWidth < 300) {
compassRoseSizingClasses = '--rose-small --rose-min';
} else if (this.sizedImageWidth < 500) {
compassRoseSizingClasses = '--rose-small';
} else if (this.sizedImageWidth > 1000) {
compassRoseSizingClasses = '--rose-max';
}
return compassRoseSizingClasses;
}, },
compassRoseStyle() { compassRoseStyle() {
return { transform: `rotate(${ this.north }deg)` }; return { transform: `rotate(${ this.north }deg)` };
}, },
north() {
return this.lockCompass ? rotate(-this.cameraPan) : 0;
},
northTextTransform() { northTextTransform() {
return this.cardinalPointsTextTransform.north; return this.cardinalPointsTextTransform.north;
}, },
@ -225,10 +204,10 @@ export default {
const rotation = `rotate(${ -this.north })`; const rotation = `rotate(${ -this.north })`;
return { return {
north: `translate(50,23) ${ rotation }`, north: `translate(50,15) ${ rotation }`,
east: `translate(82,50) ${ rotation }`, east: `translate(87,50) ${ rotation }`,
south: `translate(18,50) ${ rotation }`, south: `translate(13,50) ${ rotation }`,
west: `translate(50,82) ${ rotation }` west: `translate(50,87) ${ rotation }`
}; };
}, },
hasHeading() { hasHeading() {

View File

@ -10,7 +10,6 @@ $elemBg: rgba(black, 0.7);
} }
.c-compass { .c-compass {
pointer-events: none; // This allows the image element to receive a browser-level context click
position: absolute; position: absolute;
left: 50%; left: 50%;
top: 50%; top: 50%;
@ -21,253 +20,195 @@ $elemBg: rgba(black, 0.7);
/***************************** COMPASS HUD */ /***************************** COMPASS HUD */
.c-hud { .c-hud {
// To be placed within a imagery view, in the bounding box of the image // To be placed within a imagery view, in the bounding box of the image
$m: 1px; $m: 1px;
$padTB: 2px; $padTB: 2px;
$padLR: $padTB; $padLR: $padTB;
color: $interfaceKeyColor; color: $interfaceKeyColor;
font-size: 0.8em; font-size: 0.8em;
position: absolute;
top: $m; right: $m; left: $m;
height: 18px;
svg, div {
position: absolute; position: absolute;
top: $m; }
right: $m;
left: $m;
height: 18px;
svg, div { &__display {
position: absolute; height: 30px;
} pointer-events: all;
position: absolute;
top: 0;
right: 0;
left: 0;
}
&__display { &__range {
height: 30px; border: 1px solid $interfaceKeyColor;
pointer-events: all; border-top-color: transparent;
position: absolute; position: absolute;
top: 0; top: 50%; right: $padLR; bottom: $padTB; left: $padLR;
right: 0; }
left: 0;
}
&__range { [class*="__dir"] {
border: 1px solid $interfaceKeyColor; // NSEW
border-top-color: transparent; display: inline-block;
position: absolute; font-weight: bold;
top: 50%; text-shadow: 0 1px 2px black;
right: $padLR; top: 50%;
bottom: $padTB; transform: translate(-50%,-50%);
left: $padLR; z-index: 2;
} }
[class*="__dir"] { [class*="__dir--sub"] {
// NSEW font-weight: normal;
display: inline-block; opacity: 0.5;
font-weight: bold; }
text-shadow: 0 1px 2px black;
top: 50%;
transform: translate(-50%, -50%);
z-index: 2;
}
[class*="__dir--sub"] { &__sun {
font-weight: normal; $s: 10px;
opacity: 0.5; @include sun('circle farthest-side at bottom');
} bottom: $padTB + 2px;
height: $s; width: $s*2;
&__sun { opacity: 0.8;
$s: 10px; transform: translateX(-50%);
@include sun('circle farthest-side at bottom'); z-index: 1;
bottom: $padTB + 2px; }
height: $s;
width: $s*2;
opacity: 0.8;
transform: translateX(-50%);
z-index: 1;
}
} }
/***************************** COMPASS DIRECTIONS */ /***************************** COMPASS DIRECTIONS */
.c-nsew { .c-nsew {
$color: $interfaceKeyColor; $color: $interfaceKeyColor;
$inset: 5%; $inset: 7%;
$tickHeightPerc: 15%; $tickHeightPerc: 15%;
text-shadow: black 0 0 10px; text-shadow: black 0 0 10px;
top: $inset; top: $inset; right: $inset; bottom: $inset; left: $inset;
right: $inset; z-index: 3;
bottom: $inset;
left: $inset;
z-index: 3;
&__tick, &__tick,
&__label { &__label {
fill: $color; fill: $color;
} }
&__minor-ticks { &__minor-ticks {
opacity: 0.5; opacity: 0.5;
transform-origin: center; transform-origin: center;
transform: rotate(45deg); transform: rotate(45deg);
} }
&__label { &__label {
dominant-baseline: central; dominant-baseline: central;
font-size: 1.25em; font-size: 0.8em;
font-weight: bold; font-weight: bold;
} }
.c-label-n { .c-label-n {
font-size: 2em; font-size: 1.1em;
} }
} }
/***************************** CAMERA FIELD ANGLE */ /***************************** CAMERA FIELD ANGLE */
.c-cam-field { .c-cam-field {
$color: white; $color: white;
opacity: 0.3; opacity: 0.2;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2;
.cam-field-half {
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: 2;
.cam-field-half { .cam-field-area {
top: 0; background: $color;
right: 0; top: -30%;
bottom: 0; right: 0;
left: 0; bottom: -30%;
left: 0;
.cam-field-area {
background: $color;
top: -30%;
right: 0;
bottom: -30%;
left: 0;
}
// clip-paths overlap a bit to avoid a gap between halves
&-l {
clip-path: polygon(0 0, 50.5% 0, 50.5% 100%, 0 100%);
.cam-field-area {
transform-origin: left center;
}
}
&-r {
clip-path: polygon(49.5% 0, 100% 0, 100% 100%, 49.5% 100%);
.cam-field-area {
transform-origin: right center;
}
}
} }
// clip-paths overlap a bit to avoid a gap between halves
&-l {
clip-path: polygon(0 0, 50.5% 0, 50.5% 100%, 0 100%);
.cam-field-area {
transform-origin: left center;
}
}
&-r {
clip-path: polygon(49.5% 0, 100% 0, 100% 100%, 49.5% 100%);
.cam-field-area {
transform-origin: right center;
}
}
}
} }
/***************************** SPACECRAFT BODY */ /***************************** SPACECRAFT BODY */
.c-spacecraft-body { .c-spacecraft-body {
$color: $interfaceKeyColor; $color: $interfaceKeyColor;
$s: 30%; $s: 30%;
background: $color; background: $color;
border-radius: 3px; border-radius: 3px;
height: $s; height: $s; width: $s;
width: $s; left: 50%; top: 50%;
left: 50%; opacity: 0.4;
top: 50%; transform-origin: center top;
opacity: 0.4;
transform-origin: center top;
transform: translateX(-50%); // center by default, overridden by CompassRose.vue / headingStyle()
&:before { &:before {
// Direction arrow // Direction arrow
$color: rgba(black, 0.5); $color: rgba(black, 0.5);
$arwPointerY: 60%; $arwPointerY: 60%;
$arwBodyOffset: 25%; $arwBodyOffset: 25%;
background: $color; background: $color;
content: ''; content: '';
display: block; display: block;
position: absolute; position: absolute;
top: 10%; top: 10%; right: 20%; bottom: 50%; left: 20%;
right: 20%; clip-path: polygon(50% 0, 100% $arwPointerY, 100%-$arwBodyOffset $arwPointerY, 100%-$arwBodyOffset 100%, $arwBodyOffset 100%, $arwBodyOffset $arwPointerY, 0 $arwPointerY);
bottom: 50%; }
left: 20%;
clip-path: polygon(50% 0, 100% $arwPointerY, 100%-$arwBodyOffset $arwPointerY, 100%-$arwBodyOffset 100%, $arwBodyOffset 100%, $arwBodyOffset $arwPointerY, 0 $arwPointerY);
}
} }
/***************************** DIRECTION ROSE */ /***************************** DIRECTION ROSE */
.w-direction-rose {
$s: 10%;
$m: 2%;
position: absolute;
bottom: $m;
left: $m;
width: $s;
padding-top: $s;
&.--rose-min {
$s: 30px;
width: $s;
padding-top: $s;
}
&.--rose-small {
.c-nsew__minor-ticks,
.c-tick-w,
.c-tick-s,
.c-tick-e,
.c-label-w,
.c-label-s,
.c-label-e {
display: none;
}
.c-label-n {
font-size: 2.5em;
}
}
&.--rose-max {
$s: 100px;
width: $s;
padding-top: $s;
}
}
.c-direction-rose { .c-direction-rose {
$c2: rgba(white, 0.1); $d: 100px;
background: $elemBg; $c2: rgba(white, 0.1);
background-image: radial-gradient(circle closest-side, transparent, transparent 80%, $c2); background: $elemBg;
transform-origin: 0 0; background-image: radial-gradient(circle closest-side, transparent, transparent 80%, $c2);
width: $d;
height: $d;
transform-origin: 0 0;
position: absolute;
bottom: 10px; left: 10px;
clip-path: circle(50% at 50% 50%);
border-radius: 100%;
svg, div {
position: absolute; position: absolute;
}
// Sun
.c-sun {
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
clip-path: circle(50% at 50% 50%);
border-radius: 100%;
pointer-events: all;
svg, div { &:before {
position: absolute; $s: 35%;
} @include sun();
content: '';
// Sun display: block;
.c-sun { position: absolute;
top: 0; opacity: 0.7;
right: 0; top: 0; left: 50%;
bottom: 0; height:$s; width: $s;
left: 0; transform: translate(-50%, -60%);
&:before {
$s: 35%;
@include sun();
content: '';
display: block;
position: absolute;
opacity: 0.7;
top: 0;
left: 50%;
height: $s;
width: $s;
transform: translate(-50%, -60%);
}
} }
}
} }

View File

@ -135,14 +135,9 @@
:class="{ selected: focusedImageIndex === index && isPaused }" :class="{ selected: focusedImageIndex === index && isPaused }"
@click="setFocusedImage(index, thumbnailClick)" @click="setFocusedImage(index, thumbnailClick)"
> >
<a href="" <img class="c-thumb__image"
:download="image.imageDownloadName" :src="image.url"
@click.prevent
> >
<img class="c-thumb__image"
:src="image.url"
>
</a>
<div class="c-thumb__timestamp">{{ image.formattedTime }}</div> <div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
</div> </div>
</div> </div>
@ -223,9 +218,6 @@ export default {
canTrackDuration() { canTrackDuration() {
return this.openmct.time.clock() && this.timeSystem.isUTCBased; return this.openmct.time.clock() && this.timeSystem.isUTCBased;
}, },
focusedImageDownloadName() {
return this.getImageDownloadName(this.focusedImage);
},
isNextDisabled() { isNextDisabled() {
let disabled = false; let disabled = false;
@ -353,7 +345,6 @@ export default {
this.imageHints = { ...this.metadata.valuesForHints(['image'])[0] }; this.imageHints = { ...this.metadata.valuesForHints(['image'])[0] };
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER); this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageHints); this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageHints);
this.imageDownloadNameHints = { ...this.metadata.valuesForHints(['imageDownloadName'])[0]};
// related telemetry keys // related telemetry keys
this.spacecraftPositionKeys = ['positionX', 'positionY', 'positionZ']; this.spacecraftPositionKeys = ['positionX', 'positionY', 'positionZ'];
@ -390,9 +381,7 @@ export default {
delete this.unsubscribe; delete this.unsubscribe;
} }
if (this.imageContainerResizeObserver) { this.imageContainerResizeObserver.disconnect();
this.imageContainerResizeObserver.disconnect();
}
if (this.relatedTelemetry.hasRelatedTelemetry) { if (this.relatedTelemetry.hasRelatedTelemetry) {
this.relatedTelemetry.destroy(); this.relatedTelemetry.destroy();
@ -543,15 +532,6 @@ export default {
// Replace ISO "T" with a space to allow wrapping // Replace ISO "T" with a space to allow wrapping
return dateTimeStr.replace("T", " "); return dateTimeStr.replace("T", " ");
}, },
getImageDownloadName(datum) {
let imageDownloadName = '';
if (datum) {
const key = this.imageDownloadNameHints.key;
imageDownloadName = datum[key];
}
return imageDownloadName;
},
parseTime(datum) { parseTime(datum) {
if (!datum) { if (!datum) {
return; return;
@ -675,7 +655,6 @@ export default {
image.formattedTime = this.formatTime(datum); image.formattedTime = this.formatTime(datum);
image.url = this.formatImageUrl(datum); image.url = this.formatImageUrl(datum);
image.time = datum[this.timeKey]; image.time = datum[this.timeKey];
image.imageDownloadName = this.getImageDownloadName(datum);
this.imageHistory.push(image); this.imageHistory.push(image);
@ -704,7 +683,7 @@ export default {
window.clearInterval(this.durationTracker); window.clearInterval(this.durationTracker);
}, },
updateDuration() { updateDuration() {
let currentTime = this.openmct.time.clock() && this.openmct.time.clock().currentValue(); let currentTime = this.openmct.time.clock().currentValue();
this.numericDuration = currentTime - this.parsedSelectedTime; this.numericDuration = currentTime - this.parsedSelectedTime;
}, },
resetAgeCSS() { resetAgeCSS() {
@ -798,9 +777,6 @@ export default {
this.focusedImageNaturalAspectRatio = undefined; this.focusedImageNaturalAspectRatio = undefined;
const img = this.$refs.focusedImage; const img = this.$refs.focusedImage;
if (!img) {
return;
}
// TODO - should probably cache this // TODO - should probably cache this
img.addEventListener('load', () => { img.addEventListener('load', () => {

View File

@ -19,7 +19,7 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import ImageryPlugin from './plugin.js';
import Vue from 'vue'; import Vue from 'vue';
import { import {
createOpenMct, createOpenMct,
@ -89,11 +89,15 @@ describe("The Imagery View Layout", () => {
const START = Date.now(); const START = Date.now();
const COUNT = 10; const COUNT = 10;
let resolveFunction;
let openmct; let openmct;
let imageryPlugin;
let parent; let parent;
let child; let child;
let timeFormat = 'utc';
let bounds = {
start: START - TEN_MINUTES,
end: START
};
let imageTelemetry = generateTelemetry(START - TEN_MINUTES, COUNT); let imageTelemetry = generateTelemetry(START - TEN_MINUTES, COUNT);
let imageryObject = { let imageryObject = {
identifier: { identifier: {
@ -201,10 +205,6 @@ describe("The Imagery View Layout", () => {
openmct = createOpenMct(); openmct = createOpenMct();
openmct.install(openmct.plugins.MyItems());
openmct.install(openmct.plugins.LocalTimeSystem());
openmct.install(openmct.plugins.UTCTimeSystem());
parent = document.createElement('div'); parent = document.createElement('div');
child = document.createElement('div'); child = document.createElement('div');
parent.appendChild(child); parent.appendChild(child);
@ -215,18 +215,22 @@ describe("The Imagery View Layout", () => {
}); });
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([])); spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
imageryPlugin = new ImageryPlugin();
openmct.install(imageryPlugin);
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({})); spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
openmct.time.timeSystem(timeFormat, {
start: 0,
end: 4
});
openmct.on('start', done); openmct.on('start', done);
openmct.start(appHolder); openmct.startHeadless(appHolder);
}); });
afterEach(() => { afterEach(() => {
openmct.time.timeSystem('utc', {
start: 0,
end: 1
});
return resetApplicationState(openmct); return resetApplicationState(openmct);
}); });
@ -244,7 +248,7 @@ describe("The Imagery View Layout", () => {
let imageryViewProvider; let imageryViewProvider;
let imageryView; let imageryView;
beforeEach(async () => { beforeEach(async (done) => {
let telemetryRequestResolve; let telemetryRequestResolve;
let telemetryRequestPromise = new Promise((resolve) => { let telemetryRequestPromise = new Promise((resolve) => {
telemetryRequestResolve = resolve; telemetryRequestResolve = resolve;
@ -256,18 +260,23 @@ describe("The Imagery View Layout", () => {
return telemetryRequestPromise; return telemetryRequestPromise;
}); });
openmct.time.clock('local', {
start: bounds.start,
end: bounds.end + 100
});
applicableViews = openmct.objectViews.get(imageryObject, []); applicableViews = openmct.objectViews.get(imageryObject, []);
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey); imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
imageryView = imageryViewProvider.view(imageryObject); imageryView = imageryViewProvider.view(imageryObject);
imageryView.show(child); imageryView.show(child);
await telemetryRequestPromise; await telemetryRequestPromise;
await Vue.nextTick();
return done();
}); });
afterEach(() => { afterEach(() => {
openmct.time.stopClock();
openmct.router.removeListener('change:hash', resolveFunction);
imageryView.destroy(); imageryView.destroy();
}); });
@ -277,44 +286,43 @@ describe("The Imagery View Layout", () => {
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1); expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
}); });
it("should show the clicked thumbnail as the main image", (done) => { it("should show the clicked thumbnail as the main image", async () => {
const target = imageTelemetry[5].url; const target = imageTelemetry[5].url;
parent.querySelectorAll(`img[src='${target}']`)[0].click(); parent.querySelectorAll(`img[src='${target}']`)[0].click();
Vue.nextTick(() => { await Vue.nextTick();
const imageInfo = getImageInfo(parent); const imageInfo = getImageInfo(parent);
expect(imageInfo.url.indexOf(imageTelemetry[5].timeId)).not.toEqual(-1); expect(imageInfo.url.indexOf(imageTelemetry[5].timeId)).not.toEqual(-1);
});
it("should show that an image is new", async (done) => {
await Vue.nextTick();
// used in code, need to wait to the 500ms here too
setTimeout(() => {
const imageIsNew = isNew(parent);
expect(imageIsNew).toBeTrue();
done(); done();
}); }, REFRESH_CSS_MS);
}); });
xit("should show that an image is new", (done) => { it("should show that an image is not new", async (done) => {
Vue.nextTick(() => {
// used in code, need to wait to the 500ms here too
setTimeout(() => {
const imageIsNew = isNew(parent);
expect(imageIsNew).toBeTrue();
done();
}, REFRESH_CSS_MS);
});
});
xit("should show that an image is not new", (done) => {
const target = imageTelemetry[2].url; const target = imageTelemetry[2].url;
parent.querySelectorAll(`img[src='${target}']`)[0].click(); parent.querySelectorAll(`img[src='${target}']`)[0].click();
Vue.nextTick(() => { await Vue.nextTick();
// used in code, need to wait to the 500ms here too
setTimeout(() => {
const imageIsNew = isNew(parent);
expect(imageIsNew).toBeFalse(); // used in code, need to wait to the 500ms here too
done(); setTimeout(() => {
}, REFRESH_CSS_MS); const imageIsNew = isNew(parent);
});
expect(imageIsNew).toBeFalse();
done();
}, REFRESH_CSS_MS);
}); });
it("should navigate via arrow keys", (done) => { it("should navigate via arrow keys", async () => {
let keyOpts = { let keyOpts = {
element: parent.querySelector('.c-imagery'), element: parent.querySelector('.c-imagery'),
key: 'ArrowLeft', key: 'ArrowLeft',
@ -324,15 +332,14 @@ describe("The Imagery View Layout", () => {
simulateKeyEvent(keyOpts); simulateKeyEvent(keyOpts);
Vue.nextTick(() => { await Vue.nextTick();
const imageInfo = getImageInfo(parent);
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 2].timeId)).not.toEqual(-1); const imageInfo = getImageInfo(parent);
done();
}); expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 2].timeId)).not.toEqual(-1);
}); });
it("should navigate via numerous arrow keys", (done) => { it("should navigate via numerous arrow keys", async () => {
let element = parent.querySelector('.c-imagery'); let element = parent.querySelector('.c-imagery');
let type = 'keyup'; let type = 'keyup';
let leftKeyOpts = { let leftKeyOpts = {
@ -355,12 +362,12 @@ describe("The Imagery View Layout", () => {
// right once // right once
simulateKeyEvent(rightKeyOpts); simulateKeyEvent(rightKeyOpts);
Vue.nextTick(() => { await Vue.nextTick();
const imageInfo = getImageInfo(parent);
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 3].timeId)).not.toEqual(-1); const imageInfo = getImageInfo(parent);
done();
}); expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 3].timeId)).not.toEqual(-1);
}); });
}); });
}); });

View File

@ -3,6 +3,10 @@ import myItemsInterceptor from "./myItemsInterceptor";
export default function plugin() { export default function plugin() {
return function install(openmct) { return function install(openmct) {
openmct.objects.addRoot({
namespace: '',
key: 'mine'
});
myItemsInterceptor(openmct); myItemsInterceptor(openmct);
missingObjectInterceptor(openmct); missingObjectInterceptor(openmct);
}; };

View File

@ -0,0 +1,41 @@
export default class LocalStorageObjectProvider {
constructor({spaceKey = 'mct'}) {
this.localStorage = window.localStorage;
this.space = this.initializeSpace(spaceKey);
}
get(identifier) {
if (this.getSpaceAsObject()[identifier.key] !== undefined) {
const persistedModel = this.getSpaceAsObject()[identifier.key];
const domainObject = {
identifier,
...persistedModel
};
return Promise.resolve(domainObject);
} else {
return Promise.resolve(undefined);
}
}
getSpaceAsObject() {
return JSON.parse(this.space);
}
create(model) {
return this.setModel(model);
}
update(model) {
return this.setModel(model);
}
setModel(model) {
this.space[model.identifier.key] = JSON.stringify(model);
this.persist();
return Promise.resolve(true);
}
initializeSpace(spaceKey) {
if (this.localStorage[spaceKey] === undefined) {
this.localStorage[spaceKey] = JSON.stringify({});
}
return this.localStorage[spaceKey];
}
}

View File

@ -0,0 +1,29 @@
/*****************************************************************************
* 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.
*****************************************************************************/
import LocalStorageObjectProvider from './LocalStorageObjectProvider';
export default function (namespace = '', storageSpace = 'mct') {
return function (openmct) {
openmct.objects.addProvider(namespace, new LocalStorageObjectProvider(storageSpace));
};
}

View File

@ -55,7 +55,7 @@ describe("The local time", () => {
beforeEach(() => { beforeEach(() => {
localTimeSystem = openmct.time.timeSystem(LOCAL_SYSTEM_KEY, { localTimeSystem = openmct.time.timeSystem(LOCAL_SYSTEM_KEY, {
start: 0, start: 0,
end: 1 end: 4
}); });
}); });

View File

@ -81,7 +81,7 @@ describe("The Move Action plugin", () => {
}); });
afterEach(() => { afterEach(() => {
return resetApplicationState(openmct); resetApplicationState(openmct);
}); });
it("should be defined", () => { it("should be defined", () => {

View File

@ -135,7 +135,6 @@ import SearchResults from './SearchResults.vue';
import Sidebar from './Sidebar.vue'; import Sidebar from './Sidebar.vue';
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage'; import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries'; import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
import { NOTEBOOK_VIEW_TYPE } from '../notebook-constants';
import objectUtils from 'objectUtils'; import objectUtils from 'objectUtils';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
@ -190,14 +189,14 @@ export default {
selectedPage() { selectedPage() {
const pages = this.getPages(); const pages = this.getPages();
if (!pages) { if (!pages) {
return {}; return null;
} }
return pages.find(page => page.isSelected); return pages.find(page => page.isSelected);
}, },
selectedSection() { selectedSection() {
if (!this.sections.length) { if (!this.sections.length) {
return {}; return null;
} }
return this.sections.find(section => section.isSelected); return this.sections.find(section => section.isSelected);
@ -217,7 +216,6 @@ export default {
window.addEventListener('orientationchange', this.formatSidebar); window.addEventListener('orientationchange', this.formatSidebar);
window.addEventListener("hashchange", this.navigateToSectionPage, false); window.addEventListener("hashchange", this.navigateToSectionPage, false);
this.openmct.router.on('change:params', this.changeSectionPage);
this.navigateToSectionPage(); this.navigateToSectionPage();
}, },
@ -228,7 +226,6 @@ export default {
window.removeEventListener('orientationchange', this.formatSidebar); window.removeEventListener('orientationchange', this.formatSidebar);
window.removeEventListener("hashchange", this.navigateToSectionPage); window.removeEventListener("hashchange", this.navigateToSectionPage);
this.openmct.router.off('change:params', this.changeSectionPage);
}, },
updated: function () { updated: function () {
this.$nextTick(() => { this.$nextTick(() => {
@ -236,28 +233,6 @@ export default {
}); });
}, },
methods: { methods: {
changeSectionPage(newParams, oldParams, changedParams) {
if (newParams.view !== NOTEBOOK_VIEW_TYPE) {
return;
}
let pageId = newParams.pageId;
let sectionId = newParams.sectionId;
if (!pageId && !sectionId) {
return;
}
this.sections.forEach(section => {
section.isSelected = Boolean(section.id === sectionId);
if (section.isSelected) {
section.pages.forEach(page => {
page.isSelected = Boolean(page.id === pageId);
});
}
});
},
changeSelectedSection({ sectionId, pageId }) { changeSelectedSection({ sectionId, pageId }) {
const sections = this.sections.map(s => { const sections = this.sections.map(s => {
s.isSelected = false; s.isSelected = false;
@ -455,7 +430,7 @@ export default {
} }
// check for no entries first // check for no entries first
if (entries[section.id] && entries[section.id][page.id]) { if (entries[section.id]) {
const pageEntries = entries[section.id][page.id]; const pageEntries = entries[section.id][page.id];
pageEntries.forEach(entry => { pageEntries.forEach(entry => {
@ -543,11 +518,9 @@ export default {
return this.sections.find(section => section.isSelected); return this.sections.find(section => section.isSelected);
}, },
navigateToSectionPage() { navigateToSectionPage() {
let { pageId, sectionId } = this.openmct.router.getParams(); const { pageId, sectionId } = this.openmct.router.getParams();
if (!pageId || !sectionId) { if (!pageId || !sectionId) {
sectionId = this.selectedSection.id; return;
pageId = this.selectedPage.id;
} }
const sections = this.sections.map(s => { const sections = this.sections.map(s => {

View File

@ -145,7 +145,7 @@ export default {
const relativeHash = hash.slice(hash.indexOf('#')); const relativeHash = hash.slice(hash.indexOf('#'));
const url = new URL(relativeHash, `${location.protocol}//${location.host}${location.pathname}`); const url = new URL(relativeHash, `${location.protocol}//${location.host}${location.pathname}`);
this.openmct.router.navigate(url.hash); window.location.hash = url.hash;
}, },
formatTime(unixTime, timeFormat) { formatTime(unixTime, timeFormat) {
return Moment.utc(unixTime).format(timeFormat); return Moment.utc(unixTime).format(timeFormat);

View File

@ -22,7 +22,7 @@
<template> <template>
<div class="c-notebook__search-results"> <div class="c-notebook__search-results">
<div class="c-notebook__search-results__header">Search Results ({{ results.length }})</div> <div class="c-notebook__search-results__header">Search Results</div>
<div class="c-notebook__entries"> <div class="c-notebook__entries">
<NotebookEntry v-for="(result, index) in results" <NotebookEntry v-for="(result, index) in results"
:key="index" :key="index"

View File

@ -111,6 +111,10 @@ export default {
} }
} }
}, },
data() {
return {
};
},
computed: { computed: {
pages() { pages() {
const selectedSection = this.sections.find(section => section.isSelected); const selectedSection = this.sections.find(section => section.isSelected);

View File

@ -1,4 +1,3 @@
export const EVENT_SNAPSHOTS_UPDATED = 'SNAPSHOTS_UPDATED'; export const EVENT_SNAPSHOTS_UPDATED = 'SNAPSHOTS_UPDATED';
export const NOTEBOOK_DEFAULT = 'DEFAULT'; export const NOTEBOOK_DEFAULT = 'DEFAULT';
export const NOTEBOOK_SNAPSHOT = 'SNAPSHOT'; export const NOTEBOOK_SNAPSHOT = 'SNAPSHOT';
export const NOTEBOOK_VIEW_TYPE = 'notebook-vue';

View File

@ -65,8 +65,7 @@ describe("Notebook plugin:", () => {
afterAll(() => { afterAll(() => {
appHolder.remove(); appHolder.remove();
resetApplicationState(openmct);
return resetApplicationState(openmct);
}); });
it("has type as Notebook", () => { it("has type as Notebook", () => {

View File

@ -140,8 +140,7 @@ describe('Notebook Entries:', () => {
afterEach(() => { afterEach(() => {
notebookDomainObject.configuration.entries[selectedSection.id][selectedPage.id] = []; notebookDomainObject.configuration.entries[selectedSection.id][selectedPage.id] = [];
resetApplicationState(openmct);
return resetApplicationState(openmct);
}); });
it('getNotebookEntries has no entries', () => { it('getNotebookEntries has no entries', () => {

View File

@ -83,7 +83,7 @@ describe('Notebook Storage:', () => {
}); });
afterEach(() => { afterEach(() => {
return resetApplicationState(openmct); resetApplicationState(openmct);
}); });
it('has empty local Storage', () => { it('has empty local Storage', () => {

View File

@ -26,7 +26,6 @@ import CouchObjectQueue from "./CouchObjectQueue";
const REV = "_rev"; const REV = "_rev";
const ID = "_id"; const ID = "_id";
const HEARTBEAT = 50000; const HEARTBEAT = 50000;
const ALL_DOCS = "_all_docs?include_docs=true";
export default class CouchObjectProvider { export default class CouchObjectProvider {
// options { // options {
@ -42,8 +41,6 @@ export default class CouchObjectProvider {
this.objectQueue = {}; this.objectQueue = {};
this.observeEnabled = options.disableObserve !== true; this.observeEnabled = options.disableObserve !== true;
this.observers = {}; this.observers = {};
this.batchIds = [];
if (this.observeEnabled) { if (this.observeEnabled) {
this.observeObjectChanges(options.filter); this.observeObjectChanges(options.filter);
} }
@ -70,9 +67,6 @@ export default class CouchObjectProvider {
// stringify body if needed // stringify body if needed
if (fetchOptions.body) { if (fetchOptions.body) {
fetchOptions.body = JSON.stringify(fetchOptions.body); fetchOptions.body = JSON.stringify(fetchOptions.body);
fetchOptions.headers = {
"Content-Type": "application/json"
};
} }
return fetch(this.url + '/' + subPath, fetchOptions) return fetch(this.url + '/' + subPath, fetchOptions)
@ -84,18 +78,14 @@ export default class CouchObjectProvider {
}); });
} }
/** // Check the response to a create/update/delete request;
* Check the response to a create/update/delete request; // track the rev if it's valid, otherwise return false to
* track the rev if it's valid, otherwise return false to // indicate that the request failed.
* indicate that the request failed. // persist any queued objects
* persist any queued objects checkResponse(response, intermediateResponse) {
* @private
*/
checkResponse(response, intermediateResponse, key) {
let requestSuccess = false; let requestSuccess = false;
const id = response ? response.id : undefined; const id = response ? response.id : undefined;
let rev; let rev;
if (response && response.ok) { if (response && response.ok) {
rev = response.rev; rev = response.rev;
requestSuccess = true; requestSuccess = true;
@ -113,14 +103,9 @@ export default class CouchObjectProvider {
if (this.objectQueue[id].hasNext()) { if (this.objectQueue[id].hasNext()) {
this.updateQueued(id); this.updateQueued(id);
} }
} else {
this.objectQueue[key].pending = false;
} }
} }
/**
* @private
*/
getModel(response) { getModel(response) {
if (response && response.model) { if (response && response.model) {
let key = response[ID]; let key = response[ID];
@ -134,7 +119,8 @@ export default class CouchObjectProvider {
} }
//Sometimes CouchDB returns the old rev which fetching the object if there is a document update in progress //Sometimes CouchDB returns the old rev which fetching the object if there is a document update in progress
if (!this.objectQueue[key].pending) { //Only update the rev if it's the first time we're getting the object from CouchDB. Subsequent revs should only be updated by updates.
if (!this.objectQueue[key].pending && !this.objectQueue[key].rev) {
this.objectQueue[key].updateRevision(response[REV]); this.objectQueue[key].updateRevision(response[REV]);
} }
@ -145,118 +131,10 @@ export default class CouchObjectProvider {
} }
get(identifier, abortSignal) { get(identifier, abortSignal) {
this.batchIds.push(identifier.key); return this.request(identifier.key, "GET", undefined, abortSignal).then(this.getModel.bind(this));
if (this.bulkPromise === undefined) {
this.bulkPromise = this.deferBatchedGet(abortSignal);
}
return this.bulkPromise
.then((domainObjectMap) => {
return domainObjectMap[identifier.key];
});
} }
/** async getObjectsByFilter(filter) {
* @private
*/
deferBatchedGet(abortSignal) {
// We until the next event loop cycle to "collect" all of the get
// requests triggered in this iteration of the event loop
return this.waitOneEventCycle().then(() => {
let batchIds = this.batchIds;
this.clearBatch();
if (batchIds.length === 1) {
let objectKey = batchIds[0];
//If there's only one request, just do a regular get
return this.request(objectKey, "GET", undefined, abortSignal)
.then(this.returnAsMap(objectKey));
} else {
return this.bulkGet(batchIds, abortSignal);
}
});
}
/**
* @private
*/
returnAsMap(objectKey) {
return (result) => {
let objectMap = {};
objectMap[objectKey] = this.getModel(result);
return objectMap;
};
}
/**
* @private
*/
clearBatch() {
this.batchIds = [];
delete this.bulkPromise;
}
/**
* @private
*/
waitOneEventCycle() {
return new Promise((resolve) => {
setTimeout(resolve);
});
}
/**
* @private
*/
bulkGet(ids, signal) {
ids = this.removeDuplicates(ids);
const query = {
'keys': ids
};
return this.request(ALL_DOCS, 'POST', query, signal).then((response) => {
if (response && response.rows !== undefined) {
return response.rows.reduce((map, row) => {
if (row.doc !== undefined) {
map[row.key] = this.getModel(row.doc);
}
return map;
}, {});
} else {
return {};
}
});
}
/**
* @private
*/
removeDuplicates(array) {
return Array.from(new Set(array));
}
search(query, abortSignal) {
const filter = {
"selector": {
"model": {
"name": {
"$regex": `(?i)${query}`
}
}
}
};
return this.getObjectsByFilter(filter, abortSignal);
}
async getObjectsByFilter(filter, abortSignal) {
let objects = []; let objects = [];
let url = `${this.url}/_find`; let url = `${this.url}/_find`;
@ -271,7 +149,6 @@ export default class CouchObjectProvider {
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json"
}, },
signal: abortSignal,
body body
}); });
@ -326,9 +203,6 @@ export default class CouchObjectProvider {
}; };
} }
/**
* @private
*/
abortGetChanges() { abortGetChanges() {
if (this.controller) { if (this.controller) {
this.controller.abort(); this.controller.abort();
@ -338,9 +212,6 @@ export default class CouchObjectProvider {
return true; return true;
} }
/**
* @private
*/
async observeObjectChanges(filter) { async observeObjectChanges(filter) {
let intermediateResponse = this.getIntermediateResponse(); let intermediateResponse = this.getIntermediateResponse();
@ -421,9 +292,6 @@ export default class CouchObjectProvider {
} }
/**
* @private
*/
getIntermediateResponse() { getIntermediateResponse() {
let intermediateResponse = {}; let intermediateResponse = {};
intermediateResponse.promise = new Promise(function (resolve, reject) { intermediateResponse.promise = new Promise(function (resolve, reject) {
@ -434,9 +302,6 @@ export default class CouchObjectProvider {
return intermediateResponse; return intermediateResponse;
} }
/**
* @private
*/
enqueueObject(key, model, intermediateResponse) { enqueueObject(key, model, intermediateResponse) {
if (this.objectQueue[key]) { if (this.objectQueue[key]) {
this.objectQueue[key].enqueue({ this.objectQueue[key].enqueue({
@ -459,22 +324,19 @@ export default class CouchObjectProvider {
const queued = this.objectQueue[key].dequeue(); const queued = this.objectQueue[key].dequeue();
let document = new CouchDocument(key, queued.model); let document = new CouchDocument(key, queued.model);
this.request(key, "PUT", document).then((response) => { this.request(key, "PUT", document).then((response) => {
this.checkResponse(response, queued.intermediateResponse, key); this.checkResponse(response, queued.intermediateResponse);
}); });
return intermediateResponse.promise; return intermediateResponse.promise;
} }
/**
* @private
*/
updateQueued(key) { updateQueued(key) {
if (!this.objectQueue[key].pending) { if (!this.objectQueue[key].pending) {
this.objectQueue[key].pending = true; this.objectQueue[key].pending = true;
const queued = this.objectQueue[key].dequeue(); const queued = this.objectQueue[key].dequeue();
let document = new CouchDocument(key, queued.model, this.objectQueue[key].rev); let document = new CouchDocument(key, queued.model, this.objectQueue[key].rev);
this.request(key, "PUT", document).then((response) => { this.request(key, "PUT", document).then((response) => {
this.checkResponse(response, queued.intermediateResponse, key); this.checkResponse(response, queued.intermediateResponse);
}); });
} }
} }

View File

@ -24,6 +24,7 @@ import {
createOpenMct, createOpenMct,
resetApplicationState, spyOnBuiltins resetApplicationState, spyOnBuiltins
} from 'utils/testing'; } from 'utils/testing';
import CouchObjectProvider from './CouchObjectProvider';
describe('the plugin', () => { describe('the plugin', () => {
let openmct; let openmct;
@ -41,8 +42,7 @@ describe('the plugin', () => {
namespace: '', namespace: '',
key: 'some-value' key: 'some-value'
}, },
type: 'mock-type', type: 'mock-type'
modified: 0
}; };
options = { options = {
url: testPath, url: testPath,
@ -95,7 +95,6 @@ describe('the plugin', () => {
return { return {
ok: true, ok: true,
_id: 'some-value', _id: 'some-value',
id: 'some-value',
_rev: 1, _rev: 1,
model: {} model: {}
}; };
@ -105,131 +104,44 @@ describe('the plugin', () => {
}); });
it('gets an object', () => { it('gets an object', () => {
return openmct.objects.get(mockDomainObject.identifier).then((result) => { openmct.objects.get(mockDomainObject.identifier).then((result) => {
expect(result.identifier.key).toEqual(mockDomainObject.identifier.key); expect(result.identifier.key).toEqual(mockDomainObject.identifier.key);
}); });
}); });
it('creates an object', () => { it('creates an object', () => {
return openmct.objects.save(mockDomainObject).then((result) => { openmct.objects.save(mockDomainObject).then((result) => {
expect(provider.create).toHaveBeenCalled(); expect(provider.create).toHaveBeenCalled();
expect(result).toBeTrue(); expect(result).toBeTrue();
}); });
}); });
it('updates an object', (done) => { it('updates an object', () => {
return openmct.objects.save(mockDomainObject).then((result) => { openmct.objects.save(mockDomainObject).then((result) => {
expect(result).toBeTrue(); expect(result).toBeTrue();
expect(provider.create).toHaveBeenCalled(); expect(provider.create).toHaveBeenCalled();
openmct.objects.save(mockDomainObject).then((updatedResult) => {
//Set modified timestamp it detects a change and persists the updated model.
mockDomainObject.modified = Date.now();
return openmct.objects.save(mockDomainObject).then((updatedResult) => {
expect(updatedResult).toBeTrue(); expect(updatedResult).toBeTrue();
expect(provider.update).toHaveBeenCalled(); expect(provider.update).toHaveBeenCalled();
done();
}); });
}); });
}); });
});
describe('batches requests', () => { it('updates queued objects', () => {
let mockPromise; let couchProvider = new CouchObjectProvider(openmct, options, '');
beforeEach(() => { let intermediateResponse = couchProvider.getIntermediateResponse();
mockPromise = Promise.resolve({ spyOn(couchProvider, 'updateQueued');
json: () => { couchProvider.enqueueObject(mockDomainObject.identifier.key, mockDomainObject, intermediateResponse);
return { couchProvider.objectQueue[mockDomainObject.identifier.key].updateRevision(1);
total_rows: 0, couchProvider.update(mockDomainObject);
rows: [] expect(couchProvider.objectQueue[mockDomainObject.identifier.key].hasNext()).toBe(2);
}; couchProvider.checkResponse({
} ok: true,
}); rev: 2,
fetch.and.returnValue(mockPromise); id: mockDomainObject.identifier.key
}, intermediateResponse);
expect(couchProvider.updateQueued).toHaveBeenCalledTimes(2);
}); });
it('for multiple simultaneous gets', () => {
const objectIds = [
{
namespace: '',
key: 'object-1'
}, {
namespace: '',
key: 'object-2'
}, {
namespace: '',
key: 'object-3'
}
];
const getAllObjects = Promise.all(
objectIds.map((identifier) =>
openmct.objects.get(identifier)
));
return getAllObjects.then(() => {
const requestUrl = fetch.calls.mostRecent().args[0];
const requestMethod = fetch.calls.mostRecent().args[1].method;
expect(fetch).toHaveBeenCalledTimes(1);
expect(requestUrl.includes('_all_docs')).toBeTrue();
expect(requestMethod).toEqual('POST');
});
});
it('but not for single gets', () => {
const objectId = {
namespace: '',
key: 'object-1'
};
const getObject = openmct.objects.get(objectId);
return getObject.then(() => {
const requestUrl = fetch.calls.mostRecent().args[0];
const requestMethod = fetch.calls.mostRecent().args[1].method;
expect(fetch).toHaveBeenCalledTimes(1);
expect(requestUrl.endsWith(`${objectId.key}`)).toBeTrue();
expect(requestMethod).toEqual('GET');
});
});
});
describe('implements server-side search', () => {
let mockPromise;
beforeEach(() => {
mockPromise = Promise.resolve({
body: {
getReader() {
return {
read() {
return Promise.resolve({
done: true,
value: undefined
});
}
};
}
}
});
fetch.and.returnValue(mockPromise);
});
it("using Couch's 'find' endpoint", () => {
return Promise.all(openmct.objects.search('test')).then(() => {
const requestUrl = fetch.calls.mostRecent().args[0];
expect(fetch).toHaveBeenCalled();
expect(requestUrl.endsWith('_find')).toBeTrue();
});
});
it("and supports search by object name", () => {
return Promise.all(openmct.objects.search('test')).then(() => {
const requestPayload = JSON.parse(fetch.calls.mostRecent().args[1].body);
expect(requestPayload).toBeDefined();
expect(requestPayload.selector.model.name.$regex).toEqual('(?i)test');
});
});
}); });
}); });

View File

@ -1,46 +0,0 @@
<template>
<div class="plot-series-limit-label"
:style="styleObj"
:class="limit.cssClass"
>
<span class="plot-series-limit-value">{{ limit.value }}</span>
<span class="plot-series-color-swatch"
:style="{ 'background-color': limit.color }"
></span>
<span class="plot-series-name">{{ limit.name }}</span>
</div>
</template>
<script>
export default {
props: {
limit: {
type: Object,
required: true,
default() {
return {};
}
},
point: {
type: Object,
required: true,
default() {
return {};
}
}
},
computed: {
styleObj() {
const top = `${this.point.top - 10}px`;
const left = `${this.point.left + 5}px`;
return {
'position': 'absolute',
'top': top,
'left': left,
'color': '#fff'
};
}
}
};
</script>

View File

@ -1,44 +0,0 @@
<template>
<hr :style="styleObj"
:class="cssWithoutUprLwr"
>
</template>
<script>
export default {
props: {
point: {
type: Object,
required: true,
default() {
return {};
}
},
cssClass: {
type: String,
default() {
return '';
}
}
},
computed: {
styleObj() {
const top = `${this.point.top}px`;
const left = `${this.point.left}px`;
return {
'position': 'absolute',
'width': '100%',
'top': top,
'left': left
};
},
cssWithoutUprLwr() {
let cssClass = this.cssClass.replace(/is-limit--upr/gi, 'is-limit--line');
cssClass = cssClass.replace(/is-limit--lwr/gi, 'is-limit--line');
return cssClass;
}
}
};
</script>

View File

@ -1,105 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2021, 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.
*****************************************************************************/
import eventHelpers from '../lib/eventHelpers';
export default class MCTChartAlarmLineSet {
constructor(series, chart, offset, bounds) {
this.series = series;
this.chart = chart;
this.offset = offset;
this.bounds = bounds;
this.limits = [];
eventHelpers.extend(this);
this.listenTo(series, 'limitBounds', this.updateBounds, this);
this.listenTo(series, 'change:xKey', this.getLimitPoints, this);
if (series.limits) {
this.getLimitPoints(series);
}
}
updateBounds(bounds) {
this.bounds = bounds;
this.getLimitPoints(this.series);
}
color() {
return this.series.get('color');
}
name() {
return this.series.get('name');
}
makePoint(point, series) {
if (!this.offset.xVal) {
this.chart.setOffset(point, undefined, series);
}
return {
x: this.offset.xVal(point, series),
y: this.offset.yVal(point, series)
};
}
getLimitPoints(series) {
this.limits = [];
let xKey = series.get('xKey');
Object.keys(series.limits).forEach((key) => {
const limitForLevel = series.limits[key];
if (limitForLevel.high) {
const point = this.makePoint(Object.assign({ [xKey]: this.bounds.start }, limitForLevel.high), series);
this.limits.push({
seriesKey: series.keyString,
value: series.getYVal(limitForLevel.high),
color: this.color().asHexString(),
name: this.name(),
point,
cssClass: limitForLevel.high.cssClass
});
}
if (limitForLevel.low) {
const point = this.makePoint(Object.assign({ [xKey]: this.bounds.start }, limitForLevel.low), series);
this.limits.push({
seriesKey: series.keyString,
value: series.getYVal(limitForLevel.low),
color: this.color().asHexString(),
name: this.name(),
point,
cssClass: limitForLevel.low.cssClass
});
}
}, this);
}
reset() {
this.limits = [];
}
destroy() {
this.stopListening();
}
}

View File

@ -1,64 +0,0 @@
<!--
Open MCT, Copyright (c) 2014-2020, 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.
-->
<template>
<div>
<div v-if="canEdit">
<plot-options-edit />
</div>
<div v-else>
<plot-options-browse />
</div>
</div>
</template>
<script>
import PlotOptionsBrowse from "./PlotOptionsBrowse.vue";
import PlotOptionsEdit from "./PlotOptionsEdit.vue";
export default {
components: {
PlotOptionsBrowse,
PlotOptionsEdit
},
inject: ['openmct', 'domainObject'],
data() {
return {
isEditing: this.openmct.editor.isEditing()
};
},
computed: {
canEdit() {
return this.isEditing && !this.domainObject.locked;
}
},
mounted() {
this.openmct.editor.on('isEditing', this.setEditState);
},
beforeDestroy() {
this.openmct.editor.off('isEditing', this.setEditState);
},
methods: {
setEditState(isEditing) {
this.isEditing = isEditing;
}
}
};
</script>

Some files were not shown because too many files have changed in this diff Show More