Compare commits

..

60 Commits

Author SHA1 Message Date
Jamie Vigliotta
7a0a67070d merging master 2021-06-02 15:27:38 -07:00
Jamie Vigliotta
d36db3b958 if no item in tree and scroll path set, need to remove it as well 2021-06-02 15:18:52 -07:00
Jamie Vigliotta
5f4aa88556 checking if item is in tree, before scrolling 2021-06-02 15:17:00 -07:00
Jamie Vigliotta
73451700f2 removing comp listeners when itme is closed 2021-06-02 15:14:31 -07:00
Nikhil
a40867d544 Revert "upgrade to webpack5 (#3871)" (#3907)
This reverts commit e1e0eeac56.
2021-06-02 07:04:40 -07:00
Charles Hacskaylo
dbed9262c0 Tree revert refactor (#3886)
* Reverts object tree to expand/collapse tree model
* Retains expanded/collapsed state 
* Adds collapse all feature
2021-06-01 15:47:21 -07:00
Henry Hsu
43ac66233e [Issue] remove FollowTimerAction, followIndicator and tests (#3855)
Co-authored-by: Henry Hsu <henryhsu@Henrys-MacBook-Air.local>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-05-28 16:06:09 -07:00
Andrew Henry
04e85c176a Update templates (#3904) 2021-05-28 14:55:36 -07:00
Shefali Joshi
8274c23129 Plots limit lines (#3882) 2021-05-28 14:51:29 -07:00
John Hill
5fafde5f23 Updates to recognize issue template config (#3902)
* issue template config

* Update PR Template

Co-authored-by: unlikelyzero <jchill2@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-05-28 14:16:56 -07:00
Charles Hacskaylo
80a6e7f719 Fix erroneously checked in dev code (#3897)
Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2021-05-28 13:22:11 -07:00
Shefali Joshi
2c13aeecce Make legend configuration properties reactive (#3889)
Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
2021-05-28 13:13:57 -07:00
Shefali Joshi
ac015c3e45 Updates to the sprint cycle and end of sprint release process (#3878)
* Updates to the sprint cycle and end of sprint release process

* Adds reviewer checklist item for labelling an issue as a bug

* Update Cycle doc with test process changes.

* Update static.md references, add Issue templates

* Updated criticality definition

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: John Hill <jchill2.spam@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-05-28 12:24:14 -07:00
Shefali Joshi
ae1a4bcc6a A change in the parent container's size for plots re-requests telemetry (#3890)
* Use resize obeserver to detect a change in the parent container's size for plots and re-request telemetry
2021-05-28 11:44:56 -07:00
Nikhil
e1e0eeac56 upgrade to webpack5 (#3871)
Upgrade to webpack 5
Changes dependencies to work with webpack 5 as well.
2021-05-27 15:16:03 -07:00
Jamie Vigliotta
5610ef9921 fixing search 2021-05-27 12:35:23 -07:00
Nikhil
c90dfb2a1f Fix the browser back button in Open MCT (#3526)
Fixes Open MCT back button.

Co-authored-by: Joshi <simplyrender@gmail.com>
2021-05-26 17:00:36 -07:00
Jamie Vigliotta
1b46b0d032 updating when no search reults text shows and hides 2021-05-26 15:06:40 -07:00
Jamie Vigliotta
048805418f updating when no search reults text shows and hides 2021-05-26 14:46:56 -07:00
Jamie Vigliotta
0e7704d628 updating when to stop checking if need to scroll 2021-05-26 14:25:15 -07:00
Jamie Vigliotta
76614f8507 remove debug code 2021-05-26 13:57:03 -07:00
Jamie Vigliotta
77460961ce modified scroll to handling to be more consistent, updated how open and scroll is handled 2021-05-26 13:40:30 -07:00
Jamie Vigliotta
ec83426de2 addressing PR comments, add computed styles, cleaned up some code, clarified some things 2021-05-25 12:39:26 -07:00
Jamie Vigliotta
3e23a0b044 Merge branch 'tree-revert-refactor' of https://github.com/nasa/openmct into tree-revert-refactor
Merge'n master
2021-05-24 12:14:30 -07:00
Jamie V
b96b099551 Merge branch 'master' into tree-revert-refactor 2021-05-24 11:45:35 -07:00
Shefali Joshi
1dfa5e5b8c Prepare snapshot for sprint 1.7.3 (#3877)
Co-authored-by: John Hill <jchill2.spam@gmail.com>
2021-05-24 10:54:51 -07:00
Jamie Vigliotta
31c2dbd273 removing unused abortSignal for now 2021-05-24 10:39:42 -07:00
Jamie V
8009944f1d Merge branch 'master' into tree-revert-refactor 2021-05-24 10:32:22 -07:00
Jamie Vigliotta
4748bb1e21 clearing search results before debounce method call to prevent prev results from showing 2021-05-24 10:27:21 -07:00
charlesh88
43a1d60a3f Styling for treeview
- Added and applied new icon glyph `icon-folders-collapse`;
2021-05-21 19:25:29 -07:00
charlesh88
06d7486d2a Styling for treeview
- Cleaned up CSS code;
- Refined margins in tree item;
- Increased hit area of expand/collapse arrow;
- Better contrast for tree hovers in Espresso theme;
- Fixed grid view in mobile mode;
- TODO: better icon for "collapse all" folders button;
2021-05-21 16:50:59 -07:00
Artur Carvalho
99896b72ea Small typo (#3838)
Looks like there is a small typo: `this this object`.

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
2021-05-21 15:57:53 -07:00
Charles Hacskaylo
979ba77c8e Normalize "OK" to uppercase in all dialogs; (#3850)
Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
2021-05-21 15:38:29 -07:00
Jamie Vigliotta
a2a9f99538 some (hopefully, but most likely not) final tweaks 2021-05-20 14:54:42 -07:00
Jamie Vigliotta
261ea7b543 dev testing 2021-05-20 13:20:57 -07:00
Jamie Vigliotta
4330b54f0f dev testing 2021-05-20 12:20:25 -07:00
Jamie Vigliotta
212adfc3c1 dev testing 2021-05-20 11:30:46 -07:00
Shefali Joshi
aebb5df611 Check that mutation happens only if model has changed (#3751)
* When a mutation is requested, the LegacyObjectAPIInterceptor triggers a second mutatation request - ensure that the model for this 2nd request has some diff from the current model before saving the object.

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-05-20 10:01:14 -07:00
Jamie Vigliotta
9e371f29d2 dev testing 2021-05-20 09:45:38 -07:00
Shefali Joshi
605eeff9d7 Plots - remove legacy code (#3814)
* Remove Lecacy plot code
* Adds tests for plots inspector
* Set range max and min to undefined instead of 0
2021-05-20 09:08:50 -07:00
Jamie Vigliotta
5d8bef6ef8 dev testing 2021-05-19 18:06:08 -07:00
Jamie Vigliotta
52f70a0489 dev testing 2021-05-19 17:41:48 -07:00
Jamie Vigliotta
d48951c340 dev testing 2021-05-19 17:31:51 -07:00
Jamie Vigliotta
bd2d6d9ea5 dev testing 2021-05-19 16:27:49 -07:00
Jamie Vigliotta
8e735379f9 dev testing 2021-05-19 15:33:33 -07:00
Jamie Vigliotta
b9c8404d9d dev testing 2021-05-19 15:19:32 -07:00
Jamie Vigliotta
0b71f97b3b dev testing 2021-05-19 14:59:35 -07:00
Jamie Vigliotta
dc14858253 track loading, prevent dupe loading, abort loading 2021-05-19 14:31:46 -07:00
Jamie Vigliotta
938a127e46 wip 2021-05-18 17:09:24 -07:00
Charles Hacskaylo
a83ee1f90f Allow context click on Imagery to invoke browser-level Save As... (#3857)
- `pointer-events: none` added to `c-compass` wrapper (which was
blocking the image from catching mouse events), and
`pointer-events: all` added to `c-direction-rose` element;
- Unit tested locally in main view and both types of layouts;
- Fixed indention spacing in file;
2021-05-18 14:42:18 -07:00
Jamie Vigliotta
21ec2e46fe adding loading tracking per item for visual feedback 2021-05-17 12:38:35 -07:00
Jamie Vigliotta
170927748b Merge branch 'master' into tree-revert-refactor
Merge'n master
2021-05-17 11:27:25 -07:00
Charles Hacskaylo
fe899cbcc8 New Time Conductor time input for real-time (#3409)
* Time Conductor Real Time input popup

Co-authored-by: Jamie Vigliotta <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-05-12 11:54:10 -07:00
Shefali Joshi
633bac2ed5 Sync time conductor with plots time range (#3843)
* Adds play and pause functionality for plots (not for legacy plots)

* Add time conductor sync gesture to actions. Also fix status css.

Co-authored-by: charlesh88 <charles.f.hacskaylo@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2021-05-12 10:57:54 -07:00
Shefali Joshi
dacec48aec Update condition sets in fixed timespan mode if the datum's timestamp is valid (#3852) 2021-05-11 19:25:56 -07:00
Andrew Henry
3ca133c782 Pass objectPath as property to LAD rows (#3870)
Includes some code cleanup
2021-05-11 18:58:17 -07:00
Jamie Vigliotta
648c8a13c4 fixed scroll to issues 2021-05-07 15:08:43 -07:00
Jamie Vigliotta
bd09b1a2cb handling sync and scroll more efficiently, adding to open empty objects, cleaning up code a bit 2021-05-05 12:45:59 -07:00
Jamie Vigliotta
4eb5fe3978 WIP 2021-04-30 14:26:56 -07:00
Jamie Vigliotta
776baa159a WIP: modifying new tree back to old tree, but keepin the good stuff 2021-04-29 17:23:32 -07:00
209 changed files with 2764 additions and 9119 deletions

43
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,43 @@
<!--- 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 -->

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

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

View File

@@ -0,0 +1,23 @@
<!--- 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. -->

12
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,12 @@
### 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?

2
API.md
View File

@@ -423,7 +423,7 @@ attribute | type | flags | notes
###### Value Hints
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.
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.
Known hints:

View File

@@ -10,7 +10,7 @@ accept changes from external contributors.
The short version:
1. Write your contribution.
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)
2. Make sure your contribution meets code, test, and commit message
standards as described below.
3. Submit a pull request from a topic branch back to `master`. Include a check
@@ -18,6 +18,7 @@ The short version:
for review.)
4. Respond to any discussion. When the reviewer decides it's ready, they
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
@@ -115,7 +116,7 @@ the pull request containing the reviewer checklist (from below) and complete
the merge back to the master branch.
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.
* 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 __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__.
* 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.
@@ -296,23 +297,12 @@ these standards.
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):
* _Trivial_: Minimal impact on the usefulness and functionality of the
software; a "nice-to-have."
* _(Unspecified)_: Major loss of functionality or impairment of use.
* _Critical_: Large-scale loss of functionality or impairment of use,
such that remaining utility becomes marginal.
* _Blocker_: Harmful or otherwise unacceptable behavior. Must fix.
* _Trivial_: Minimal impact on the usefulness and functionality of the software; a "nice-to-have." Visual impact without functional impact,
* _Medium_: Some impairment of use, but simple workarounds exist
* _Critical_: Significant loss of functionality or impairment of use. Display of telemetry data is not affected though.
* _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.
## Check Lists
@@ -322,16 +312,19 @@ checklist).
### Author Checklist
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?
[Within PR Template](.github/PULL_REQUEST_TEMPLATE.md)
### Reviewer Checklist
1. Changes appear to address issue?
2. Appropriate unit tests included?
3. Code style and in-line documentation are appropriate?
4. Commit messages meet standards?
5. Has associated issue been labelled `unverified`? (only applicable if this PR closes the issue)
* [ ] Changes appear to address issue?
* [ ] Appropriate unit tests included?
* [ ] Code style and in-line documentation are appropriate?
* [ ] Commit messages meet standards?
* [ ] 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.
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), 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/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).
## 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
for the subsequent sprint.
| Week | Mon | Tue | Wed | Thu | Fri |
|:-----:|:-------------------------:|:------:|:---:|:----------------------------:|:-----------:|
| __1__ | Sprint plan | Tag-up | | | |
| __2__ | | Tag-up | | | Code freeze |
| __3__ | Per-sprint testing | Triage | | _Per-sprint testing*_ | Ship |
| Week | Mon | Tue | Wed | Thu | Fri |
|:-----:|:-------------------------:|:------:|:---:|:----------------------------:|:-------------------------------------:|
| __1__ | Sprint plan | Tag-up | | | |
| __2__ | | Tag-up | | | Code freeze and sprint branch |
| __3__ | Per-sprint testing | Triage | | _Per-sprint testing*_ | Ship and merge sprint branch to master|
&ast; If necessary.
@@ -105,14 +105,20 @@ emphasis on testing.
that team may begin work for that sprint during the
third week, since testing and blocker resolution is unlikely
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.
May amend plan for sprint as-needed.
* __Code freeze.__ Any new work from this sprint
(features, bug fixes, enhancements) must be integrated by the
end of the second week of the sprint. After code freeze
(and until the end of the sprint) the only changes that should be
merged into the master branch should directly address issues
needed to pass acceptance testing.
end of the second week of the sprint. After code freeze, a sprint
branch will be created (and until the end of the sprint) the only
changes that should be merged into the sprint branch should
directly address issues 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)
Structured testing with predefined
success criteria. No release should ship without passing
@@ -126,8 +132,8 @@ emphasis on testing.
* [__Testathon.__](testing/plan.md#user-testing)
Multi-user testing, involving as many users as
is feasible, plus development team. Open-ended; should verify
completed work from this sprint, test exploratorily for
regressions, et cetera.
completed work from this sprint using the sprint branch, test
exploratorily for regressions, et cetera.
* [__Long-Duration Test.__](testing/plan.md#long-duration-testing) A
test to verify that the software remains
stable after running for longer durations. May include some
@@ -143,7 +149,7 @@ emphasis on testing.
Subset of Pre-release Testing
which should be performed before shipping at the end of any
sprint. Time is allocated for a second round of
Pre-release Testing if the first round is not passed.
Pre-release Testing if the first round is not passed. Smoke tests collected from issues/PRs
* __Triage.__ Team reviews issues from acceptance testing and uses
success criteria to determine whether or not they should block
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
of interest. Verifies that the software runs and that basic functionality
is present.
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.
### Unit Testing
@@ -49,7 +49,7 @@ User testing will focus on the following activities:
* General "trying to break things."
During user testing, users will
[report issues](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#issue-reporting)
[report issues](https://github.com/nasa/openmct/issues/new/choose)
as they are encountered.
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?
Any defects or unexpected behavior identified during testing should be
[reported as issues](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#issue-reporting)
[reported as issues](https://github.com/nasa/openmct/issues/new/choose)
and reviewed for severity.
## Test Performance

View File

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

View File

@@ -93,5 +93,36 @@ 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;
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,56 +0,0 @@
/*****************************************************************************
* 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

@@ -1,51 +0,0 @@
/*****************************************************************************
* 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

@@ -1,89 +0,0 @@
/*****************************************************************************
* 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

@@ -1,96 +0,0 @@
/*****************************************************************************
* 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

@@ -252,7 +252,7 @@ define([
this.status = new api.StatusAPI(this);
this.router = new ApplicationRouter();
this.router = new ApplicationRouter(this);
this.branding = BrandingAPI.default;

View File

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

View File

@@ -161,6 +161,22 @@ define([
evaluate: function (datum, property) {
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

@@ -24,13 +24,6 @@ define([
return false;
}
//TODO: Remove this when plots Angular implementation is deprecated
let parent = selection[0].length > 1 && selection[0][1].context.item;
if (parent && parent.type === 'time-strip') {
return (selectionContext.item.type === typeDefinition.key)
&& (typeDefinition.key !== 'telemetry.plot.overlay');
}
return selectionContext.item.type === typeDefinition.key;
},
view: function (selection) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -486,6 +486,12 @@ 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.
*

View File

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

View File

@@ -504,6 +504,26 @@ define([
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.
* Limit Evaluators help you evaluate limit and alarm status of individual
@@ -531,5 +551,42 @@ define([
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;
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,306 +25,118 @@ import {
} from 'utils/testing';
describe("The URLTimeSettingsSynchronizer", () => {
let appHolder;
let openmct;
let testClock;
let resolveFunction;
let oldHash;
beforeEach((done) => {
openmct = createOpenMct();
openmct.install(openmct.plugins.MyItems());
openmct.install(openmct.plugins.LocalTimeSystem());
testClock = jasmine.createSpyObj("testClock", ["start", "stop", "tick", "currentValue", "on", "off"]);
testClock.key = "test-clock";
testClock.currentValue.and.returnValue(0);
openmct.time.addClock(testClock);
openmct.install(openmct.plugins.UTCTimeSystem());
openmct.on('start', done);
openmct.startHeadless();
appHolder = document.createElement("div");
openmct.start(appHolder);
});
afterEach(() => resetApplicationState(openmct));
afterEach(() => {
openmct.time.stopClock();
openmct.router.removeListener('change:hash', resolveFunction);
describe("realtime mode", () => {
it("when the clock is set via the time API, it is immediately reflected in the URL", () => {
//Test expected initial conditions
appHolder = undefined;
openmct = undefined;
resolveFunction = undefined;
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);
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', {
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,
end: 200
});
expect(window.location.hash.includes('tc.startDelta=2000')).toBe(true);
expect(window.location.hash.includes('tc.endDelta=200')).toBe(true);
//Test that expected initial conditions are no longer true
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
});
describe("when set in the url", () => {
it("will change from fixed to realtime mode when the mode changes", () => {
expectLocationToBeInFixedMode();
const hasStartDelta = window.location.hash.includes('tc.startDelta=2000');
const hasEndDelta = window.location.hash.includes('tc.endDelta=200');
const hasLocalClock = window.location.hash.includes('tc.mode=local');
success = hasStartDelta && hasEndDelta && hasLocalClock;
if (success) {
expect(success).toBe(true);
return switchToRealtimeMode().then(() => {
let clock = openmct.time.clock();
openmct.router.removeListener('change:hash', resolveFunction);
done();
}
};
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);
});
});
});
openmct.router.on('change:hash', resolveFunction);
});
function setRealtimeLocationParameters() {
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');
it("when the clock mode is set to local, it is reflected in the URL", (done) => {
let success;
window.location.hash = hash;
}
resolveFunction = () => {
let hash = window.location.hash;
hash = hash.replace('tc.mode=fixed', 'tc.mode=local');
window.location.hash = hash;
function setFixedLocationParameters() {
let hash = window.location.hash.toString()
.replace('tc.mode=local', 'tc.mode=fixed')
.replace('tc.timeSystem=utc', 'tc.timeSystem=local')
.replace('tc.startDelta=1000', 'tc.startBound=50')
.replace('tc.endDelta=100', 'tc.endBound=60');
success = window.location.hash.includes('tc.mode=local');
if (success) {
expect(success).toBe(true);
done();
}
};
window.location.hash = hash;
}
openmct.router.on('change:hash', resolveFunction);
});
function switchToRealtimeMode() {
let resolveFunction;
it("when the clock mode is set to local, it is reflected in the URL", (done) => {
let success;
return new Promise((resolve) => {
resolveFunction = resolve;
openmct.time.on('clock', resolveFunction);
setRealtimeLocationParameters();
}).then(() => {
openmct.time.off('clock', resolveFunction);
});
}
resolveFunction = () => {
let hash = window.location.hash;
function switchToFixedMode() {
let resolveFunction;
hash = hash.replace('tc.mode=fixed', 'tc.mode=local');
window.location.hash = hash;
success = window.location.hash.includes('tc.mode=local');
if (success) {
expect(success).toBe(true);
done();
}
};
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);
});
}
openmct.router.on('change:hash', resolveFunction);
});
function expectLocationToBeInRealtimeMode() {
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);
}
it("reset hash", (done) => {
let success;
function expectLocationToBeInFixedMode() {
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
expect(window.location.hash.includes('tc.mode=local')).toBe(false);
}
window.location.hash = oldHash;
resolveFunction = () => {
success = window.location.hash === oldHash;
if (success) {
expect(success).toBe(true);
done();
}
};
openmct.router.on('change:hash', resolveFunction);
});
});

View File

@@ -272,11 +272,11 @@ export default class Condition extends EventEmitter {
}
}
requestLADConditionResult() {
requestLADConditionResult(options) {
let latestTimestamp;
let criteriaResults = {};
const criteriaRequests = this.criteria
.map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects));
.map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects, options));
return Promise.all(criteriaRequests)
.then(results => {

View File

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

View File

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

View File

@@ -35,6 +35,7 @@ export default class StyleRuleManager extends EventEmitter {
if (styleConfiguration) {
this.initialize(styleConfiguration);
if (styleConfiguration.conditionSetIdentifier) {
this.openmct.time.on("bounds", this.refreshData.bind(this));
this.subscribeToConditionSet();
} else {
this.applyStaticStyle();
@@ -83,6 +84,25 @@ 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) {
if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) {
this.initialize(styleConfiguration || {});
@@ -160,10 +180,14 @@ export default class StyleRuleManager extends EventEmitter {
destroy() {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
delete this.stopProvidingTelemetry;
}
this.openmct.time.off("bounds", this.refreshData);
this.openmct.editor.off('isEditing', this.toggleSubscription);
this.conditionSetIdentifier = undefined;
}

View File

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

View File

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

View File

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

View File

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

View File

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

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?`,
buttons: [
{
label: 'Ok',
label: 'OK',
emphasis: 'true',
callback: function () {
removeItem(getAllTypes(selection));

View File

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

View File

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

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?`,
buttons: [
{
label: 'Ok',
label: 'OK',
emphasis: 'true',
callback: function () {
deleteFrameAction(primary.context.frameId);
@@ -162,7 +162,7 @@ function ToolbarProvider(openmct) {
message: 'This action will permanently delete this container from this Flexible Layout',
buttons: [
{
label: 'Ok',
label: 'OK',
emphasis: 'true',
callback: function () {
removeContainer(containerId);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ $elemBg: rgba(black, 0.7);
}
.c-compass {
pointer-events: none; // This allows the image element to receive a browser-level context click
position: absolute;
left: 50%;
top: 50%;
@@ -242,6 +243,7 @@ $elemBg: rgba(black, 0.7);
left: 0;
clip-path: circle(50% at 50% 50%);
border-radius: 100%;
pointer-events: all;
svg, div {
position: absolute;

View File

@@ -390,7 +390,9 @@ export default {
delete this.unsubscribe;
}
this.imageContainerResizeObserver.disconnect();
if (this.imageContainerResizeObserver) {
this.imageContainerResizeObserver.disconnect();
}
if (this.relatedTelemetry.hasRelatedTelemetry) {
this.relatedTelemetry.destroy();
@@ -702,7 +704,7 @@ export default {
window.clearInterval(this.durationTracker);
},
updateDuration() {
let currentTime = this.openmct.time.clock().currentValue();
let currentTime = this.openmct.time.clock() && this.openmct.time.clock().currentValue();
this.numericDuration = currentTime - this.parsedSelectedTime;
},
resetAgeCSS() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -91,7 +91,7 @@ export default class CouchObjectProvider {
* persist any queued objects
* @private
*/
checkResponse(response, intermediateResponse) {
checkResponse(response, intermediateResponse, key) {
let requestSuccess = false;
const id = response ? response.id : undefined;
let rev;
@@ -113,6 +113,8 @@ export default class CouchObjectProvider {
if (this.objectQueue[id].hasNext()) {
this.updateQueued(id);
}
} else {
this.objectQueue[key].pending = false;
}
}
@@ -132,8 +134,7 @@ export default class CouchObjectProvider {
}
//Sometimes CouchDB returns the old rev which fetching the object if there is a document update in progress
//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) {
if (!this.objectQueue[key].pending) {
this.objectQueue[key].updateRevision(response[REV]);
}
@@ -458,7 +459,7 @@ export default class CouchObjectProvider {
const queued = this.objectQueue[key].dequeue();
let document = new CouchDocument(key, queued.model);
this.request(key, "PUT", document).then((response) => {
this.checkResponse(response, queued.intermediateResponse);
this.checkResponse(response, queued.intermediateResponse, key);
});
return intermediateResponse.promise;
@@ -473,7 +474,7 @@ export default class CouchObjectProvider {
const queued = this.objectQueue[key].dequeue();
let document = new CouchDocument(key, queued.model, this.objectQueue[key].rev);
this.request(key, "PUT", document).then((response) => {
this.checkResponse(response, queued.intermediateResponse);
this.checkResponse(response, queued.intermediateResponse, key);
});
}
}

View File

@@ -117,7 +117,7 @@ describe('the plugin', () => {
});
});
it('updates an object', () => {
it('updates an object', (done) => {
return openmct.objects.save(mockDomainObject).then((result) => {
expect(result).toBeTrue();
expect(provider.create).toHaveBeenCalled();
@@ -128,6 +128,7 @@ describe('the plugin', () => {
return openmct.objects.save(mockDomainObject).then((updatedResult) => {
expect(updatedResult).toBeTrue();
expect(provider.update).toHaveBeenCalled();
done();
});
});
});

View File

@@ -28,6 +28,7 @@
:series="config.series.models"
:highlights="highlights"
:legend="config.legend"
@legendHoverChanged="legendHoverChanged"
/>
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
<y-axis v-if="config.series.models.length > 0"
@@ -45,14 +46,9 @@
<div class="gl-plot-display-area has-local-controls has-cursor-guides">
<div class="l-state-indicators">
<span v-if="plotHistory.length"
class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
<span class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
title="This plot is not currently displaying the latest data. Reset pan/zoom to view latest data."
></span>
<span v-else
class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
title="This plot is not currently displaying the latest data. Resume to view latest data."
></span>
</div>
<mct-ticks v-show="gridLines && !options.compact"
@@ -70,9 +66,9 @@
<div ref="chartContainer"
class="gl-plot-chart-wrapper"
>
<mct-chart :series-config="config"
:rectangles="rectangles"
<mct-chart :rectangles="rectangles"
:highlights="highlights"
:show-limit-line-labels="showLimitLineLabels"
@plotReinitializeCanvas="initCanvas"
/>
</div>
@@ -120,6 +116,15 @@
>
</button>
</div>
<div v-if="isTimeOutOfSync || isFrozen"
class="c-button-set c-button-set--strip-h"
>
<button class="c-button icon-clock"
title="Synchronize Time Conductor"
@click="showSynchronizeDialog()"
>
</button>
</div>
</div>
<!--Cursor guides-->
@@ -156,6 +161,7 @@ import MctTicks from "./MctTicks.vue";
import MctChart from "./chart/MctChart.vue";
import XAxis from "./axis/XAxis.vue";
import YAxis from "./axis/YAxis.vue";
import _ from "lodash";
export default {
components: {
@@ -208,7 +214,9 @@ export default {
config: {},
pending: 0,
isRealTime: this.openmct.time.clock() !== undefined,
loaded: false
loaded: false,
isTimeOutOfSync: false,
showLimitLineLabels: undefined
};
},
computed: {
@@ -458,6 +466,7 @@ export default {
if (typeof value !== 'undefined') {
this._synchronized = value;
this.isTimeOutOfSync = value !== true;
const isUnsynced = isLocalClock && !value;
this.setStatus(isUnsynced);
@@ -468,6 +477,7 @@ export default {
setStatus(isNotInSync) {
const outOfSync = isNotInSync === true
|| this.isTimeOutOfSync === true
|| this.isFrozen === true;
if (outOfSync === true) {
this.openmct.status.set(this.domainObject.identifier, 'timeconductor-unsynced');
@@ -490,6 +500,10 @@ export default {
},
initialize() {
_.debounce(this.handleWindowResize, 400);
this.plotContainerResizeObserver = new ResizeObserver(this.handleWindowResize);
this.plotContainerResizeObserver.observe(this.$parent.$refs.plotWrapper);
// Setup canvas etc.
this.xScale = new LinearScale(this.config.xAxis.get('displayRange'));
this.yScale = new LinearScale(this.config.yAxis.get('displayRange'));
@@ -931,6 +945,51 @@ export default {
this.clear();
},
showSynchronizeDialog() {
const isLocalClock = this.openmct.time.clock();
if (isLocalClock !== undefined) {
const message = `
This action will change the Time Conductor to Fixed Timespan mode with this plot view's current time bounds.
Do you want to continue?
`;
let dialog = this.openmct.overlays.dialog({
title: 'Synchronize Time Conductor',
iconClass: 'alert',
size: 'fit',
message: message,
buttons: [
{
label: 'OK',
callback: () => {
dialog.dismiss();
this.synchronizeTimeConductor();
}
},
{
label: 'Cancel',
callback: () => {
dialog.dismiss();
}
}
]
});
} else {
this.openmct.notifications.alert('Time conductor bounds have changed.');
this.synchronizeTimeConductor();
}
},
synchronizeTimeConductor() {
this.openmct.time.stopClock();
const range = this.config.xAxis.get('displayRange');
this.openmct.time.bounds({
start: range.min,
end: range.max
});
this.isTimeOutOfSync = false;
},
destroy() {
configStore.deleteStore(this.config.id);
@@ -948,12 +1007,23 @@ export default {
this.removeStatusListener();
}
this.plotContainerResizeObserver.disconnect();
this.openmct.time.off('clock', this.updateRealTime);
this.openmct.time.off('bounds', this.updateDisplayBounds);
this.openmct.objectViews.off('clearData', this.clearData);
},
updateStatus(status) {
this.$emit('statusUpdated', status);
},
handleWindowResize() {
if (this.offsetWidth !== this.$parent.$refs.plotWrapper.offsetWidth) {
this.offsetWidth = this.$parent.$refs.plotWrapper.offsetWidth;
this.config.series.models.forEach(this.loadSeriesData, this);
}
},
legendHoverChanged(data) {
this.showLimitLineLabels = data;
}
}
};

View File

@@ -228,15 +228,16 @@ export default {
doTickUpdate() {
if (this.shouldCheckWidth) {
const tickElements = this.$refs.tickContainer.querySelectorAll('.gl-plot-tick > span');
const tickElements = this.$refs.tickContainer && this.$refs.tickContainer.querySelectorAll('.gl-plot-tick > span');
if (tickElements) {
const tickWidth = Number([].reduce.call(tickElements, function (memo, first) {
return Math.max(memo, first.offsetWidth);
}, 0));
const tickWidth = Number([].reduce.call(tickElements, function (memo, first) {
return Math.max(memo, first.offsetWidth);
}, 0));
this.tickWidth = tickWidth;
this.$emit('plotTickWidth', tickWidth);
this.shouldCheckWidth = false;
this.tickWidth = tickWidth;
this.$emit('plotTickWidth', tickWidth);
this.shouldCheckWidth = false;
}
}
this.tickUpdate = false;

View File

@@ -48,7 +48,7 @@ export default function PlotViewProvider(openmct) {
name: 'Plot',
cssClass: 'icon-telemetry',
canView(domainObject, objectPath) {
return isCompactView(objectPath) && hasTelemetry(domainObject, openmct);
return hasTelemetry(domainObject, openmct);
},
view: function (domainObject, objectPath) {

View File

@@ -0,0 +1,46 @@
<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

@@ -0,0 +1,44 @@
<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

@@ -0,0 +1,105 @@
/*****************************************************************************
* 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

@@ -23,6 +23,9 @@
<div class="gl-plot-chart-area">
<span v-html="canvasTemplate"></span>
<span v-html="canvasTemplate"></span>
<div ref="limitArea"
class="js-limit-area"
></div>
</div>
</template>
@@ -34,8 +37,12 @@ import MCTChartLineLinear from './MCTChartLineLinear';
import MCTChartLineStepAfter from './MCTChartLineStepAfter';
import MCTChartPointSet from './MCTChartPointSet';
import MCTChartAlarmPointSet from './MCTChartAlarmPointSet';
import MCTChartAlarmLineSet from "./MCTChartAlarmLineSet";
import configStore from "../configuration/configStore";
import PlotConfigurationModel from "../configuration/PlotConfigurationModel";
import LimitLine from "./LimitLine.vue";
import LimitLabel from "./LimitLabel.vue";
import Vue from 'vue';
const MARKER_SIZE = 6.0;
const HIGHLIGHT_SIZE = MARKER_SIZE * 2.0;
@@ -54,6 +61,12 @@ export default {
default() {
return [];
}
},
showLimitLineLabels: {
type: Object,
default() {
return {};
}
}
},
data() {
@@ -67,18 +80,22 @@ export default {
},
rectangles() {
this.scheduleDraw();
},
showLimitLineLabels() {
this.drawLimitLines();
}
},
mounted() {
eventHelpers.extend(this);
this.config = this.getConfig();
this.isDestroyed = false;
this.lines = [];
this.limitLines = [];
this.pointSets = [];
this.alarmSets = [];
this.offset = {};
this.seriesElements = new WeakMap();
this.seriesLimits = new WeakMap();
let canvasEls = this.$parent.$refs.chartContainer.querySelectorAll("canvas");
const mainCanvas = canvasEls[1];
@@ -90,8 +107,8 @@ export default {
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
this.listenTo(this.config.yAxis, 'change:key', this.clearOffset, this);
this.listenTo(this.config.yAxis, 'change', this.scheduleDraw);
this.listenTo(this.config.xAxis, 'change', this.scheduleDraw);
this.listenTo(this.config.yAxis, 'change', this.updateLimitsAndDraw);
this.listenTo(this.config.xAxis, 'change', this.updateLimitsAndDraw);
this.config.series.forEach(this.onSeriesAdd, this);
},
beforeDestroy() {
@@ -116,15 +133,18 @@ export default {
this.changeInterpolate(mode, o, series);
this.changeMarkers(mode, o, series);
this.changeAlarmMarkers(mode, o, series);
this.changeLimitLines(mode, o, series);
},
onSeriesAdd(series) {
this.listenTo(series, 'change:xKey', this.reDraw, this);
this.listenTo(series, 'change:interpolate', this.changeInterpolate, this);
this.listenTo(series, 'change:markers', this.changeMarkers, this);
this.listenTo(series, 'change:alarmMarkers', this.changeAlarmMarkers, this);
this.listenTo(series, 'change:limitLines', this.changeLimitLines, this);
this.listenTo(series, 'change', this.scheduleDraw);
this.listenTo(series, 'add', this.scheduleDraw);
this.makeChartElement(series);
this.makeLimitLines(series);
},
changeInterpolate(mode, o, series) {
if (mode === o) {
@@ -178,6 +198,14 @@ export default {
this.pointSets.push(pointSet);
}
},
changeLimitLines(mode, o, series) {
if (mode === o) {
return;
}
this.makeLimitLines(series);
this.updateLimitsAndDraw();
},
onSeriesRemove(series) {
this.stopListening(series);
this.removeChartElement(series);
@@ -187,6 +215,7 @@ export default {
this.isDestroyed = true;
this.stopListening();
this.lines.forEach(line => line.destroy());
this.limitLines.forEach(line => line.destroy());
DrawLoader.releaseDrawAPI(this.drawAPI);
},
clearOffset() {
@@ -199,6 +228,9 @@ export default {
this.lines.forEach(function (line) {
line.reset();
});
this.limitLines.forEach(function (line) {
line.reset();
});
this.pointSets.forEach(function (pointSet) {
pointSet.reset();
});
@@ -269,6 +301,8 @@ export default {
}
this.seriesElements.delete(series);
this.clearLimitLines(series);
},
lineForSeries(series) {
if (series.get('interpolate') === 'linear') {
@@ -287,6 +321,14 @@ export default {
);
}
},
limitLineForSeries(series) {
return new MCTChartAlarmLineSet(
series,
this,
this.offset,
this.openmct.time.bounds()
);
},
pointSetForSeries(series) {
if (series.get('markers')) {
return new MCTChartPointSet(
@@ -308,7 +350,8 @@ export default {
makeChartElement(series) {
const elements = {
lines: [],
pointSets: []
pointSets: [],
limitLines: []
};
const line = this.lineForSeries(series);
@@ -330,6 +373,37 @@ export default {
this.seriesElements.set(series, elements);
},
makeLimitLines(series) {
this.clearLimitLines(series);
if (!series.get('limitLines')) {
return;
}
const limitElements = {
limitLines: []
};
const limitLine = this.limitLineForSeries(series);
if (limitLine) {
limitElements.limitLines.push(limitLine);
this.limitLines.push(limitLine);
}
this.seriesLimits.set(series, limitElements);
},
clearLimitLines(series) {
const seriesLimits = this.seriesLimits.get(series);
if (seriesLimits) {
seriesLimits.limitLines.forEach(function (line) {
this.limitLines.splice(this.limitLines.indexOf(line), 1);
line.destroy();
}, this);
this.seriesLimits.delete(series);
}
},
canDraw() {
if (!this.offset.x || !this.offset.y) {
return false;
@@ -337,6 +411,10 @@ export default {
return true;
},
updateLimitsAndDraw() {
this.drawLimitLines();
this.scheduleDraw();
},
scheduleDraw() {
if (!this.drawScheduled) {
requestAnimationFrame(this.draw);
@@ -385,6 +463,68 @@ export default {
this.pointSets.forEach(this.drawPoints, this);
this.alarmSets.forEach(this.drawAlarmPoints, this);
},
drawLimitLines() {
if (this.canDraw()) {
this.updateViewport();
if (!this.drawAPI.origin) {
return;
}
Array.from(this.$refs.limitArea.children).forEach((el) => el.remove());
this.limitLines.forEach((limitLine) => {
let limitContainerEl = this.$refs.limitArea;
limitLine.limits.forEach((limit) => {
const showLabels = this.showLabels(limit.seriesKey);
if (showLabels) {
let limitLabelEl = this.getLimitLabel(limit);
limitContainerEl.appendChild(limitLabelEl);
}
let limitEl = this.getLimitElement(limit);
limitContainerEl.appendChild(limitEl);
}, this);
});
}
},
showLabels(seriesKey) {
return this.showLimitLineLabels.seriesKey
&& (this.showLimitLineLabels.seriesKey === seriesKey);
},
getLimitElement(limit) {
let point = {
left: 0,
top: this.drawAPI.y(limit.point.y)
};
let LimitLineClass = Vue.extend(LimitLine);
const component = new LimitLineClass({
propsData: {
point,
cssClass: limit.cssClass
}
});
component.$mount();
return component.$el;
},
getLimitLabel(limit) {
let point = {
left: 0,
top: this.drawAPI.y(limit.point.y)
};
let LimitLabelClass = Vue.extend(LimitLabel);
const component = new LimitLabelClass({
propsData: {
limit,
point
}
});
component.$mount();
return component.$el;
},
drawAlarmPoints(alarmSet) {
this.drawAPI.drawLimitPoints(
alarmSet.points,
@@ -401,11 +541,12 @@ export default {
chartElement.series.get('markerShape')
);
},
drawLine(chartElement) {
drawLine(chartElement, disconnected) {
this.drawAPI.drawLine(
chartElement.getBuffer(),
chartElement.color().asRGBAArray(),
chartElement.count
chartElement.count,
disconnected
);
},
drawHighlights() {

View File

@@ -97,7 +97,8 @@ export default class PlotSeries extends Model {
markers: true,
markerShape: 'point',
markerSize: 2.0,
alarmMarkers: true
alarmMarkers: true,
limitLines: false
};
}
@@ -115,9 +116,16 @@ export default class PlotSeries extends Model {
this.domainObject = options.domainObject;
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject);
this.limitDefinition = this.openmct.telemetry.limitDefinition(options.domainObject);
this.limits = this.limitDefinition.limits();
this.openmct.time.on('bounds', this.updateLimits);
this.on('destroy', this.onDestroy, this);
}
updateLimits(bounds) {
this.emit('limitBounds', bounds);
}
locateOldObject(oldStyleParent) {
return oldStyleParent.useCapability('composition')
.then(function (children) {

View File

@@ -166,7 +166,6 @@ export default class YAxisModel extends Model {
* Update yAxis format, values, and label from known series.
*/
updateFromSeries(series) {
this.unset('displayRange');
const plotModel = this.plot.get('domainObject');
const label = _.get(plotModel, 'configuration.yAxis.label');
const sampleSeries = series.first();

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