mirror of
https://github.com/nasa/openmct.git
synced 2025-06-26 11:09:22 +00:00
Compare commits
39 Commits
remove-ang
...
version-1.
Author | SHA1 | Date | |
---|---|---|---|
cfd7f2201a | |||
a123889d6a | |||
97aebbf458 | |||
a40867d544 | |||
dbed9262c0 | |||
43ac66233e | |||
04e85c176a | |||
8274c23129 | |||
5fafde5f23 | |||
80a6e7f719 | |||
2c13aeecce | |||
ac015c3e45 | |||
ae1a4bcc6a | |||
e1e0eeac56 | |||
c90dfb2a1f | |||
1dfa5e5b8c | |||
99896b72ea | |||
979ba77c8e | |||
aebb5df611 | |||
605eeff9d7 | |||
a83ee1f90f | |||
fe899cbcc8 | |||
633bac2ed5 | |||
dacec48aec | |||
3ca133c782 | |||
12416b8079 | |||
9920e67c83 | |||
0e80a5b8a0 | |||
05f9202fe4 | |||
0da35a44b0 | |||
2305cd2e49 | |||
b30b6bc94e | |||
564f254652 | |||
9fa71244ea | |||
8157cdc7e9 | |||
721bdd737a | |||
1b57999059 | |||
b7460cef41 | |||
46f7f6dd04 |
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
23
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal 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
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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?
|
3
API.md
3
API.md
@ -423,13 +423,14 @@ attribute | type | flags | notes
|
|||||||
|
|
||||||
###### Value Hints
|
###### 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:
|
Known hints:
|
||||||
|
|
||||||
* `domain`: Values with a `domain` hint will be used for the x-axis of a plot, and tables will render columns for these values first.
|
* `domain`: Values with a `domain` hint will be used for the x-axis of a plot, and tables will render columns for these values first.
|
||||||
* `range`: Values with a `range` hint will be used as the y-axis on a plot, and tables will render columns for these values after the `domain` values.
|
* `range`: Values with a `range` hint will be used as the y-axis on a plot, and tables will render columns for these values after the `domain` values.
|
||||||
* `image`: Indicates that the value may be interpreted as the URL to an image file, in which case appropriate views will be made available.
|
* `image`: Indicates that the value may be interpreted as the URL to an image file, in which case appropriate views will be made available.
|
||||||
|
* `imageDownloadName`: Indicates that the value may be interpreted as the name of the image file.
|
||||||
|
|
||||||
##### The Time Conductor and Telemetry
|
##### The Time Conductor and Telemetry
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ accept changes from external contributors.
|
|||||||
|
|
||||||
The short version:
|
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
|
2. Make sure your contribution meets code, test, and commit message
|
||||||
standards as described below.
|
standards as described below.
|
||||||
3. Submit a pull request from a topic branch back to `master`. Include a check
|
3. Submit a pull request from a topic branch back to `master`. Include a check
|
||||||
@ -18,6 +18,7 @@ The short version:
|
|||||||
for review.)
|
for review.)
|
||||||
4. Respond to any discussion. When the reviewer decides it's ready, they
|
4. Respond to any discussion. When the reviewer decides it's ready, they
|
||||||
will merge back `master` and fill out their own check list.
|
will merge back `master` and fill out their own check list.
|
||||||
|
5. If you are a first-time contributor, please see [this discussion](https://github.com/nasa/openmct/discussions/3821) for further information.
|
||||||
|
|
||||||
## Contribution Process
|
## Contribution Process
|
||||||
|
|
||||||
@ -115,7 +116,7 @@ the pull request containing the reviewer checklist (from below) and complete
|
|||||||
the merge back to the master branch.
|
the merge back to the master branch.
|
||||||
|
|
||||||
Additionally:
|
Additionally:
|
||||||
* Every pull request must link to the issue that it addresses. Eg. “Addresses #1234” or “Closes #1234”. This is the responsibility of the pull request’s __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 request’s __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.
|
* 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 request’s __author__.
|
* A pull request that closes an issue should say so in the description. Including the text “Closes #1234” will cause the linked issue to be automatically closed when the pull request is merged. This is the responsibility of the pull request’s __author__.
|
||||||
* When a pull request is merged, and the corresponding issue closed, the __reviewer__ must add the tag “unverified” to the original issue. This will indicate that although the issue is closed, it has not been tested yet.
|
* When a pull request is merged, and the corresponding issue closed, the __reviewer__ must add the tag “unverified” to the original issue. This will indicate that although the issue is closed, it has not been tested yet.
|
||||||
@ -296,23 +297,12 @@ these standards.
|
|||||||
|
|
||||||
Issues are tracked at https://github.com/nasa/openmct/issues.
|
Issues are tracked at https://github.com/nasa/openmct/issues.
|
||||||
|
|
||||||
Issues should include:
|
|
||||||
|
|
||||||
* A short description of the issue encountered.
|
|
||||||
* A longer-form description of the issue encountered. When possible, steps to
|
|
||||||
reproduce the issue.
|
|
||||||
* When possible, a description of the impact of the issue. What use case does
|
|
||||||
it impede?
|
|
||||||
* An assessment of the severity of the issue.
|
|
||||||
|
|
||||||
Issue severity is categorized as follows (in ascending order):
|
Issue severity is categorized as follows (in ascending order):
|
||||||
|
|
||||||
* _Trivial_: Minimal impact on the usefulness and functionality of the
|
* _Trivial_: Minimal impact on the usefulness and functionality of the software; a "nice-to-have." Visual impact without functional impact,
|
||||||
software; a "nice-to-have."
|
* _Medium_: Some impairment of use, but simple workarounds exist
|
||||||
* _(Unspecified)_: Major loss of functionality or impairment of use.
|
* _Critical_: Significant loss of functionality or impairment of use. Display of telemetry data is not affected though.
|
||||||
* _Critical_: Large-scale loss of functionality or impairment of use,
|
* _Blocker_: Major functionality is impaired or lost, threatening mission success. Display of telemetry data is impaired or blocked by the bug, which could lead to loss of situational awareness.
|
||||||
such that remaining utility becomes marginal.
|
|
||||||
* _Blocker_: Harmful or otherwise unacceptable behavior. Must fix.
|
|
||||||
|
|
||||||
## Check Lists
|
## Check Lists
|
||||||
|
|
||||||
@ -322,16 +312,19 @@ checklist).
|
|||||||
|
|
||||||
### Author Checklist
|
### Author Checklist
|
||||||
|
|
||||||
1. Changes address original issue?
|
[Within PR Template](.github/PULL_REQUEST_TEMPLATE.md)
|
||||||
2. Unit tests included and/or updated with changes?
|
|
||||||
3. Command line build passes?
|
|
||||||
4. Changes have been smoke-tested?
|
|
||||||
5. Testing instructions included?
|
|
||||||
|
|
||||||
### Reviewer Checklist
|
### Reviewer Checklist
|
||||||
|
|
||||||
1. Changes appear to address issue?
|
* [ ] Changes appear to address issue?
|
||||||
2. Appropriate unit tests included?
|
* [ ] Appropriate unit tests included?
|
||||||
3. Code style and in-line documentation are appropriate?
|
* [ ] Code style and in-line documentation are appropriate?
|
||||||
4. Commit messages meet standards?
|
* [ ] Commit messages meet standards?
|
||||||
5. Has associated issue been labelled `unverified`? (only applicable if this PR closes the issue)
|
* [ ] 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.```
|
@ -44,7 +44,7 @@ The clearest examples for developing Open MCT plugins are in the
|
|||||||
our documentation.
|
our documentation.
|
||||||
|
|
||||||
We want Open MCT to be as easy to use, install, run, and develop for as
|
We want Open MCT to be as easy to use, install, run, and develop for as
|
||||||
possible, and your feedback will help us get there! Feedback can be provided via [GitHub issues](https://github.com/nasa/openmct/issues), 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
|
## Building Applications With Open MCT
|
||||||
|
|
||||||
|
@ -74,10 +74,10 @@ resources not needed for this effort should be used to begin work
|
|||||||
for the subsequent sprint.
|
for the subsequent sprint.
|
||||||
|
|
||||||
| Week | Mon | Tue | Wed | Thu | Fri |
|
| Week | Mon | Tue | Wed | Thu | Fri |
|
||||||
|:-----:|:-------------------------:|:------:|:---:|:----------------------------:|:-----------:|
|
|:-----:|:-------------------------:|:------:|:---:|:----------------------------:|:-------------------------------------:|
|
||||||
| __1__ | Sprint plan | Tag-up | | | |
|
| __1__ | Sprint plan | Tag-up | | | |
|
||||||
| __2__ | | Tag-up | | | Code freeze |
|
| __2__ | | Tag-up | | | Code freeze and sprint branch |
|
||||||
| __3__ | Per-sprint testing | Triage | | _Per-sprint testing*_ | Ship |
|
| __3__ | Per-sprint testing | Triage | | _Per-sprint testing*_ | Ship and merge sprint branch to master|
|
||||||
|
|
||||||
* If necessary.
|
* If necessary.
|
||||||
|
|
||||||
@ -105,14 +105,20 @@ emphasis on testing.
|
|||||||
that team may begin work for that sprint during the
|
that team may begin work for that sprint during the
|
||||||
third week, since testing and blocker resolution is unlikely
|
third week, since testing and blocker resolution is unlikely
|
||||||
to require all available resources.
|
to require all available resources.
|
||||||
|
* Testing success criteria identified per issue (where necessary). This could be in the form of acceptance tests on the issue or detailing performance tests, for example.
|
||||||
* __Tag-up.__ Check in and status update among development team.
|
* __Tag-up.__ Check in and status update among development team.
|
||||||
May amend plan for sprint as-needed.
|
May amend plan for sprint as-needed.
|
||||||
* __Code freeze.__ Any new work from this sprint
|
* __Code freeze.__ Any new work from this sprint
|
||||||
(features, bug fixes, enhancements) must be integrated by the
|
(features, bug fixes, enhancements) must be integrated by the
|
||||||
end of the second week of the sprint. After code freeze
|
end of the second week of the sprint. After code freeze, a sprint
|
||||||
(and until the end of the sprint) the only changes that should be
|
branch will be created (and until the end of the sprint) the only
|
||||||
merged into the master branch should directly address issues
|
changes that should be merged into the sprint branch should
|
||||||
needed to pass acceptance testing.
|
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)
|
* [__Per-release Testing.__](testing/plan.md#per-release-testing)
|
||||||
Structured testing with predefined
|
Structured testing with predefined
|
||||||
success criteria. No release should ship without passing
|
success criteria. No release should ship without passing
|
||||||
@ -126,8 +132,8 @@ emphasis on testing.
|
|||||||
* [__Testathon.__](testing/plan.md#user-testing)
|
* [__Testathon.__](testing/plan.md#user-testing)
|
||||||
Multi-user testing, involving as many users as
|
Multi-user testing, involving as many users as
|
||||||
is feasible, plus development team. Open-ended; should verify
|
is feasible, plus development team. Open-ended; should verify
|
||||||
completed work from this sprint, test exploratorily for
|
completed work from this sprint using the sprint branch, test
|
||||||
regressions, et cetera.
|
exploratorily for regressions, et cetera.
|
||||||
* [__Long-Duration Test.__](testing/plan.md#long-duration-testing) A
|
* [__Long-Duration Test.__](testing/plan.md#long-duration-testing) A
|
||||||
test to verify that the software remains
|
test to verify that the software remains
|
||||||
stable after running for longer durations. May include some
|
stable after running for longer durations. May include some
|
||||||
@ -143,7 +149,7 @@ emphasis on testing.
|
|||||||
Subset of Pre-release Testing
|
Subset of Pre-release Testing
|
||||||
which should be performed before shipping at the end of any
|
which should be performed before shipping at the end of any
|
||||||
sprint. Time is allocated for a second round of
|
sprint. Time is allocated for a second round of
|
||||||
Pre-release Testing if the first round is not passed.
|
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
|
* __Triage.__ Team reviews issues from acceptance testing and uses
|
||||||
success criteria to determine whether or not they should block
|
success criteria to determine whether or not they should block
|
||||||
release, then formulates a plan to address these issues before
|
release, then formulates a plan to address these issues before
|
||||||
|
@ -19,7 +19,7 @@ Testing for Open MCT includes:
|
|||||||
|
|
||||||
Manual, non-rigorous testing of the software and/or specific features
|
Manual, non-rigorous testing of the software and/or specific features
|
||||||
of interest. Verifies that the software runs and that basic functionality
|
of interest. Verifies that the software runs and that basic functionality
|
||||||
is present.
|
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
|
### Unit Testing
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ User testing will focus on the following activities:
|
|||||||
* General "trying to break things."
|
* General "trying to break things."
|
||||||
|
|
||||||
During user testing, users will
|
During user testing, users will
|
||||||
[report issues](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#issue-reporting)
|
[report issues](https://github.com/nasa/openmct/issues/new/choose)
|
||||||
as they are encountered.
|
as they are encountered.
|
||||||
|
|
||||||
Desired outcomes of user testing are:
|
Desired outcomes of user testing are:
|
||||||
@ -71,7 +71,7 @@ usage. After twenty-four hours, the software is evaluated for:
|
|||||||
at the start of the test? Is it as responsive?
|
at the start of the test? Is it as responsive?
|
||||||
|
|
||||||
Any defects or unexpected behavior identified during testing should be
|
Any defects or unexpected behavior identified during testing should be
|
||||||
[reported as issues](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#issue-reporting)
|
[reported as issues](https://github.com/nasa/openmct/issues/new/choose)
|
||||||
and reviewed for severity.
|
and reviewed for severity.
|
||||||
|
|
||||||
## Test Performance
|
## Test Performance
|
||||||
|
@ -92,8 +92,8 @@ should update (or delegate the task of updating) Open MCT version
|
|||||||
numbers by the following process:
|
numbers by the following process:
|
||||||
|
|
||||||
1. Update version number in `package.json`
|
1. Update version number in `package.json`
|
||||||
1. Create a new branch off the `master` branch.
|
1. Checkout branch created for the last sprint that has been successfully tested.
|
||||||
2. Remove `-SNAPSHOT` suffix from the version in `package.json`.
|
2. Remove a `-SNAPSHOT` suffix from the version in `package.json`.
|
||||||
3. Verify that resulting version number meets semantic versioning
|
3. Verify that resulting version number meets semantic versioning
|
||||||
requirements relative to previous stable version. Increment the
|
requirements relative to previous stable version. Increment the
|
||||||
version number if necessary.
|
version number if necessary.
|
||||||
@ -132,6 +132,7 @@ numbers by the following process:
|
|||||||
4. Test the package before publishing by doing `npm publish --dry-run`
|
4. Test the package before publishing by doing `npm publish --dry-run`
|
||||||
if necessary.
|
if necessary.
|
||||||
5. Publish the package to the npmjs registry (e.g. `npm publish --access public`)
|
5. Publish the package to the npmjs registry (e.g. `npm publish --access public`)
|
||||||
|
NOTE: Use the `--tag unstable` flag to the npm publishj if this is a prerelease.
|
||||||
6. Confirm the package has been published (e.g. `https://www.npmjs.com/package/openmct`)
|
6. Confirm the package has been published (e.g. `https://www.npmjs.com/package/openmct`)
|
||||||
5. Update snapshot status in `package.json`
|
5. Update snapshot status in `package.json`
|
||||||
1. Create a new branch off the `master` branch.
|
1. Create a new branch off the `master` branch.
|
||||||
|
@ -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;
|
return SinewaveLimitProvider;
|
||||||
});
|
});
|
||||||
|
@ -50,11 +50,16 @@ define([
|
|||||||
const IMAGE_DELAY = 20000;
|
const IMAGE_DELAY = 20000;
|
||||||
|
|
||||||
function pointForTimestamp(timestamp, name) {
|
function pointForTimestamp(timestamp, name) {
|
||||||
|
const url = IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length];
|
||||||
|
const urlItems = url.split('/');
|
||||||
|
const imageDownloadName = `example.imagery.${urlItems[urlItems.length - 1]}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: name,
|
name,
|
||||||
utc: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
utc: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
||||||
local: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
local: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
||||||
url: IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length]
|
url,
|
||||||
|
imageDownloadName
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,6 +144,14 @@ define([
|
|||||||
hints: {
|
hints: {
|
||||||
image: 1
|
image: 1
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Image Download Name',
|
||||||
|
key: 'imageDownloadName',
|
||||||
|
format: 'imageDownloadName',
|
||||||
|
hints: {
|
||||||
|
imageDownloadName: 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
@ -88,7 +88,6 @@
|
|||||||
openmct.install(openmct.plugins.ExampleImagery());
|
openmct.install(openmct.plugins.ExampleImagery());
|
||||||
openmct.install(openmct.plugins.PlanLayout());
|
openmct.install(openmct.plugins.PlanLayout());
|
||||||
openmct.install(openmct.plugins.Timeline());
|
openmct.install(openmct.plugins.Timeline());
|
||||||
openmct.install(openmct.plugins.PlotVue());
|
|
||||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||||
openmct.install(openmct.plugins.AutoflowView({
|
openmct.install(openmct.plugins.AutoflowView({
|
||||||
type: "telemetry.panel"
|
type: "telemetry.panel"
|
||||||
|
@ -78,6 +78,7 @@ module.exports = (config) => {
|
|||||||
preserveDescribeNesting: true,
|
preserveDescribeNesting: true,
|
||||||
foldAll: false
|
foldAll: false
|
||||||
},
|
},
|
||||||
|
browserConsoleLogOptions: { level: "error", format: "%b %T: %m", terminal: true },
|
||||||
coverageIstanbulReporter: {
|
coverageIstanbulReporter: {
|
||||||
fixWebpackSourcePaths: true,
|
fixWebpackSourcePaths: true,
|
||||||
dir: process.env.CIRCLE_ARTIFACTS ?
|
dir: process.env.CIRCLE_ARTIFACTS ?
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "1.6.3-SNAPSHOT",
|
"version": "1.7.4-SNAPSHOT",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -78,7 +78,8 @@
|
|||||||
"zepto": "^1.2.0"
|
"zepto": "^1.2.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"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",
|
"start": "node app.js",
|
||||||
"lint": "eslint platform example src --ext .js,.vue openmct.js",
|
"lint": "eslint platform example src --ext .js,.vue openmct.js",
|
||||||
"lint:fix": "eslint platform example src --ext .js,.vue openmct.js --fix",
|
"lint:fix": "eslint platform example src --ext .js,.vue openmct.js --fix",
|
||||||
|
@ -86,7 +86,7 @@ define(
|
|||||||
})
|
})
|
||||||
.join('/');
|
.join('/');
|
||||||
|
|
||||||
window.location.href = url;
|
openmct.router.navigate(url);
|
||||||
|
|
||||||
if (isFirstViewEditable(object.useCapability('adapter'), objectPath)) {
|
if (isFirstViewEditable(object.useCapability('adapter'), objectPath)) {
|
||||||
openmct.editor.edit();
|
openmct.editor.edit();
|
||||||
|
@ -141,11 +141,17 @@ define(
|
|||||||
if (mutationResult !== false) {
|
if (mutationResult !== false) {
|
||||||
// Copy values if result was a different object
|
// Copy values if result was a different object
|
||||||
// (either our clone or some other new thing)
|
// (either our clone or some other new thing)
|
||||||
if (model !== result) {
|
let modelHasChanged = _.isEqual(model, result) === false;
|
||||||
|
if (modelHasChanged) {
|
||||||
copyValues(model, result);
|
copyValues(model, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (modelHasChanged
|
||||||
|
|| (useTimestamp !== undefined)
|
||||||
|
|| (model.modified === undefined)) {
|
||||||
model.modified = useTimestamp ? timestamp : now();
|
model.modified = useTimestamp ? timestamp : now();
|
||||||
|
}
|
||||||
|
|
||||||
notifyListeners(model);
|
notifyListeners(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,13 +23,11 @@
|
|||||||
define([
|
define([
|
||||||
"moment-timezone",
|
"moment-timezone",
|
||||||
"./src/indicators/ClockIndicator",
|
"./src/indicators/ClockIndicator",
|
||||||
"./src/indicators/FollowIndicator",
|
|
||||||
"./src/services/TickerService",
|
"./src/services/TickerService",
|
||||||
"./src/services/TimerService",
|
"./src/services/TimerService",
|
||||||
"./src/controllers/ClockController",
|
"./src/controllers/ClockController",
|
||||||
"./src/controllers/TimerController",
|
"./src/controllers/TimerController",
|
||||||
"./src/controllers/RefreshingController",
|
"./src/controllers/RefreshingController",
|
||||||
"./src/actions/FollowTimerAction",
|
|
||||||
"./src/actions/StartTimerAction",
|
"./src/actions/StartTimerAction",
|
||||||
"./src/actions/RestartTimerAction",
|
"./src/actions/RestartTimerAction",
|
||||||
"./src/actions/StopTimerAction",
|
"./src/actions/StopTimerAction",
|
||||||
@ -39,13 +37,11 @@ define([
|
|||||||
], function (
|
], function (
|
||||||
MomentTimezone,
|
MomentTimezone,
|
||||||
ClockIndicator,
|
ClockIndicator,
|
||||||
FollowIndicator,
|
|
||||||
TickerService,
|
TickerService,
|
||||||
TimerService,
|
TimerService,
|
||||||
ClockController,
|
ClockController,
|
||||||
TimerController,
|
TimerController,
|
||||||
RefreshingController,
|
RefreshingController,
|
||||||
FollowTimerAction,
|
|
||||||
StartTimerAction,
|
StartTimerAction,
|
||||||
RestartTimerAction,
|
RestartTimerAction,
|
||||||
StopTimerAction,
|
StopTimerAction,
|
||||||
@ -144,15 +140,6 @@ define([
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"actions": [
|
"actions": [
|
||||||
{
|
|
||||||
"key": "timer.follow",
|
|
||||||
"implementation": FollowTimerAction,
|
|
||||||
"depends": ["timerService"],
|
|
||||||
"category": "contextual",
|
|
||||||
"name": "Follow Timer",
|
|
||||||
"cssClass": "icon-clock",
|
|
||||||
"priority": "optional"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "timer.start",
|
"key": "timer.start",
|
||||||
"implementation": StartTimerAction,
|
"implementation": StartTimerAction,
|
||||||
@ -299,10 +286,7 @@ define([
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"runs": [{
|
"runs": [],
|
||||||
"implementation": FollowIndicator,
|
|
||||||
"depends": ["openmct", "timerService"]
|
|
||||||
}],
|
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
"name": "moment-duration-format",
|
"name": "moment-duration-format",
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
);
|
|
@ -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);
|
|
||||||
};
|
|
||||||
});
|
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -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);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -154,7 +154,9 @@ define(['zepto', 'objectUtils'], function ($, objectUtils) {
|
|||||||
tree = JSON.stringify(tree).replace(new RegExp(oldIdKeyString, 'g'), newIdKeyString);
|
tree = JSON.stringify(tree).replace(new RegExp(oldIdKeyString, 'g'), newIdKeyString);
|
||||||
|
|
||||||
return JSON.parse(tree, (key, value) => {
|
return JSON.parse(tree, (key, value) => {
|
||||||
if (Object.prototype.hasOwnProperty.call(value, 'key')
|
if (value !== undefined
|
||||||
|
&& value !== null
|
||||||
|
&& Object.prototype.hasOwnProperty.call(value, 'key')
|
||||||
&& Object.prototype.hasOwnProperty.call(value, 'namespace')
|
&& Object.prototype.hasOwnProperty.call(value, 'namespace')
|
||||||
&& value.key === oldId.key
|
&& value.key === oldId.key
|
||||||
&& value.namespace === oldId.namespace) {
|
&& value.namespace === oldId.namespace) {
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
# Couch DB Persistence Plugin
|
|
||||||
An adapter for using CouchDB for persistence of user-created objects. The plugin installation function takes the URL
|
|
||||||
for the CouchDB database as a parameter.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
```js
|
|
||||||
openmct.install(openmct.plugins.CouchDB('http://localhost:5984/openmct'))
|
|
||||||
```
|
|
@ -1,78 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define([
|
|
||||||
"./src/CouchPersistenceProvider",
|
|
||||||
"./src/CouchIndicator"
|
|
||||||
], function (
|
|
||||||
CouchPersistenceProvider,
|
|
||||||
CouchIndicator
|
|
||||||
) {
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: "platform/persistence/couch",
|
|
||||||
definition: {
|
|
||||||
"name": "Couch Persistence",
|
|
||||||
"description": "Adapter to read and write objects using a CouchDB instance.",
|
|
||||||
"extensions": {
|
|
||||||
"components": [
|
|
||||||
{
|
|
||||||
"provides": "persistenceService",
|
|
||||||
"type": "provider",
|
|
||||||
"implementation": CouchPersistenceProvider,
|
|
||||||
"depends": [
|
|
||||||
"$http",
|
|
||||||
"$q",
|
|
||||||
"PERSISTENCE_SPACE",
|
|
||||||
"COUCHDB_PATH"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"constants": [
|
|
||||||
{
|
|
||||||
"key": "PERSISTENCE_SPACE",
|
|
||||||
"value": "mct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "COUCHDB_PATH",
|
|
||||||
"value": "/couch/openmct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "COUCHDB_INDICATOR_INTERVAL",
|
|
||||||
"value": 15000
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"indicators": [
|
|
||||||
{
|
|
||||||
"implementation": CouchIndicator,
|
|
||||||
"depends": [
|
|
||||||
"$http",
|
|
||||||
"$interval",
|
|
||||||
"COUCHDB_PATH",
|
|
||||||
"COUCHDB_INDICATOR_INTERVAL"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
@ -1,61 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A CouchDocument describes domain object model in a format
|
|
||||||
* which is easily read-written to CouchDB. This includes
|
|
||||||
* Couch's _id and _rev fields, as well as a separate
|
|
||||||
* metadata field which contains a subset of information found
|
|
||||||
* in the model itself (to support search optimization with
|
|
||||||
* CouchDB views.)
|
|
||||||
* @memberof platform/persistence/couch
|
|
||||||
* @constructor
|
|
||||||
* @param {string} id the id under which to store this mode
|
|
||||||
* @param {object} model the model to store
|
|
||||||
* @param {string} rev the revision to include (or undefined,
|
|
||||||
* if no revision should be noted for couch)
|
|
||||||
* @param {boolean} whether or not to mark this document as
|
|
||||||
* deleted (see CouchDB docs for _deleted)
|
|
||||||
*/
|
|
||||||
function CouchDocument(id, model, rev, markDeleted) {
|
|
||||||
return {
|
|
||||||
"_id": id,
|
|
||||||
"_rev": rev,
|
|
||||||
"_deleted": markDeleted,
|
|
||||||
"metadata": {
|
|
||||||
"category": "domain object",
|
|
||||||
"type": model.type,
|
|
||||||
"owner": "admin",
|
|
||||||
"name": model.name,
|
|
||||||
"created": Date.now()
|
|
||||||
},
|
|
||||||
"model": model
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return CouchDocument;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,119 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
// Set of connection states; changing among these states will be
|
|
||||||
// reflected in the indicator's appearance.
|
|
||||||
// CONNECTED: Everything nominal, expect to be able to read/write.
|
|
||||||
// DISCONNECTED: HTTP failed; maybe misconfigured, disconnected.
|
|
||||||
// SEMICONNECTED: Connected to the database, but it reported an error.
|
|
||||||
// PENDING: Still trying to connect, and haven't failed yet.
|
|
||||||
var CONNECTED = {
|
|
||||||
text: "Connected",
|
|
||||||
glyphClass: "ok",
|
|
||||||
statusClass: "s-status-on",
|
|
||||||
description: "Connected to the domain object database."
|
|
||||||
},
|
|
||||||
DISCONNECTED = {
|
|
||||||
text: "Disconnected",
|
|
||||||
glyphClass: "err",
|
|
||||||
statusClass: "s-status-caution",
|
|
||||||
description: "Unable to connect to the domain object database."
|
|
||||||
},
|
|
||||||
SEMICONNECTED = {
|
|
||||||
text: "Unavailable",
|
|
||||||
glyphClass: "caution",
|
|
||||||
statusClass: "s-status-caution",
|
|
||||||
description: "Database does not exist or is unavailable."
|
|
||||||
},
|
|
||||||
PENDING = {
|
|
||||||
text: "Checking connection...",
|
|
||||||
statusClass: "s-status-caution"
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicator for the current CouchDB connection. Polls CouchDB
|
|
||||||
* at a regular interval (defined by bundle constants) to ensure
|
|
||||||
* that the database is available.
|
|
||||||
* @constructor
|
|
||||||
* @memberof platform/persistence/couch
|
|
||||||
* @implements {Indicator}
|
|
||||||
* @param $http Angular's $http service
|
|
||||||
* @param $interval Angular's $interval service
|
|
||||||
* @param {string} path the URL to poll to check for couch availability
|
|
||||||
* @param {number} interval the interval, in milliseconds, to poll at
|
|
||||||
*/
|
|
||||||
function CouchIndicator($http, $interval, path, interval) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// Track the current connection state
|
|
||||||
this.state = PENDING;
|
|
||||||
|
|
||||||
this.$http = $http;
|
|
||||||
this.$interval = $interval;
|
|
||||||
this.path = path;
|
|
||||||
this.interval = interval;
|
|
||||||
|
|
||||||
// Callback if the HTTP request to Couch fails
|
|
||||||
function handleError() {
|
|
||||||
self.state = DISCONNECTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback if the HTTP request succeeds. CouchDB may
|
|
||||||
// report an error, so check for that.
|
|
||||||
function handleResponse(response) {
|
|
||||||
var data = response.data;
|
|
||||||
self.state = data.error ? SEMICONNECTED : CONNECTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to connect to CouchDB, and update the indicator.
|
|
||||||
function updateIndicator() {
|
|
||||||
$http.get(path).then(handleResponse, handleError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the indicator initially, and start polling.
|
|
||||||
updateIndicator();
|
|
||||||
$interval(updateIndicator, interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
CouchIndicator.prototype.getCssClass = function () {
|
|
||||||
return "c-indicator--clickable icon-suitcase " + this.state.statusClass;
|
|
||||||
};
|
|
||||||
|
|
||||||
CouchIndicator.prototype.getGlyphClass = function () {
|
|
||||||
return this.state.glyphClass;
|
|
||||||
};
|
|
||||||
|
|
||||||
CouchIndicator.prototype.getText = function () {
|
|
||||||
return this.state.text;
|
|
||||||
};
|
|
||||||
|
|
||||||
CouchIndicator.prototype.getDescription = function () {
|
|
||||||
return this.state.description;
|
|
||||||
};
|
|
||||||
|
|
||||||
return CouchIndicator;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,145 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This bundle implements a persistence service which uses CouchDB to
|
|
||||||
* store documents.
|
|
||||||
* @namespace platform/persistence/cache
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
["./CouchDocument"],
|
|
||||||
function (CouchDocument) {
|
|
||||||
|
|
||||||
// JSLint doesn't like dangling _'s, but CouchDB uses these, so
|
|
||||||
// hide this behind variables.
|
|
||||||
var REV = "_rev",
|
|
||||||
ID = "_id";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The CouchPersistenceProvider reads and writes JSON documents
|
|
||||||
* (more specifically, domain object models) to/from a CouchDB
|
|
||||||
* instance.
|
|
||||||
* @memberof platform/persistence/couch
|
|
||||||
* @constructor
|
|
||||||
* @implements {PersistenceService}
|
|
||||||
* @param $http Angular's $http service
|
|
||||||
* @param $interval Angular's $interval service
|
|
||||||
* @param {string} space the name of the persistence space being served
|
|
||||||
* @param {string} path the path to the CouchDB instance
|
|
||||||
*/
|
|
||||||
function CouchPersistenceProvider($http, $q, space, path) {
|
|
||||||
this.spaces = [space];
|
|
||||||
this.revs = {};
|
|
||||||
this.$q = $q;
|
|
||||||
this.$http = $http;
|
|
||||||
this.path = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull out a list of document IDs from CouchDB's
|
|
||||||
// _all_docs response
|
|
||||||
function getIdsFromAllDocs(allDocs) {
|
|
||||||
return allDocs.rows.map(function (r) {
|
|
||||||
return r.id;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the response to a create/update/delete request;
|
|
||||||
// track the rev if it's valid, otherwise return false to
|
|
||||||
// indicate that the request failed.
|
|
||||||
CouchPersistenceProvider.prototype.checkResponse = function (response) {
|
|
||||||
if (response && response.ok) {
|
|
||||||
this.revs[response.id] = response.rev;
|
|
||||||
|
|
||||||
return response.ok;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get a domain object model out of CouchDB's response
|
|
||||||
CouchPersistenceProvider.prototype.getModel = function (response) {
|
|
||||||
if (response && response.model) {
|
|
||||||
this.revs[response[ID]] = response[REV];
|
|
||||||
|
|
||||||
return response.model;
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Issue a request using $http; get back the plain JS object
|
|
||||||
// from the expected JSON response
|
|
||||||
CouchPersistenceProvider.prototype.request = function (subpath, method, value) {
|
|
||||||
return this.$http({
|
|
||||||
method: method,
|
|
||||||
url: this.path + '/' + subpath,
|
|
||||||
data: value
|
|
||||||
}).then(function (response) {
|
|
||||||
return response.data;
|
|
||||||
}, function () {
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Shorthand methods for GET/PUT methods
|
|
||||||
CouchPersistenceProvider.prototype.get = function (subpath) {
|
|
||||||
return this.request(subpath, "GET");
|
|
||||||
};
|
|
||||||
|
|
||||||
CouchPersistenceProvider.prototype.put = function (subpath, value) {
|
|
||||||
return this.request(subpath, "PUT", value);
|
|
||||||
};
|
|
||||||
|
|
||||||
CouchPersistenceProvider.prototype.listSpaces = function () {
|
|
||||||
return this.$q.when(this.spaces);
|
|
||||||
};
|
|
||||||
|
|
||||||
CouchPersistenceProvider.prototype.listObjects = function () {
|
|
||||||
return this.get("_all_docs").then(getIdsFromAllDocs.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
CouchPersistenceProvider.prototype.createObject = function (space, key, value) {
|
|
||||||
return this.put(key, new CouchDocument(key, value))
|
|
||||||
.then(this.checkResponse.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
CouchPersistenceProvider.prototype.readObject = function (space, key) {
|
|
||||||
return this.get(key).then(this.getModel.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
CouchPersistenceProvider.prototype.updateObject = function (space, key, value) {
|
|
||||||
var rev = this.revs[key];
|
|
||||||
|
|
||||||
return this.put(key, new CouchDocument(key, value, rev))
|
|
||||||
.then(this.checkResponse.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
CouchPersistenceProvider.prototype.deleteObject = function (space, key, value) {
|
|
||||||
var rev = this.revs[key];
|
|
||||||
|
|
||||||
return this.put(key, new CouchDocument(key, value, rev, true))
|
|
||||||
.then(this.checkResponse.bind(this));
|
|
||||||
};
|
|
||||||
|
|
||||||
return CouchPersistenceProvider;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,63 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DomainObjectProviderSpec. Created by vwoeltje on 11/6/14.
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
["../src/CouchDocument"],
|
|
||||||
function (CouchDocument) {
|
|
||||||
|
|
||||||
// JSLint doesn't like dangling _'s, but CouchDB uses these, so
|
|
||||||
// hide this behind variables.
|
|
||||||
var REV = "_rev",
|
|
||||||
ID = "_id",
|
|
||||||
DELETED = "_deleted";
|
|
||||||
|
|
||||||
describe("A couch document", function () {
|
|
||||||
it("includes an id", function () {
|
|
||||||
expect(new CouchDocument("testId", {})[ID])
|
|
||||||
.toEqual("testId");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("includes a rev only when one is provided", function () {
|
|
||||||
expect(new CouchDocument("testId", {})[REV])
|
|
||||||
.not.toBeDefined();
|
|
||||||
expect(new CouchDocument("testId", {}, "testRev")[REV])
|
|
||||||
.toEqual("testRev");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("includes the provided model", function () {
|
|
||||||
var model = { someKey: "some value" };
|
|
||||||
expect(new CouchDocument("testId", model).model)
|
|
||||||
.toEqual(model);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("marks documents as deleted only on request", function () {
|
|
||||||
expect(new CouchDocument("testId", {}, "testRev")[DELETED])
|
|
||||||
.not.toBeDefined();
|
|
||||||
expect(new CouchDocument("testId", {}, "testRev", true)[DELETED])
|
|
||||||
.toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,129 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["../src/CouchIndicator"],
|
|
||||||
function (CouchIndicator) {
|
|
||||||
|
|
||||||
xdescribe("The CouchDB status indicator", function () {
|
|
||||||
var mockHttp,
|
|
||||||
mockInterval,
|
|
||||||
testPath,
|
|
||||||
testInterval,
|
|
||||||
mockPromise,
|
|
||||||
indicator;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockHttp = jasmine.createSpyObj("$http", ["get"]);
|
|
||||||
mockInterval = jasmine.createSpy("$interval");
|
|
||||||
mockPromise = jasmine.createSpyObj("promise", ["then"]);
|
|
||||||
testPath = "/test/path";
|
|
||||||
testInterval = 12321; // Some number
|
|
||||||
|
|
||||||
mockHttp.get.and.returnValue(mockPromise);
|
|
||||||
|
|
||||||
indicator = new CouchIndicator(
|
|
||||||
mockHttp,
|
|
||||||
mockInterval,
|
|
||||||
testPath,
|
|
||||||
testInterval
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("polls for changes", function () {
|
|
||||||
expect(mockInterval).toHaveBeenCalledWith(
|
|
||||||
jasmine.any(Function),
|
|
||||||
testInterval
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("has a database icon", function () {
|
|
||||||
expect(indicator.getCssClass()).toEqual("icon-database s-status-caution");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("consults the database at the configured path", function () {
|
|
||||||
expect(mockHttp.get).toHaveBeenCalledWith(testPath);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("changes when the database connection is nominal", function () {
|
|
||||||
var initialText = indicator.getText(),
|
|
||||||
initialDescrption = indicator.getDescription(),
|
|
||||||
initialGlyphClass = indicator.getGlyphClass();
|
|
||||||
|
|
||||||
// Nominal just means getting back an object, without
|
|
||||||
// an error field.
|
|
||||||
mockPromise.then.calls.mostRecent().args[0]({ data: {} });
|
|
||||||
|
|
||||||
// Verify that these values changed;
|
|
||||||
// don't test for specific text.
|
|
||||||
expect(indicator.getText()).not.toEqual(initialText);
|
|
||||||
expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass);
|
|
||||||
expect(indicator.getDescription()).not.toEqual(initialDescrption);
|
|
||||||
|
|
||||||
// Do check for specific class
|
|
||||||
expect(indicator.getGlyphClass()).toEqual("ok");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("changes when the server reports an error", function () {
|
|
||||||
var initialText = indicator.getText(),
|
|
||||||
initialDescrption = indicator.getDescription(),
|
|
||||||
initialGlyphClass = indicator.getGlyphClass();
|
|
||||||
|
|
||||||
// Nominal just means getting back an object, with
|
|
||||||
// an error field.
|
|
||||||
mockPromise.then.calls.mostRecent().args[0](
|
|
||||||
{ data: { error: "Uh oh." } }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify that these values changed;
|
|
||||||
// don't test for specific text.
|
|
||||||
expect(indicator.getText()).not.toEqual(initialText);
|
|
||||||
expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass);
|
|
||||||
expect(indicator.getDescription()).not.toEqual(initialDescrption);
|
|
||||||
|
|
||||||
// Do check for specific class
|
|
||||||
expect(indicator.getGlyphClass()).toEqual("caution");
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("changes when the server cannot be reached", function () {
|
|
||||||
var initialText = indicator.getText(),
|
|
||||||
initialDescrption = indicator.getDescription(),
|
|
||||||
initialGlyphClass = indicator.getGlyphClass();
|
|
||||||
|
|
||||||
// Nominal just means getting back an object, without
|
|
||||||
// an error field.
|
|
||||||
mockPromise.then.calls.mostRecent().args[1]({ data: {} });
|
|
||||||
|
|
||||||
// Verify that these values changed;
|
|
||||||
// don't test for specific text.
|
|
||||||
expect(indicator.getText()).not.toEqual(initialText);
|
|
||||||
expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass);
|
|
||||||
expect(indicator.getDescription()).not.toEqual(initialDescrption);
|
|
||||||
|
|
||||||
// Do check for specific class
|
|
||||||
expect(indicator.getGlyphClass()).toEqual("err");
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,223 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DomainObjectProviderSpec. Created by vwoeltje on 11/6/14.
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
["../src/CouchPersistenceProvider"],
|
|
||||||
function (CouchPersistenceProvider) {
|
|
||||||
|
|
||||||
describe("The couch persistence provider", function () {
|
|
||||||
var mockHttp,
|
|
||||||
mockQ,
|
|
||||||
testSpace = "testSpace",
|
|
||||||
testPath = "/test/db",
|
|
||||||
capture,
|
|
||||||
provider;
|
|
||||||
|
|
||||||
function mockPromise(value) {
|
|
||||||
return {
|
|
||||||
then: function (callback) {
|
|
||||||
return mockPromise(callback(value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockHttp = jasmine.createSpy("$http");
|
|
||||||
mockQ = jasmine.createSpyObj("$q", ["when"]);
|
|
||||||
|
|
||||||
mockQ.when.and.callFake(mockPromise);
|
|
||||||
|
|
||||||
// Capture promise results
|
|
||||||
capture = jasmine.createSpy("capture");
|
|
||||||
|
|
||||||
provider = new CouchPersistenceProvider(
|
|
||||||
mockHttp,
|
|
||||||
mockQ,
|
|
||||||
testSpace,
|
|
||||||
testPath
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("reports available spaces", function () {
|
|
||||||
provider.listSpaces().then(capture);
|
|
||||||
expect(capture).toHaveBeenCalledWith([testSpace]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// General pattern of tests below is to simulate CouchDB's
|
|
||||||
// response, verify that request looks like what CouchDB
|
|
||||||
// would expect, and finally verify that CouchPersistenceProvider's
|
|
||||||
// return values match what is expected.
|
|
||||||
it("lists all available documents", function () {
|
|
||||||
mockHttp.and.returnValue(mockPromise({
|
|
||||||
data: { rows: [{ id: "a" }, { id: "b" }, { id: "c" }] }
|
|
||||||
}));
|
|
||||||
provider.listObjects().then(capture);
|
|
||||||
expect(mockHttp).toHaveBeenCalledWith({
|
|
||||||
url: "/test/db/_all_docs", // couch document listing
|
|
||||||
method: "GET",
|
|
||||||
data: undefined
|
|
||||||
});
|
|
||||||
expect(capture).toHaveBeenCalledWith(["a", "b", "c"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows object creation", function () {
|
|
||||||
var model = { someKey: "some value" };
|
|
||||||
mockHttp.and.returnValue(mockPromise({
|
|
||||||
data: {
|
|
||||||
"_id": "abc",
|
|
||||||
"_rev": "xyz",
|
|
||||||
"ok": true
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
provider.createObject("testSpace", "abc", model).then(capture);
|
|
||||||
expect(mockHttp).toHaveBeenCalledWith({
|
|
||||||
url: "/test/db/abc",
|
|
||||||
method: "PUT",
|
|
||||||
data: {
|
|
||||||
"_id": "abc",
|
|
||||||
"_rev": undefined,
|
|
||||||
"_deleted": undefined,
|
|
||||||
metadata: jasmine.any(Object),
|
|
||||||
model: model
|
|
||||||
}
|
|
||||||
});
|
|
||||||
expect(capture).toHaveBeenCalledWith(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows object models to be read back", function () {
|
|
||||||
var model = { someKey: "some value" };
|
|
||||||
mockHttp.and.returnValue(mockPromise({
|
|
||||||
data: {
|
|
||||||
"_id": "abc",
|
|
||||||
"_rev": "xyz",
|
|
||||||
"model": model
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
provider.readObject("testSpace", "abc").then(capture);
|
|
||||||
expect(mockHttp).toHaveBeenCalledWith({
|
|
||||||
url: "/test/db/abc",
|
|
||||||
method: "GET",
|
|
||||||
data: undefined
|
|
||||||
});
|
|
||||||
expect(capture).toHaveBeenCalledWith(model);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows object update", function () {
|
|
||||||
var model = { someKey: "some value" };
|
|
||||||
|
|
||||||
// First do a read to populate rev tags...
|
|
||||||
mockHttp.and.returnValue(mockPromise({
|
|
||||||
data: {
|
|
||||||
"_id": "abc",
|
|
||||||
"_rev": "xyz",
|
|
||||||
"model": {}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
provider.readObject("testSpace", "abc");
|
|
||||||
|
|
||||||
// Now perform an update
|
|
||||||
mockHttp.and.returnValue(mockPromise({
|
|
||||||
data: {
|
|
||||||
"_id": "abc",
|
|
||||||
"_rev": "uvw",
|
|
||||||
"ok": true
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
provider.updateObject("testSpace", "abc", model).then(capture);
|
|
||||||
expect(mockHttp).toHaveBeenCalledWith({
|
|
||||||
url: "/test/db/abc",
|
|
||||||
method: "PUT",
|
|
||||||
data: {
|
|
||||||
"_id": "abc",
|
|
||||||
"_rev": "xyz",
|
|
||||||
"_deleted": undefined,
|
|
||||||
metadata: jasmine.any(Object),
|
|
||||||
model: model
|
|
||||||
}
|
|
||||||
});
|
|
||||||
expect(capture).toHaveBeenCalledWith(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows object deletion", function () {
|
|
||||||
// First do a read to populate rev tags...
|
|
||||||
mockHttp.and.returnValue(mockPromise({
|
|
||||||
data: {
|
|
||||||
"_id": "abc",
|
|
||||||
"_rev": "xyz",
|
|
||||||
"model": {}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
provider.readObject("testSpace", "abc");
|
|
||||||
|
|
||||||
// Now perform an update
|
|
||||||
mockHttp.and.returnValue(mockPromise({
|
|
||||||
data: {
|
|
||||||
"_id": "abc",
|
|
||||||
"_rev": "uvw",
|
|
||||||
"ok": true
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
provider.deleteObject("testSpace", "abc", {}).then(capture);
|
|
||||||
expect(mockHttp).toHaveBeenCalledWith({
|
|
||||||
url: "/test/db/abc",
|
|
||||||
method: "PUT",
|
|
||||||
data: {
|
|
||||||
"_id": "abc",
|
|
||||||
"_rev": "xyz",
|
|
||||||
"_deleted": true,
|
|
||||||
metadata: jasmine.any(Object),
|
|
||||||
model: {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
expect(capture).toHaveBeenCalledWith(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("reports failure to create objects", function () {
|
|
||||||
var model = { someKey: "some value" };
|
|
||||||
mockHttp.and.returnValue(mockPromise({
|
|
||||||
data: {
|
|
||||||
"_id": "abc",
|
|
||||||
"_rev": "xyz",
|
|
||||||
"ok": false
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
provider.createObject("testSpace", "abc", model).then(capture);
|
|
||||||
expect(capture).toHaveBeenCalledWith(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns undefined when objects are not found", function () {
|
|
||||||
// Act like a 404
|
|
||||||
mockHttp.and.returnValue({
|
|
||||||
then: function (success, fail) {
|
|
||||||
return mockPromise(fail());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
provider.readObject("testSpace", "abc").then(capture);
|
|
||||||
expect(capture).toHaveBeenCalledWith(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -252,7 +252,7 @@ define([
|
|||||||
|
|
||||||
this.status = new api.StatusAPI(this);
|
this.status = new api.StatusAPI(this);
|
||||||
|
|
||||||
this.router = new ApplicationRouter();
|
this.router = new ApplicationRouter(this);
|
||||||
|
|
||||||
this.branding = BrandingAPI.default;
|
this.branding = BrandingAPI.default;
|
||||||
|
|
||||||
|
@ -36,7 +36,8 @@ define([
|
|||||||
'./views/installLegacyViews',
|
'./views/installLegacyViews',
|
||||||
'./policies/LegacyCompositionPolicyAdapter',
|
'./policies/LegacyCompositionPolicyAdapter',
|
||||||
'./actions/LegacyActionAdapter',
|
'./actions/LegacyActionAdapter',
|
||||||
'./services/LegacyPersistenceAdapter'
|
'./services/LegacyPersistenceAdapter',
|
||||||
|
'./services/ExportImageService'
|
||||||
], function (
|
], function (
|
||||||
ActionDialogDecorator,
|
ActionDialogDecorator,
|
||||||
AdapterCapability,
|
AdapterCapability,
|
||||||
@ -53,7 +54,8 @@ define([
|
|||||||
installLegacyViews,
|
installLegacyViews,
|
||||||
legacyCompositionPolicyAdapter,
|
legacyCompositionPolicyAdapter,
|
||||||
LegacyActionAdapter,
|
LegacyActionAdapter,
|
||||||
LegacyPersistenceAdapter
|
LegacyPersistenceAdapter,
|
||||||
|
ExportImageService
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
name: 'src/adapter',
|
name: 'src/adapter',
|
||||||
@ -82,6 +84,13 @@ define([
|
|||||||
"identifierService",
|
"identifierService",
|
||||||
"cacheService"
|
"cacheService"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "exportImageService",
|
||||||
|
"implementation": ExportImageService,
|
||||||
|
"depends": [
|
||||||
|
"dialogService"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
components: [
|
components: [
|
||||||
|
@ -161,6 +161,22 @@ define([
|
|||||||
evaluate: function (datum, property) {
|
evaluate: function (datum, property) {
|
||||||
return limitEvaluator.evaluate(datum, property && property.key);
|
return limitEvaluator.evaluate(datum, property && property.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
LegacyTelemetryProvider.prototype.getLimits = function (domainObject) {
|
||||||
|
const oldObject = this.instantiate(
|
||||||
|
utils.toOldFormat(domainObject),
|
||||||
|
utils.makeKeyString(domainObject.identifier)
|
||||||
|
);
|
||||||
|
const limitEvaluator = oldObject.getCapability("limit");
|
||||||
|
|
||||||
|
return {
|
||||||
|
limits: function () {
|
||||||
|
return limitEvaluator.limits();
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ define([
|
|||||||
|
|
||||||
oldStyleObject.getCapability('mutation').mutate(function () {
|
oldStyleObject.getCapability('mutation').mutate(function () {
|
||||||
return utils.toOldFormat(newStyleObject);
|
return utils.toOldFormat(newStyleObject);
|
||||||
});
|
}, newStyleObject.modified);
|
||||||
|
|
||||||
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
|
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
@ -119,7 +119,8 @@ describe('The ActionCollection', () => {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
actionCollection.destroy();
|
actionCollection.destroy();
|
||||||
resetApplicationState(openmct);
|
|
||||||
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("disable method invoked with action keys", () => {
|
describe("disable method invoked with action keys", () => {
|
||||||
|
@ -99,7 +99,7 @@ describe('The Actions API', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("register method", () => {
|
describe("register method", () => {
|
||||||
|
@ -215,12 +215,12 @@ define([
|
|||||||
* @memberof {module:openmct.CompositionCollection#}
|
* @memberof {module:openmct.CompositionCollection#}
|
||||||
* @name load
|
* @name load
|
||||||
*/
|
*/
|
||||||
CompositionCollection.prototype.load = function () {
|
CompositionCollection.prototype.load = function (abortSignal) {
|
||||||
this.cleanUpMutables();
|
this.cleanUpMutables();
|
||||||
|
|
||||||
return this.provider.load(this.domainObject)
|
return this.provider.load(this.domainObject)
|
||||||
.then(function (children) {
|
.then(function (children) {
|
||||||
return Promise.all(children.map((c) => this.publicAPI.objects.get(c)));
|
return Promise.all(children.map((c) => this.publicAPI.objects.get(c, abortSignal)));
|
||||||
}.bind(this))
|
}.bind(this))
|
||||||
.then(function (childObjects) {
|
.then(function (childObjects) {
|
||||||
childObjects.forEach(c => this.add(c, true));
|
childObjects.forEach(c => this.add(c, true));
|
||||||
|
@ -76,7 +76,7 @@ describe ('The Menu API', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("showMenu method", () => {
|
describe("showMenu method", () => {
|
||||||
|
@ -76,7 +76,10 @@ class MutableDomainObject {
|
|||||||
}
|
}
|
||||||
$set(path, value) {
|
$set(path, value) {
|
||||||
_.set(this, path, value);
|
_.set(this, path, value);
|
||||||
|
|
||||||
|
if (path !== 'persisted' && path !== 'modified') {
|
||||||
_.set(this, 'modified', Date.now());
|
_.set(this, 'modified', Date.now());
|
||||||
|
}
|
||||||
|
|
||||||
//Emit secret synchronization event first, so that all objects are in sync before subsequent events fired.
|
//Emit secret synchronization event first, so that all objects are in sync before subsequent events fired.
|
||||||
this._globalEventEmitter.emit(qualifiedEventName(this, '$_synchronize_model'), this);
|
this._globalEventEmitter.emit(qualifiedEventName(this, '$_synchronize_model'), this);
|
||||||
@ -112,9 +115,11 @@ class MutableDomainObject {
|
|||||||
return () => this._instanceEventEmitter.off(event, callback);
|
return () => this._instanceEventEmitter.off(event, callback);
|
||||||
}
|
}
|
||||||
$destroy() {
|
$destroy() {
|
||||||
this._observers.forEach(observer => observer());
|
while (this._observers.length > 0) {
|
||||||
delete this._globalEventEmitter;
|
const observer = this._observers.pop();
|
||||||
delete this._observers;
|
observer();
|
||||||
|
}
|
||||||
|
|
||||||
this._instanceEventEmitter.emit('$_destroy');
|
this._instanceEventEmitter.emit('$_destroy');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,6 +161,7 @@ ObjectAPI.prototype.addProvider = function (namespace, provider) {
|
|||||||
|
|
||||||
ObjectAPI.prototype.get = function (identifier, abortSignal) {
|
ObjectAPI.prototype.get = function (identifier, abortSignal) {
|
||||||
let keystring = this.makeKeyString(identifier);
|
let keystring = this.makeKeyString(identifier);
|
||||||
|
|
||||||
if (this.cache[keystring] !== undefined) {
|
if (this.cache[keystring] !== undefined) {
|
||||||
return this.cache[keystring];
|
return this.cache[keystring];
|
||||||
}
|
}
|
||||||
@ -176,15 +177,16 @@ ObjectAPI.prototype.get = function (identifier, abortSignal) {
|
|||||||
throw new Error('Provider does not support get!');
|
throw new Error('Provider does not support get!');
|
||||||
}
|
}
|
||||||
|
|
||||||
let objectPromise = provider.get(identifier, abortSignal);
|
let objectPromise = provider.get(identifier, abortSignal).then(result => {
|
||||||
this.cache[keystring] = objectPromise;
|
|
||||||
|
|
||||||
return objectPromise.then(result => {
|
|
||||||
delete this.cache[keystring];
|
delete this.cache[keystring];
|
||||||
result = this.applyGetInterceptors(identifier, result);
|
result = this.applyGetInterceptors(identifier, result);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.cache[keystring] = objectPromise;
|
||||||
|
|
||||||
|
return objectPromise;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -484,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.
|
* Uniquely identifies a domain object.
|
||||||
*
|
*
|
||||||
@ -520,8 +528,10 @@ ObjectAPI.prototype.getOriginalPath = function (identifier, path = []) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
function hasAlreadyBeenPersisted(domainObject) {
|
function hasAlreadyBeenPersisted(domainObject) {
|
||||||
return domainObject.persisted !== undefined
|
const result = domainObject.persisted !== undefined
|
||||||
&& domainObject.persisted === domainObject.modified;
|
&& domainObject.persisted >= domainObject.modified;
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ObjectAPI;
|
export default ObjectAPI;
|
||||||
|
@ -22,7 +22,7 @@ describe("The Status API", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("set function", () => {
|
describe("set function", () => {
|
||||||
|
@ -504,6 +504,26 @@ define([
|
|||||||
return this.getLimitEvaluator(domainObject);
|
return this.getLimitEvaluator(domainObject);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a limits for this domain object.
|
||||||
|
* Limits help you display limits and alarms of
|
||||||
|
* telemetry for display purposes without having to interact directly
|
||||||
|
* with the Limit API.
|
||||||
|
*
|
||||||
|
* This method is optional.
|
||||||
|
* If a provider does not implement this method, it is presumed
|
||||||
|
* that no limits are defined for this domain object's telemetry.
|
||||||
|
*
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain
|
||||||
|
* object for which to get limits
|
||||||
|
* @returns {module:openmct.TelemetryAPI~LimitEvaluator}
|
||||||
|
* @method limits
|
||||||
|
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||||
|
*/
|
||||||
|
TelemetryAPI.prototype.limitDefinition = function (domainObject) {
|
||||||
|
return this.getLimits(domainObject);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a limit evaluator for this domain object.
|
* Get a limit evaluator for this domain object.
|
||||||
* Limit Evaluators help you evaluate limit and alarm status of individual
|
* Limit Evaluators help you evaluate limit and alarm status of individual
|
||||||
@ -531,5 +551,42 @@ define([
|
|||||||
return provider.getLimitEvaluator(domainObject);
|
return provider.getLimitEvaluator(domainObject);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a limit definitions for this domain object.
|
||||||
|
* Limit Definitions help you indicate limits and alarms of
|
||||||
|
* telemetry for display purposes without having to interact directly
|
||||||
|
* with the Limit API.
|
||||||
|
*
|
||||||
|
* This method is optional.
|
||||||
|
* If a provider does not implement this method, it is presumed
|
||||||
|
* that no limits are defined for this domain object's telemetry.
|
||||||
|
*
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain
|
||||||
|
* object for which to display limits
|
||||||
|
* @returns {module:openmct.TelemetryAPI~LimitEvaluator}
|
||||||
|
* @method limits returns a limits object of
|
||||||
|
* type {
|
||||||
|
* level1: {
|
||||||
|
* low: { key1: value1, key2: value2 },
|
||||||
|
* high: { key1: value1, key2: value2 }
|
||||||
|
* },
|
||||||
|
* level2: {
|
||||||
|
* low: { key1: value1, key2: value2 },
|
||||||
|
* high: { key1: value1, key2: value2 }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||||
|
*/
|
||||||
|
TelemetryAPI.prototype.getLimits = function (domainObject) {
|
||||||
|
const provider = this.findLimitEvaluator(domainObject);
|
||||||
|
if (!provider || !provider.getLimits) {
|
||||||
|
return {
|
||||||
|
limits: function () {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.getLimits(domainObject);
|
||||||
|
};
|
||||||
|
|
||||||
return TelemetryAPI;
|
return TelemetryAPI;
|
||||||
});
|
});
|
||||||
|
@ -90,7 +90,6 @@ define([
|
|||||||
'../platform/framework/src/load/Bundle',
|
'../platform/framework/src/load/Bundle',
|
||||||
'../platform/identity/bundle',
|
'../platform/identity/bundle',
|
||||||
'../platform/persistence/aggregator/bundle',
|
'../platform/persistence/aggregator/bundle',
|
||||||
'../platform/persistence/couch/bundle',
|
|
||||||
'../platform/persistence/elastic/bundle',
|
'../platform/persistence/elastic/bundle',
|
||||||
'../platform/persistence/local/bundle',
|
'../platform/persistence/local/bundle',
|
||||||
'../platform/persistence/queue/bundle',
|
'../platform/persistence/queue/bundle',
|
||||||
|
@ -45,10 +45,14 @@ export default function LADTableSetViewProvider(openmct) {
|
|||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
domainObject,
|
|
||||||
objectPath
|
objectPath
|
||||||
},
|
},
|
||||||
template: '<lad-table-set></lad-table-set>'
|
data() {
|
||||||
|
return {
|
||||||
|
domainObject
|
||||||
|
};
|
||||||
|
},
|
||||||
|
template: '<lad-table-set :domain-object="domainObject"></lad-table-set>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
destroy: function (element) {
|
destroy: function (element) {
|
||||||
|
@ -56,7 +56,7 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
objectPath: {
|
pathToTable: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
@ -66,20 +66,19 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
let currentObjectPath = this.objectPath.slice();
|
|
||||||
currentObjectPath.unshift(this.domainObject);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
timestamp: undefined,
|
timestamp: undefined,
|
||||||
value: '---',
|
value: '---',
|
||||||
valueClass: '',
|
valueClass: '',
|
||||||
currentObjectPath,
|
|
||||||
unit: ''
|
unit: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
formattedTimestamp() {
|
formattedTimestamp() {
|
||||||
return this.timestamp !== undefined ? this.getFormattedTimestamp(this.timestamp) : '---';
|
return this.timestamp !== undefined ? this.getFormattedTimestamp(this.timestamp) : '---';
|
||||||
|
},
|
||||||
|
objectPath() {
|
||||||
|
return [this.domainObject, ...this.pathToTable];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -182,7 +181,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
showContextMenu(event) {
|
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 allActions = actionCollection.getActionsObject();
|
||||||
let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);
|
let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);
|
||||||
|
|
||||||
|
@ -33,10 +33,10 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<lad-row
|
<lad-row
|
||||||
v-for="item in items"
|
v-for="ladRow in items"
|
||||||
:key="item.key"
|
:key="ladRow.key"
|
||||||
:domain-object="item.domainObject"
|
:domain-object="ladRow.domainObject"
|
||||||
:object-path="objectPath"
|
:path-to-table="objectPath"
|
||||||
:has-units="hasUnits"
|
:has-units="hasUnits"
|
||||||
/>
|
/>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -43,9 +43,10 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<lad-row
|
<lad-row
|
||||||
v-for="telemetryObject in ladTelemetryObjects[ladTable.key]"
|
v-for="ladRow in ladTelemetryObjects[ladTable.key]"
|
||||||
:key="telemetryObject.key"
|
:key="ladRow.key"
|
||||||
:domain-object="telemetryObject.domainObject"
|
:domain-object="ladRow.domainObject"
|
||||||
|
:path-to-table="ladTable.objectPath"
|
||||||
:has-units="hasUnits"
|
:has-units="hasUnits"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@ -60,7 +61,13 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
LadRow
|
LadRow
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'objectPath'],
|
||||||
|
props: {
|
||||||
|
domainObject: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
ladTableObjects: [],
|
ladTableObjects: [],
|
||||||
@ -106,6 +113,7 @@ export default {
|
|||||||
let ladTable = {};
|
let ladTable = {};
|
||||||
ladTable.domainObject = domainObject;
|
ladTable.domainObject = domainObject;
|
||||||
ladTable.key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
ladTable.key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
ladTable.objectPath = [domainObject, ...this.objectPath];
|
||||||
|
|
||||||
this.$set(this.ladTelemetryObjects, ladTable.key, []);
|
this.$set(this.ladTelemetryObjects, ladTable.key, []);
|
||||||
this.ladTableObjects.push(ladTable);
|
this.ladTableObjects.push(ladTable);
|
||||||
|
@ -292,6 +292,11 @@ describe("The LAD Table Set", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
openmct.time.timeSystem('utc', {
|
||||||
|
start: 0,
|
||||||
|
end: 1
|
||||||
|
});
|
||||||
|
|
||||||
return resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,10 +19,6 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import {
|
|
||||||
getAllSearchParams,
|
|
||||||
setAllSearchParams
|
|
||||||
} from 'utils/openmctLocation';
|
|
||||||
|
|
||||||
const TIME_EVENTS = ['timeSystem', 'clock', 'clockOffsets'];
|
const TIME_EVENTS = ['timeSystem', 'clock', 'clockOffsets'];
|
||||||
const SEARCH_MODE = 'tc.mode';
|
const SEARCH_MODE = 'tc.mode';
|
||||||
@ -49,9 +45,8 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
this.updateTimeSettings();
|
this.openmct.router.on('change:params', this.updateTimeSettings);
|
||||||
|
|
||||||
window.addEventListener('hashchange', this.updateTimeSettings);
|
|
||||||
TIME_EVENTS.forEach(event => {
|
TIME_EVENTS.forEach(event => {
|
||||||
this.openmct.time.on(event, this.setUrlFromTimeApi);
|
this.openmct.time.on(event, this.setUrlFromTimeApi);
|
||||||
});
|
});
|
||||||
@ -59,7 +54,8 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
window.removeEventListener('hashchange', this.updateTimeSettings);
|
this.openmct.router.off('change:params', this.updateTimeSettings);
|
||||||
|
|
||||||
this.openmct.off('start', this.initialize);
|
this.openmct.off('start', this.initialize);
|
||||||
this.openmct.off('destroy', this.destroy);
|
this.openmct.off('destroy', this.destroy);
|
||||||
|
|
||||||
@ -70,22 +66,18 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateTimeSettings() {
|
updateTimeSettings() {
|
||||||
// Prevent from triggering self
|
|
||||||
if (!this.isUrlUpdateInProgress) {
|
|
||||||
let timeParameters = this.parseParametersFromUrl();
|
let timeParameters = this.parseParametersFromUrl();
|
||||||
|
|
||||||
if (this.areTimeParametersValid(timeParameters)) {
|
if (this.areTimeParametersValid(timeParameters)) {
|
||||||
this.setTimeApiFromUrl(timeParameters);
|
this.setTimeApiFromUrl(timeParameters);
|
||||||
|
this.openmct.router.setLocationFromUrl();
|
||||||
} else {
|
} else {
|
||||||
this.setUrlFromTimeApi();
|
this.setUrlFromTimeApi();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.isUrlUpdateInProgress = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parseParametersFromUrl() {
|
parseParametersFromUrl() {
|
||||||
let searchParams = getAllSearchParams();
|
let searchParams = this.openmct.router.getAllSearchParams();
|
||||||
|
|
||||||
let mode = searchParams.get(SEARCH_MODE);
|
let mode = searchParams.get(SEARCH_MODE);
|
||||||
let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
|
let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
|
||||||
@ -148,7 +140,7 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setUrlFromTimeApi() {
|
setUrlFromTimeApi() {
|
||||||
let searchParams = getAllSearchParams();
|
let searchParams = this.openmct.router.getAllSearchParams();
|
||||||
let clock = this.openmct.time.clock();
|
let clock = this.openmct.time.clock();
|
||||||
let bounds = this.openmct.time.bounds();
|
let bounds = this.openmct.time.bounds();
|
||||||
let clockOffsets = this.openmct.time.clockOffsets();
|
let clockOffsets = this.openmct.time.clockOffsets();
|
||||||
@ -176,8 +168,7 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
|
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
|
||||||
this.isUrlUpdateInProgress = true;
|
this.openmct.router.setAllSearchParams(searchParams);
|
||||||
setAllSearchParams(searchParams);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
areTimeParametersValid(timeParameters) {
|
areTimeParametersValid(timeParameters) {
|
||||||
|
@ -25,306 +25,118 @@ import {
|
|||||||
} from 'utils/testing';
|
} from 'utils/testing';
|
||||||
|
|
||||||
describe("The URLTimeSettingsSynchronizer", () => {
|
describe("The URLTimeSettingsSynchronizer", () => {
|
||||||
|
let appHolder;
|
||||||
let openmct;
|
let openmct;
|
||||||
let testClock;
|
let resolveFunction;
|
||||||
|
let oldHash;
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
|
openmct.install(openmct.plugins.MyItems());
|
||||||
openmct.install(openmct.plugins.LocalTimeSystem());
|
openmct.install(openmct.plugins.LocalTimeSystem());
|
||||||
testClock = jasmine.createSpyObj("testClock", ["start", "stop", "tick", "currentValue", "on", "off"]);
|
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||||
testClock.key = "test-clock";
|
|
||||||
testClock.currentValue.and.returnValue(0);
|
|
||||||
|
|
||||||
openmct.time.addClock(testClock);
|
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.startHeadless();
|
|
||||||
|
appHolder = document.createElement("div");
|
||||||
|
openmct.start(appHolder);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => resetApplicationState(openmct));
|
afterEach(() => {
|
||||||
|
openmct.time.stopClock();
|
||||||
|
openmct.router.removeListener('change:hash', resolveFunction);
|
||||||
|
|
||||||
describe("realtime mode", () => {
|
appHolder = undefined;
|
||||||
it("when the clock is set via the time API, it is immediately reflected in the URL", () => {
|
openmct = undefined;
|
||||||
//Test expected initial conditions
|
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);
|
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
|
||||||
|
|
||||||
|
openmct.router.removeListener('change:hash', resolveFunction);
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
openmct.router.on('change:hash', resolveFunction);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when the clock is set via the time API, it is reflected in the URL", (done) => {
|
||||||
|
let success;
|
||||||
|
|
||||||
|
resolveFunction = () => {
|
||||||
openmct.time.clock('local', {
|
openmct.time.clock('local', {
|
||||||
start: -1000,
|
|
||||||
end: 100
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(window.location.hash.includes('tc.mode=local')).toBe(true);
|
|
||||||
|
|
||||||
//Test that expected initial conditions are no longer true
|
|
||||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
|
||||||
});
|
|
||||||
it("when offsets are set via the time API, they are immediately reflected in the URL", () => {
|
|
||||||
//Test expected initial conditions
|
|
||||||
expect(window.location.hash.includes('tc.startDelta')).toBe(false);
|
|
||||||
expect(window.location.hash.includes('tc.endDelta')).toBe(false);
|
|
||||||
|
|
||||||
openmct.time.clock('local', {
|
|
||||||
start: -1000,
|
|
||||||
end: 100
|
|
||||||
});
|
|
||||||
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
|
|
||||||
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
|
|
||||||
|
|
||||||
openmct.time.clockOffsets({
|
|
||||||
start: -2000,
|
start: -2000,
|
||||||
end: 200
|
end: 200
|
||||||
});
|
});
|
||||||
expect(window.location.hash.includes('tc.startDelta=2000')).toBe(true);
|
|
||||||
expect(window.location.hash.includes('tc.endDelta=200')).toBe(true);
|
|
||||||
|
|
||||||
//Test that expected initial conditions are no longer true
|
const hasStartDelta = window.location.hash.includes('tc.startDelta=2000');
|
||||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
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);
|
||||||
|
|
||||||
|
openmct.router.removeListener('change:hash', resolveFunction);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
openmct.router.on('change:hash', resolveFunction);
|
||||||
});
|
});
|
||||||
describe("when set in the url", () => {
|
|
||||||
it("will change from fixed to realtime mode when the mode changes", () => {
|
|
||||||
expectLocationToBeInFixedMode();
|
|
||||||
|
|
||||||
return switchToRealtimeMode().then(() => {
|
it("when the clock mode is set to local, it is reflected in the URL", (done) => {
|
||||||
let clock = openmct.time.clock();
|
let success;
|
||||||
|
|
||||||
expect(clock).toBeDefined();
|
resolveFunction = () => {
|
||||||
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;
|
let hash = window.location.hash;
|
||||||
hash = hash.replace('tc.mode=local', 'tc.mode=test-clock');
|
hash = hash.replace('tc.mode=fixed', 'tc.mode=local');
|
||||||
window.location.hash = hash;
|
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) => {
|
success = window.location.hash.includes('tc.mode=local');
|
||||||
resolveFunction = resolve;
|
if (success) {
|
||||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
expect(success).toBe(true);
|
||||||
//detected in the API.
|
done();
|
||||||
openmct.time.on('clockOffsets', resolveFunction);
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
openmct.router.on('change:hash', resolveFunction);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when the clock mode is set to local, it is reflected in the URL", (done) => {
|
||||||
|
let success;
|
||||||
|
|
||||||
|
resolveFunction = () => {
|
||||||
let hash = window.location.hash;
|
let hash = window.location.hash;
|
||||||
hash = hash.replace('tc.startDelta=1000', 'tc.startDelta=2000');
|
|
||||||
hash = hash.replace('tc.endDelta=100', 'tc.endDelta=200');
|
hash = hash.replace('tc.mode=fixed', 'tc.mode=local');
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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');
|
|
||||||
|
|
||||||
window.location.hash = hash;
|
window.location.hash = hash;
|
||||||
|
success = window.location.hash.includes('tc.mode=local');
|
||||||
|
if (success) {
|
||||||
|
expect(success).toBe(true);
|
||||||
|
done();
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function setFixedLocationParameters() {
|
openmct.router.on('change:hash', resolveFunction);
|
||||||
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');
|
|
||||||
|
|
||||||
window.location.hash = hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
function switchToRealtimeMode() {
|
|
||||||
let resolveFunction;
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
resolveFunction = resolve;
|
|
||||||
openmct.time.on('clock', resolveFunction);
|
|
||||||
setRealtimeLocationParameters();
|
|
||||||
}).then(() => {
|
|
||||||
openmct.time.off('clock', resolveFunction);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("reset hash", (done) => {
|
||||||
|
let success;
|
||||||
|
|
||||||
|
window.location.hash = oldHash;
|
||||||
|
resolveFunction = () => {
|
||||||
|
success = window.location.hash === oldHash;
|
||||||
|
if (success) {
|
||||||
|
expect(success).toBe(true);
|
||||||
|
done();
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function switchToFixedMode() {
|
openmct.router.on('change:hash', resolveFunction);
|
||||||
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);
|
|
||||||
setFixedLocationParameters();
|
|
||||||
}).then(() => {
|
|
||||||
openmct.time.off('clock', 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
@ -82,8 +82,10 @@ export default class Condition extends EventEmitter {
|
|||||||
if (this.isAnyOrAllTelemetry(criterion)) {
|
if (this.isAnyOrAllTelemetry(criterion)) {
|
||||||
criterion.updateResult(datum, this.conditionManager.telemetryObjects);
|
criterion.updateResult(datum, this.conditionManager.telemetryObjects);
|
||||||
} else {
|
} else {
|
||||||
|
if (criterion.usesTelemetry(datum.id)) {
|
||||||
criterion.updateResult(datum);
|
criterion.updateResult(datum);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.result = evaluateResults(this.criteria.map(criterion => criterion.result), this.trigger);
|
this.result = evaluateResults(this.criteria.map(criterion => criterion.result), this.trigger);
|
||||||
@ -102,7 +104,7 @@ export default class Condition extends EventEmitter {
|
|||||||
|
|
||||||
isTelemetryUsed(id) {
|
isTelemetryUsed(id) {
|
||||||
return this.criteria.some(criterion => {
|
return this.criteria.some(criterion => {
|
||||||
return this.isAnyOrAllTelemetry(criterion) || criterion.telemetryObjectIdAsString === id;
|
return this.isAnyOrAllTelemetry(criterion) || criterion.usesTelemetry(id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,11 +272,11 @@ export default class Condition extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
requestLADConditionResult() {
|
requestLADConditionResult(options) {
|
||||||
let latestTimestamp;
|
let latestTimestamp;
|
||||||
let criteriaResults = {};
|
let criteriaResults = {};
|
||||||
const criteriaRequests = this.criteria
|
const criteriaRequests = this.criteria
|
||||||
.map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects));
|
.map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects, options));
|
||||||
|
|
||||||
return Promise.all(criteriaRequests)
|
return Promise.all(criteriaRequests)
|
||||||
.then(results => {
|
.then(results => {
|
||||||
|
@ -282,7 +282,7 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
return currentCondition;
|
return currentCondition;
|
||||||
}
|
}
|
||||||
|
|
||||||
requestLADConditionSetOutput() {
|
requestLADConditionSetOutput(options) {
|
||||||
if (!this.conditions.length) {
|
if (!this.conditions.length) {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
@ -291,7 +291,7 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
let latestTimestamp;
|
let latestTimestamp;
|
||||||
let conditionResults = {};
|
let conditionResults = {};
|
||||||
const conditionRequests = this.conditions
|
const conditionRequests = this.conditions
|
||||||
.map(condition => condition.requestLADConditionResult());
|
.map(condition => condition.requestLADConditionResult(options));
|
||||||
|
|
||||||
return Promise.all(conditionRequests)
|
return Promise.all(conditionRequests)
|
||||||
.then((results) => {
|
.then((results) => {
|
||||||
|
@ -40,10 +40,10 @@ export default class ConditionSetTelemetryProvider {
|
|||||||
return domainObject.type === 'conditionSet';
|
return domainObject.type === 'conditionSet';
|
||||||
}
|
}
|
||||||
|
|
||||||
request(domainObject) {
|
request(domainObject, options) {
|
||||||
let conditionManager = this.getConditionManager(domainObject);
|
let conditionManager = this.getConditionManager(domainObject);
|
||||||
|
|
||||||
return conditionManager.requestLADConditionSetOutput()
|
return conditionManager.requestLADConditionSetOutput(options)
|
||||||
.then(latestOutput => {
|
.then(latestOutput => {
|
||||||
return latestOutput;
|
return latestOutput;
|
||||||
});
|
});
|
||||||
@ -52,7 +52,9 @@ export default class ConditionSetTelemetryProvider {
|
|||||||
subscribe(domainObject, callback) {
|
subscribe(domainObject, callback) {
|
||||||
let conditionManager = this.getConditionManager(domainObject);
|
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));
|
return this.destroyConditionManager.bind(this, this.openmct.objects.makeKeyString(domainObject.identifier));
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ export default class StyleRuleManager extends EventEmitter {
|
|||||||
if (styleConfiguration) {
|
if (styleConfiguration) {
|
||||||
this.initialize(styleConfiguration);
|
this.initialize(styleConfiguration);
|
||||||
if (styleConfiguration.conditionSetIdentifier) {
|
if (styleConfiguration.conditionSetIdentifier) {
|
||||||
|
this.openmct.time.on("bounds", this.refreshData.bind(this));
|
||||||
this.subscribeToConditionSet();
|
this.subscribeToConditionSet();
|
||||||
} else {
|
} else {
|
||||||
this.applyStaticStyle();
|
this.applyStaticStyle();
|
||||||
@ -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) {
|
updateObjectStyleConfig(styleConfiguration) {
|
||||||
if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) {
|
if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) {
|
||||||
this.initialize(styleConfiguration || {});
|
this.initialize(styleConfiguration || {});
|
||||||
@ -160,10 +180,14 @@ export default class StyleRuleManager extends EventEmitter {
|
|||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.stopProvidingTelemetry) {
|
if (this.stopProvidingTelemetry) {
|
||||||
|
|
||||||
this.stopProvidingTelemetry();
|
this.stopProvidingTelemetry();
|
||||||
delete this.stopProvidingTelemetry;
|
delete this.stopProvidingTelemetry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.openmct.time.off("bounds", this.refreshData);
|
||||||
|
this.openmct.editor.off('isEditing', this.toggleSubscription);
|
||||||
|
|
||||||
this.conditionSetIdentifier = undefined;
|
this.conditionSetIdentifier = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,6 @@
|
|||||||
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
||||||
<a v-if="conditionSetDomainObject"
|
<a v-if="conditionSetDomainObject"
|
||||||
class="c-object-label icon-conditional"
|
class="c-object-label icon-conditional"
|
||||||
:href="navigateToPath"
|
|
||||||
@click="navigateOrPreview"
|
@click="navigateOrPreview"
|
||||||
>
|
>
|
||||||
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
|
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
|
||||||
@ -286,6 +285,8 @@ export default {
|
|||||||
if (this.openmct.editor.isEditing()) {
|
if (this.openmct.editor.isEditing()) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.previewAction.invoke(this.objectPath);
|
this.previewAction.invoke(this.objectPath);
|
||||||
|
} else {
|
||||||
|
this.openmct.router.navigate(this.navigateToPath);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeConditionSet() {
|
removeConditionSet() {
|
||||||
|
@ -66,7 +66,6 @@
|
|||||||
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
||||||
<a v-if="conditionSetDomainObject"
|
<a v-if="conditionSetDomainObject"
|
||||||
class="c-object-label"
|
class="c-object-label"
|
||||||
:href="navigateToPath"
|
|
||||||
@click="navigateOrPreview"
|
@click="navigateOrPreview"
|
||||||
>
|
>
|
||||||
<span class="c-object-label__type-icon icon-conditional"></span>
|
<span class="c-object-label__type-icon icon-conditional"></span>
|
||||||
@ -309,6 +308,8 @@ export default {
|
|||||||
if (this.openmct.editor.isEditing()) {
|
if (this.openmct.editor.isEditing()) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.previewAction.invoke(this.objectPath);
|
this.previewAction.invoke(this.objectPath);
|
||||||
|
} else {
|
||||||
|
this.openmct.router.navigate(this.navigateToPath);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isItemType(type, item) {
|
isItemType(type, item) {
|
||||||
@ -344,6 +345,11 @@ export default {
|
|||||||
const layoutItem = selectionItem[0].context.layoutItem;
|
const layoutItem = selectionItem[0].context.layoutItem;
|
||||||
const isChildItem = selectionItem.length > 1;
|
const isChildItem = selectionItem.length > 1;
|
||||||
|
|
||||||
|
if (!item && !layoutItem) {
|
||||||
|
// cases where selection is used for table cells
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isChildItem) {
|
if (!isChildItem) {
|
||||||
domainObject = item;
|
domainObject = item;
|
||||||
itemStyle = getApplicableStylesForItem(item);
|
itemStyle = getApplicableStylesForItem(item);
|
||||||
|
@ -147,12 +147,16 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
|
|||||||
this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry);
|
this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestLAD(telemetryObjects) {
|
requestLAD(telemetryObjects, requestOptions) {
|
||||||
const options = {
|
let options = {
|
||||||
strategy: 'latest',
|
strategy: 'latest',
|
||||||
size: 1
|
size: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (requestOptions !== undefined) {
|
||||||
|
options = Object.assign(options, requestOptions);
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.isValid()) {
|
if (!this.isValid()) {
|
||||||
return this.formatData({}, telemetryObjects);
|
return this.formatData({}, telemetryObjects);
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,10 @@ export default class TelemetryCriterion extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usesTelemetry(id) {
|
||||||
|
return this.telemetryObjectIdAsString && (this.telemetryObjectIdAsString === id);
|
||||||
|
}
|
||||||
|
|
||||||
subscribeForStaleData() {
|
subscribeForStaleData() {
|
||||||
if (this.stalenessSubscription) {
|
if (this.stalenessSubscription) {
|
||||||
this.stalenessSubscription.clear();
|
this.stalenessSubscription.clear();
|
||||||
@ -133,12 +137,16 @@ export default class TelemetryCriterion extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
requestLAD() {
|
requestLAD(telemetryObjects, requestOptions) {
|
||||||
const options = {
|
let options = {
|
||||||
strategy: 'latest',
|
strategy: 'latest',
|
||||||
size: 1
|
size: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (requestOptions !== undefined) {
|
||||||
|
options = Object.assign(options, requestOptions);
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.isValid()) {
|
if (!this.isValid()) {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
@ -104,7 +104,7 @@ export function getConsolidatedStyleValues(multipleItemStyles) {
|
|||||||
const properties = Object.keys(styleProps);
|
const properties = Object.keys(styleProps);
|
||||||
properties.forEach((property) => {
|
properties.forEach((property) => {
|
||||||
const values = aggregatedStyleValues[property];
|
const values = aggregatedStyleValues[property];
|
||||||
if (values.length) {
|
if (values && values.length) {
|
||||||
if (values.every(value => value === values[0])) {
|
if (values.every(value => value === values[0])) {
|
||||||
styleValues[property] = values[0];
|
styleValues[property] = values[0];
|
||||||
} else {
|
} else {
|
||||||
|
@ -46,7 +46,7 @@ xdescribe("the plugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('installs the new folder action', () => {
|
it('installs the new folder action', () => {
|
||||||
|
@ -235,7 +235,7 @@ define(['lodash'], function (_) {
|
|||||||
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
|
message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`,
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
label: 'Ok',
|
label: 'OK',
|
||||||
emphasis: 'true',
|
emphasis: 'true',
|
||||||
callback: function () {
|
callback: function () {
|
||||||
removeItem(getAllTypes(selection));
|
removeItem(getAllTypes(selection));
|
||||||
|
@ -147,7 +147,7 @@ export default {
|
|||||||
this.mutablePromise.then(() => {
|
this.mutablePromise.then(() => {
|
||||||
this.openmct.objects.destroyMutable(this.domainObject);
|
this.openmct.objects.destroyMutable(this.domainObject);
|
||||||
});
|
});
|
||||||
} else {
|
} else if (this.domainObject.isMutable) {
|
||||||
this.openmct.objects.destroyMutable(this.domainObject);
|
this.openmct.objects.destroyMutable(this.domainObject);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -240,7 +240,7 @@ export default {
|
|||||||
this.mutablePromise.then(() => {
|
this.mutablePromise.then(() => {
|
||||||
this.openmct.objects.destroyMutable(this.domainObject);
|
this.openmct.objects.destroyMutable(this.domainObject);
|
||||||
});
|
});
|
||||||
} else {
|
} else if (this.domainObject.isMutable) {
|
||||||
this.openmct.objects.destroyMutable(this.domainObject);
|
this.openmct.objects.destroyMutable(this.domainObject);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -269,7 +269,12 @@ export default {
|
|||||||
},
|
},
|
||||||
subscribeToObject() {
|
subscribeToObject() {
|
||||||
this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) {
|
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);
|
this.updateView(datum);
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
@ -112,7 +112,7 @@ describe("The Duplicate Action plugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be defined", () => {
|
it("should be defined", () => {
|
||||||
|
@ -90,14 +90,12 @@ export default {
|
|||||||
this.composition.load();
|
this.composition.load();
|
||||||
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
|
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
|
||||||
this.unobserveGlobalFilters = this.openmct.objects.observe(this.providedObject, 'configuration.globalFilters', this.updateGlobalFilters);
|
this.unobserveGlobalFilters = this.openmct.objects.observe(this.providedObject, 'configuration.globalFilters', this.updateGlobalFilters);
|
||||||
this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject);
|
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.composition.off('add', this.addChildren);
|
this.composition.off('add', this.addChildren);
|
||||||
this.composition.off('remove', this.removeChildren);
|
this.composition.off('remove', this.removeChildren);
|
||||||
this.unobserve();
|
this.unobserve();
|
||||||
this.unobserveGlobalFilters();
|
this.unobserveGlobalFilters();
|
||||||
this.unobserveAllMutation();
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addChildren(domainObject) {
|
addChildren(domainObject) {
|
||||||
@ -158,8 +156,10 @@ export default {
|
|||||||
},
|
},
|
||||||
getGlobalFiltersToRemove(keyString) {
|
getGlobalFiltersToRemove(keyString) {
|
||||||
let filtersToRemove = new Set();
|
let filtersToRemove = new Set();
|
||||||
|
const child = this.children[keyString];
|
||||||
this.children[keyString].metadataWithFilters.forEach(metadatum => {
|
if (child && child.metadataWithFilters) {
|
||||||
|
const metadataWithFilters = child.metadataWithFilters;
|
||||||
|
metadataWithFilters.forEach(metadatum => {
|
||||||
let keepFilter = false;
|
let keepFilter = false;
|
||||||
Object.keys(this.children).forEach(childKeyString => {
|
Object.keys(this.children).forEach(childKeyString => {
|
||||||
if (childKeyString !== keyString) {
|
if (childKeyString !== keyString) {
|
||||||
@ -177,6 +177,7 @@ export default {
|
|||||||
filtersToRemove.add(metadatum.key);
|
filtersToRemove.add(metadatum.key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Array.from(filtersToRemove);
|
return Array.from(filtersToRemove);
|
||||||
},
|
},
|
||||||
|
@ -97,7 +97,7 @@ function ToolbarProvider(openmct) {
|
|||||||
message: `This action will remove this frame from this Flexible Layout. Do you want to continue?`,
|
message: `This action will remove this frame from this Flexible Layout. Do you want to continue?`,
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
label: 'Ok',
|
label: 'OK',
|
||||||
emphasis: 'true',
|
emphasis: 'true',
|
||||||
callback: function () {
|
callback: function () {
|
||||||
deleteFrameAction(primary.context.frameId);
|
deleteFrameAction(primary.context.frameId);
|
||||||
@ -162,7 +162,7 @@ function ToolbarProvider(openmct) {
|
|||||||
message: 'This action will permanently delete this container from this Flexible Layout',
|
message: 'This action will permanently delete this container from this Flexible Layout',
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
label: 'Ok',
|
label: 'OK',
|
||||||
emphasis: 'true',
|
emphasis: 'true',
|
||||||
callback: function () {
|
callback: function () {
|
||||||
removeContainer(containerId);
|
removeContainer(containerId);
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
'is-alias': item.isAlias === true,
|
'is-alias': item.isAlias === true,
|
||||||
'c-grid-item--unknown': item.type.cssClass === undefined || item.type.cssClass.indexOf('unknown') !== -1
|
'c-grid-item--unknown': item.type.cssClass === undefined || item.type.cssClass.indexOf('unknown') !== -1
|
||||||
}, statusClass]"
|
}, statusClass]"
|
||||||
:href="objectLink"
|
@click="navigate"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c-grid-item__type-icon"
|
class="c-grid-item__type-icon"
|
||||||
@ -49,11 +49,17 @@ import statusListener from './status-listener';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [contextMenuGesture, objectLink, statusListener],
|
mixins: [contextMenuGesture, objectLink, statusListener],
|
||||||
|
inject: ['openmct'],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
navigate() {
|
||||||
|
this.openmct.router.navigate(this.objectLink);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
ref="objectLink"
|
ref="objectLink"
|
||||||
class="c-object-label"
|
class="c-object-label"
|
||||||
:class="[statusClass]"
|
:class="[statusClass]"
|
||||||
:href="objectLink"
|
@click="navigate"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c-object-label__type-icon c-list-item__name__type-icon"
|
class="c-object-label__type-icon c-list-item__name__type-icon"
|
||||||
@ -45,6 +45,7 @@ import statusListener from './status-listener';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [contextMenuGesture, objectLink, statusListener],
|
mixins: [contextMenuGesture, objectLink, statusListener],
|
||||||
|
inject: ['openmct'],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -56,7 +57,7 @@ export default {
|
|||||||
return moment(timestamp).format(format);
|
return moment(timestamp).format(format);
|
||||||
},
|
},
|
||||||
navigate() {
|
navigate() {
|
||||||
this.$refs.objectLink.click();
|
this.openmct.router.navigate(this.objectLink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -19,6 +19,10 @@
|
|||||||
margin: 0 $interiorMargin $interiorMargin 0;
|
margin: 0 $interiorMargin $interiorMargin 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.mobile & {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************* GRID ITEMS */
|
/******************************* GRID ITEMS */
|
||||||
|
@ -41,7 +41,7 @@ export default class GoToOriginalAction {
|
|||||||
.slice(1)
|
.slice(1)
|
||||||
.join('/');
|
.join('/');
|
||||||
|
|
||||||
window.location.href = url;
|
this._openmct.router.navigate(url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
appliesTo(objectPath) {
|
appliesTo(objectPath) {
|
||||||
|
@ -47,7 +47,6 @@ describe("the plugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('when invoked', () => {
|
describe('when invoked', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockObjectPath = [{
|
mockObjectPath = [{
|
||||||
name: 'mock folder',
|
name: 'mock folder',
|
||||||
@ -63,11 +62,15 @@ describe("the plugin", () => {
|
|||||||
key: 'test'
|
key: 'test'
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
goToFolderAction.invoke(mockObjectPath);
|
goToFolderAction.invoke(mockObjectPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('goes to the original location', () => {
|
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');
|
expect(window.location.href).toContain('context.html#/browse/?tc.mode=fixed&tc.startBound=0&tc.endBound=1&tc.timeSystem=utc');
|
||||||
|
done();
|
||||||
|
}, 1500);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="c-compass"
|
class="c-compass"
|
||||||
:style="compassDimensionsStyle"
|
:style="`width: ${ sizedImageDimensions.width }px; height: ${ sizedImageDimensions.height }px`"
|
||||||
>
|
>
|
||||||
<CompassHUD
|
<CompassHUD
|
||||||
v-if="hasCameraFieldOfView"
|
v-if="hasCameraFieldOfView"
|
||||||
@ -34,6 +34,7 @@
|
|||||||
<CompassRose
|
<CompassRose
|
||||||
v-if="hasCameraFieldOfView"
|
v-if="hasCameraFieldOfView"
|
||||||
:heading="heading"
|
:heading="heading"
|
||||||
|
:sized-image-width="sizedImageDimensions.width"
|
||||||
:sun-heading="sunHeading"
|
:sun-heading="sunHeading"
|
||||||
:camera-angle-of-view="cameraAngleOfView"
|
:camera-angle-of-view="cameraAngleOfView"
|
||||||
:camera-pan="cameraPan"
|
:camera-pan="cameraPan"
|
||||||
@ -77,6 +78,20 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
sizedImageDimensions() {
|
||||||
|
let sizedImageDimensions = {};
|
||||||
|
if ((this.containerWidth / this.containerHeight) > this.naturalAspectRatio) {
|
||||||
|
// container is wider than image
|
||||||
|
sizedImageDimensions.width = this.containerHeight * this.naturalAspectRatio;
|
||||||
|
sizedImageDimensions.height = this.containerHeight;
|
||||||
|
} else {
|
||||||
|
// container is taller than image
|
||||||
|
sizedImageDimensions.width = this.containerWidth;
|
||||||
|
sizedImageDimensions.height = this.containerWidth * this.naturalAspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sizedImageDimensions;
|
||||||
|
},
|
||||||
hasCameraFieldOfView() {
|
hasCameraFieldOfView() {
|
||||||
return this.cameraPan !== undefined && this.cameraAngleOfView > 0;
|
return this.cameraPan !== undefined && this.cameraAngleOfView > 0;
|
||||||
},
|
},
|
||||||
@ -94,25 +109,6 @@ export default {
|
|||||||
},
|
},
|
||||||
cameraAngleOfView() {
|
cameraAngleOfView() {
|
||||||
return CAMERA_ANGLE_OF_VIEW;
|
return CAMERA_ANGLE_OF_VIEW;
|
||||||
},
|
|
||||||
compassDimensionsStyle() {
|
|
||||||
const containerAspectRatio = this.containerWidth / this.containerHeight;
|
|
||||||
|
|
||||||
let width;
|
|
||||||
let height;
|
|
||||||
|
|
||||||
if (containerAspectRatio < this.naturalAspectRatio) {
|
|
||||||
width = '100%';
|
|
||||||
height = `${ this.containerWidth / this.naturalAspectRatio }px`;
|
|
||||||
} else {
|
|
||||||
width = `${ this.containerHeight * this.naturalAspectRatio }px`;
|
|
||||||
height = '100%';
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
width: width,
|
|
||||||
height: height
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -21,6 +21,10 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div
|
||||||
|
class="w-direction-rose"
|
||||||
|
:class="compassRoseSizingClasses"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="c-direction-rose"
|
class="c-direction-rose"
|
||||||
@click="toggleLockCompass"
|
@click="toggleLockCompass"
|
||||||
@ -70,7 +74,7 @@
|
|||||||
>
|
>
|
||||||
<polygon
|
<polygon
|
||||||
class="c-nsew__tick c-tick-n"
|
class="c-nsew__tick c-tick-n"
|
||||||
points="50,0 57,5 43,5"
|
points="50,0 60,10 40,10"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
class="c-nsew__tick c-tick-e"
|
class="c-nsew__tick c-tick-e"
|
||||||
@ -148,6 +152,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -155,6 +160,10 @@ import { rotate } from './utils';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
sizedImageWidth: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
heading: {
|
heading: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true
|
required: true
|
||||||
@ -177,12 +186,24 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
north() {
|
compassRoseSizingClasses() {
|
||||||
return this.lockCompass ? rotate(-this.cameraPan) : 0;
|
let compassRoseSizingClasses = '';
|
||||||
|
if (this.sizedImageWidth < 300) {
|
||||||
|
compassRoseSizingClasses = '--rose-small --rose-min';
|
||||||
|
} else if (this.sizedImageWidth < 500) {
|
||||||
|
compassRoseSizingClasses = '--rose-small';
|
||||||
|
} else if (this.sizedImageWidth > 1000) {
|
||||||
|
compassRoseSizingClasses = '--rose-max';
|
||||||
|
}
|
||||||
|
|
||||||
|
return compassRoseSizingClasses;
|
||||||
},
|
},
|
||||||
compassRoseStyle() {
|
compassRoseStyle() {
|
||||||
return { transform: `rotate(${ this.north }deg)` };
|
return { transform: `rotate(${ this.north }deg)` };
|
||||||
},
|
},
|
||||||
|
north() {
|
||||||
|
return this.lockCompass ? rotate(-this.cameraPan) : 0;
|
||||||
|
},
|
||||||
northTextTransform() {
|
northTextTransform() {
|
||||||
return this.cardinalPointsTextTransform.north;
|
return this.cardinalPointsTextTransform.north;
|
||||||
},
|
},
|
||||||
@ -204,10 +225,10 @@ export default {
|
|||||||
const rotation = `rotate(${ -this.north })`;
|
const rotation = `rotate(${ -this.north })`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
north: `translate(50,15) ${ rotation }`,
|
north: `translate(50,23) ${ rotation }`,
|
||||||
east: `translate(87,50) ${ rotation }`,
|
east: `translate(82,50) ${ rotation }`,
|
||||||
south: `translate(13,50) ${ rotation }`,
|
south: `translate(18,50) ${ rotation }`,
|
||||||
west: `translate(50,87) ${ rotation }`
|
west: `translate(50,82) ${ rotation }`
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
hasHeading() {
|
hasHeading() {
|
||||||
|
@ -10,6 +10,7 @@ $elemBg: rgba(black, 0.7);
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c-compass {
|
.c-compass {
|
||||||
|
pointer-events: none; // This allows the image element to receive a browser-level context click
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@ -27,7 +28,9 @@ $elemBg: rgba(black, 0.7);
|
|||||||
color: $interfaceKeyColor;
|
color: $interfaceKeyColor;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: $m; right: $m; left: $m;
|
top: $m;
|
||||||
|
right: $m;
|
||||||
|
left: $m;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
|
|
||||||
svg, div {
|
svg, div {
|
||||||
@ -47,7 +50,10 @@ $elemBg: rgba(black, 0.7);
|
|||||||
border: 1px solid $interfaceKeyColor;
|
border: 1px solid $interfaceKeyColor;
|
||||||
border-top-color: transparent;
|
border-top-color: transparent;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%; right: $padLR; bottom: $padTB; left: $padLR;
|
top: 50%;
|
||||||
|
right: $padLR;
|
||||||
|
bottom: $padTB;
|
||||||
|
left: $padLR;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="__dir"] {
|
[class*="__dir"] {
|
||||||
@ -69,7 +75,8 @@ $elemBg: rgba(black, 0.7);
|
|||||||
$s: 10px;
|
$s: 10px;
|
||||||
@include sun('circle farthest-side at bottom');
|
@include sun('circle farthest-side at bottom');
|
||||||
bottom: $padTB + 2px;
|
bottom: $padTB + 2px;
|
||||||
height: $s; width: $s*2;
|
height: $s;
|
||||||
|
width: $s*2;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@ -80,10 +87,13 @@ $elemBg: rgba(black, 0.7);
|
|||||||
/***************************** COMPASS DIRECTIONS */
|
/***************************** COMPASS DIRECTIONS */
|
||||||
.c-nsew {
|
.c-nsew {
|
||||||
$color: $interfaceKeyColor;
|
$color: $interfaceKeyColor;
|
||||||
$inset: 7%;
|
$inset: 5%;
|
||||||
$tickHeightPerc: 15%;
|
$tickHeightPerc: 15%;
|
||||||
text-shadow: black 0 0 10px;
|
text-shadow: black 0 0 10px;
|
||||||
top: $inset; right: $inset; bottom: $inset; left: $inset;
|
top: $inset;
|
||||||
|
right: $inset;
|
||||||
|
bottom: $inset;
|
||||||
|
left: $inset;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
|
||||||
&__tick,
|
&__tick,
|
||||||
@ -99,19 +109,19 @@ $elemBg: rgba(black, 0.7);
|
|||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
dominant-baseline: central;
|
dominant-baseline: central;
|
||||||
font-size: 0.8em;
|
font-size: 1.25em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-label-n {
|
.c-label-n {
|
||||||
font-size: 1.1em;
|
font-size: 2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/***************************** CAMERA FIELD ANGLE */
|
/***************************** CAMERA FIELD ANGLE */
|
||||||
.c-cam-field {
|
.c-cam-field {
|
||||||
$color: white;
|
$color: white;
|
||||||
opacity: 0.2;
|
opacity: 0.3;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@ -135,6 +145,7 @@ $elemBg: rgba(black, 0.7);
|
|||||||
// clip-paths overlap a bit to avoid a gap between halves
|
// clip-paths overlap a bit to avoid a gap between halves
|
||||||
&-l {
|
&-l {
|
||||||
clip-path: polygon(0 0, 50.5% 0, 50.5% 100%, 0 100%);
|
clip-path: polygon(0 0, 50.5% 0, 50.5% 100%, 0 100%);
|
||||||
|
|
||||||
.cam-field-area {
|
.cam-field-area {
|
||||||
transform-origin: left center;
|
transform-origin: left center;
|
||||||
}
|
}
|
||||||
@ -142,6 +153,7 @@ $elemBg: rgba(black, 0.7);
|
|||||||
|
|
||||||
&-r {
|
&-r {
|
||||||
clip-path: polygon(49.5% 0, 100% 0, 100% 100%, 49.5% 100%);
|
clip-path: polygon(49.5% 0, 100% 0, 100% 100%, 49.5% 100%);
|
||||||
|
|
||||||
.cam-field-area {
|
.cam-field-area {
|
||||||
transform-origin: right center;
|
transform-origin: right center;
|
||||||
}
|
}
|
||||||
@ -155,10 +167,13 @@ $elemBg: rgba(black, 0.7);
|
|||||||
$s: 30%;
|
$s: 30%;
|
||||||
background: $color;
|
background: $color;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
height: $s; width: $s;
|
height: $s;
|
||||||
left: 50%; top: 50%;
|
width: $s;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
transform-origin: center top;
|
transform-origin: center top;
|
||||||
|
transform: translateX(-50%); // center by default, overridden by CompassRose.vue / headingStyle()
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
// Direction arrow
|
// Direction arrow
|
||||||
@ -169,24 +184,66 @@ $elemBg: rgba(black, 0.7);
|
|||||||
content: '';
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10%; right: 20%; bottom: 50%; left: 20%;
|
top: 10%;
|
||||||
|
right: 20%;
|
||||||
|
bottom: 50%;
|
||||||
|
left: 20%;
|
||||||
clip-path: polygon(50% 0, 100% $arwPointerY, 100%-$arwBodyOffset $arwPointerY, 100%-$arwBodyOffset 100%, $arwBodyOffset 100%, $arwBodyOffset $arwPointerY, 0 $arwPointerY);
|
clip-path: polygon(50% 0, 100% $arwPointerY, 100%-$arwBodyOffset $arwPointerY, 100%-$arwBodyOffset 100%, $arwBodyOffset 100%, $arwBodyOffset $arwPointerY, 0 $arwPointerY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/***************************** DIRECTION ROSE */
|
/***************************** DIRECTION ROSE */
|
||||||
|
.w-direction-rose {
|
||||||
|
$s: 10%;
|
||||||
|
$m: 2%;
|
||||||
|
position: absolute;
|
||||||
|
bottom: $m;
|
||||||
|
left: $m;
|
||||||
|
width: $s;
|
||||||
|
padding-top: $s;
|
||||||
|
|
||||||
|
&.--rose-min {
|
||||||
|
$s: 30px;
|
||||||
|
width: $s;
|
||||||
|
padding-top: $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.--rose-small {
|
||||||
|
.c-nsew__minor-ticks,
|
||||||
|
.c-tick-w,
|
||||||
|
.c-tick-s,
|
||||||
|
.c-tick-e,
|
||||||
|
.c-label-w,
|
||||||
|
.c-label-s,
|
||||||
|
.c-label-e {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-label-n {
|
||||||
|
font-size: 2.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.--rose-max {
|
||||||
|
$s: 100px;
|
||||||
|
width: $s;
|
||||||
|
padding-top: $s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.c-direction-rose {
|
.c-direction-rose {
|
||||||
$d: 100px;
|
|
||||||
$c2: rgba(white, 0.1);
|
$c2: rgba(white, 0.1);
|
||||||
background: $elemBg;
|
background: $elemBg;
|
||||||
background-image: radial-gradient(circle closest-side, transparent, transparent 80%, $c2);
|
background-image: radial-gradient(circle closest-side, transparent, transparent 80%, $c2);
|
||||||
width: $d;
|
|
||||||
height: $d;
|
|
||||||
transform-origin: 0 0;
|
transform-origin: 0 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 10px; left: 10px;
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
clip-path: circle(50% at 50% 50%);
|
clip-path: circle(50% at 50% 50%);
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
svg, div {
|
svg, div {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -206,8 +263,10 @@ $elemBg: rgba(black, 0.7);
|
|||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
top: 0; left: 50%;
|
top: 0;
|
||||||
height:$s; width: $s;
|
left: 50%;
|
||||||
|
height: $s;
|
||||||
|
width: $s;
|
||||||
transform: translate(-50%, -60%);
|
transform: translate(-50%, -60%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,10 +134,15 @@
|
|||||||
class="c-imagery__thumb c-thumb"
|
class="c-imagery__thumb c-thumb"
|
||||||
:class="{ selected: focusedImageIndex === index && isPaused }"
|
:class="{ selected: focusedImageIndex === index && isPaused }"
|
||||||
@click="setFocusedImage(index, thumbnailClick)"
|
@click="setFocusedImage(index, thumbnailClick)"
|
||||||
|
>
|
||||||
|
<a href=""
|
||||||
|
:download="image.imageDownloadName"
|
||||||
|
@click.prevent
|
||||||
>
|
>
|
||||||
<img class="c-thumb__image"
|
<img class="c-thumb__image"
|
||||||
:src="image.url"
|
:src="image.url"
|
||||||
>
|
>
|
||||||
|
</a>
|
||||||
<div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
|
<div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -218,6 +223,9 @@ export default {
|
|||||||
canTrackDuration() {
|
canTrackDuration() {
|
||||||
return this.openmct.time.clock() && this.timeSystem.isUTCBased;
|
return this.openmct.time.clock() && this.timeSystem.isUTCBased;
|
||||||
},
|
},
|
||||||
|
focusedImageDownloadName() {
|
||||||
|
return this.getImageDownloadName(this.focusedImage);
|
||||||
|
},
|
||||||
isNextDisabled() {
|
isNextDisabled() {
|
||||||
let disabled = false;
|
let disabled = false;
|
||||||
|
|
||||||
@ -345,6 +353,7 @@ export default {
|
|||||||
this.imageHints = { ...this.metadata.valuesForHints(['image'])[0] };
|
this.imageHints = { ...this.metadata.valuesForHints(['image'])[0] };
|
||||||
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageHints);
|
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageHints);
|
||||||
|
this.imageDownloadNameHints = { ...this.metadata.valuesForHints(['imageDownloadName'])[0]};
|
||||||
|
|
||||||
// related telemetry keys
|
// related telemetry keys
|
||||||
this.spacecraftPositionKeys = ['positionX', 'positionY', 'positionZ'];
|
this.spacecraftPositionKeys = ['positionX', 'positionY', 'positionZ'];
|
||||||
@ -381,7 +390,9 @@ export default {
|
|||||||
delete this.unsubscribe;
|
delete this.unsubscribe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.imageContainerResizeObserver) {
|
||||||
this.imageContainerResizeObserver.disconnect();
|
this.imageContainerResizeObserver.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.relatedTelemetry.hasRelatedTelemetry) {
|
if (this.relatedTelemetry.hasRelatedTelemetry) {
|
||||||
this.relatedTelemetry.destroy();
|
this.relatedTelemetry.destroy();
|
||||||
@ -532,6 +543,15 @@ export default {
|
|||||||
// Replace ISO "T" with a space to allow wrapping
|
// Replace ISO "T" with a space to allow wrapping
|
||||||
return dateTimeStr.replace("T", " ");
|
return dateTimeStr.replace("T", " ");
|
||||||
},
|
},
|
||||||
|
getImageDownloadName(datum) {
|
||||||
|
let imageDownloadName = '';
|
||||||
|
if (datum) {
|
||||||
|
const key = this.imageDownloadNameHints.key;
|
||||||
|
imageDownloadName = datum[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageDownloadName;
|
||||||
|
},
|
||||||
parseTime(datum) {
|
parseTime(datum) {
|
||||||
if (!datum) {
|
if (!datum) {
|
||||||
return;
|
return;
|
||||||
@ -655,6 +675,7 @@ export default {
|
|||||||
image.formattedTime = this.formatTime(datum);
|
image.formattedTime = this.formatTime(datum);
|
||||||
image.url = this.formatImageUrl(datum);
|
image.url = this.formatImageUrl(datum);
|
||||||
image.time = datum[this.timeKey];
|
image.time = datum[this.timeKey];
|
||||||
|
image.imageDownloadName = this.getImageDownloadName(datum);
|
||||||
|
|
||||||
this.imageHistory.push(image);
|
this.imageHistory.push(image);
|
||||||
|
|
||||||
@ -683,7 +704,7 @@ export default {
|
|||||||
window.clearInterval(this.durationTracker);
|
window.clearInterval(this.durationTracker);
|
||||||
},
|
},
|
||||||
updateDuration() {
|
updateDuration() {
|
||||||
let currentTime = this.openmct.time.clock().currentValue();
|
let currentTime = this.openmct.time.clock() && this.openmct.time.clock().currentValue();
|
||||||
this.numericDuration = currentTime - this.parsedSelectedTime;
|
this.numericDuration = currentTime - this.parsedSelectedTime;
|
||||||
},
|
},
|
||||||
resetAgeCSS() {
|
resetAgeCSS() {
|
||||||
@ -777,6 +798,9 @@ export default {
|
|||||||
this.focusedImageNaturalAspectRatio = undefined;
|
this.focusedImageNaturalAspectRatio = undefined;
|
||||||
|
|
||||||
const img = this.$refs.focusedImage;
|
const img = this.$refs.focusedImage;
|
||||||
|
if (!img) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO - should probably cache this
|
// TODO - should probably cache this
|
||||||
img.addEventListener('load', () => {
|
img.addEventListener('load', () => {
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import ImageryPlugin from './plugin.js';
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import {
|
import {
|
||||||
createOpenMct,
|
createOpenMct,
|
||||||
@ -89,15 +89,11 @@ describe("The Imagery View Layout", () => {
|
|||||||
const START = Date.now();
|
const START = Date.now();
|
||||||
const COUNT = 10;
|
const COUNT = 10;
|
||||||
|
|
||||||
|
let resolveFunction;
|
||||||
|
|
||||||
let openmct;
|
let openmct;
|
||||||
let imageryPlugin;
|
|
||||||
let parent;
|
let parent;
|
||||||
let child;
|
let child;
|
||||||
let timeFormat = 'utc';
|
|
||||||
let bounds = {
|
|
||||||
start: START - TEN_MINUTES,
|
|
||||||
end: START
|
|
||||||
};
|
|
||||||
let imageTelemetry = generateTelemetry(START - TEN_MINUTES, COUNT);
|
let imageTelemetry = generateTelemetry(START - TEN_MINUTES, COUNT);
|
||||||
let imageryObject = {
|
let imageryObject = {
|
||||||
identifier: {
|
identifier: {
|
||||||
@ -205,6 +201,10 @@ describe("The Imagery View Layout", () => {
|
|||||||
|
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
|
|
||||||
|
openmct.install(openmct.plugins.MyItems());
|
||||||
|
openmct.install(openmct.plugins.LocalTimeSystem());
|
||||||
|
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||||
|
|
||||||
parent = document.createElement('div');
|
parent = document.createElement('div');
|
||||||
child = document.createElement('div');
|
child = document.createElement('div');
|
||||||
parent.appendChild(child);
|
parent.appendChild(child);
|
||||||
@ -215,22 +215,18 @@ describe("The Imagery View Layout", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
||||||
|
|
||||||
imageryPlugin = new ImageryPlugin();
|
|
||||||
openmct.install(imageryPlugin);
|
|
||||||
|
|
||||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
||||||
|
|
||||||
openmct.time.timeSystem(timeFormat, {
|
|
||||||
start: 0,
|
|
||||||
end: 4
|
|
||||||
});
|
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.startHeadless(appHolder);
|
openmct.start(appHolder);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
openmct.time.timeSystem('utc', {
|
||||||
|
start: 0,
|
||||||
|
end: 1
|
||||||
|
});
|
||||||
|
|
||||||
return resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -248,7 +244,7 @@ describe("The Imagery View Layout", () => {
|
|||||||
let imageryViewProvider;
|
let imageryViewProvider;
|
||||||
let imageryView;
|
let imageryView;
|
||||||
|
|
||||||
beforeEach(async (done) => {
|
beforeEach(async () => {
|
||||||
let telemetryRequestResolve;
|
let telemetryRequestResolve;
|
||||||
let telemetryRequestPromise = new Promise((resolve) => {
|
let telemetryRequestPromise = new Promise((resolve) => {
|
||||||
telemetryRequestResolve = resolve;
|
telemetryRequestResolve = resolve;
|
||||||
@ -260,23 +256,18 @@ describe("The Imagery View Layout", () => {
|
|||||||
return telemetryRequestPromise;
|
return telemetryRequestPromise;
|
||||||
});
|
});
|
||||||
|
|
||||||
openmct.time.clock('local', {
|
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end + 100
|
|
||||||
});
|
|
||||||
|
|
||||||
applicableViews = openmct.objectViews.get(imageryObject, []);
|
applicableViews = openmct.objectViews.get(imageryObject, []);
|
||||||
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
|
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
|
||||||
imageryView = imageryViewProvider.view(imageryObject);
|
imageryView = imageryViewProvider.view(imageryObject);
|
||||||
imageryView.show(child);
|
imageryView.show(child);
|
||||||
|
|
||||||
await telemetryRequestPromise;
|
await telemetryRequestPromise;
|
||||||
await Vue.nextTick();
|
|
||||||
|
|
||||||
return done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
openmct.time.stopClock();
|
||||||
|
openmct.router.removeListener('change:hash', resolveFunction);
|
||||||
|
|
||||||
imageryView.destroy();
|
imageryView.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -286,33 +277,33 @@ describe("The Imagery View Layout", () => {
|
|||||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
|
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show the clicked thumbnail as the main image", async () => {
|
it("should show the clicked thumbnail as the main image", (done) => {
|
||||||
const target = imageTelemetry[5].url;
|
const target = imageTelemetry[5].url;
|
||||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||||
await Vue.nextTick();
|
Vue.nextTick(() => {
|
||||||
const imageInfo = getImageInfo(parent);
|
const imageInfo = getImageInfo(parent);
|
||||||
|
|
||||||
expect(imageInfo.url.indexOf(imageTelemetry[5].timeId)).not.toEqual(-1);
|
expect(imageInfo.url.indexOf(imageTelemetry[5].timeId)).not.toEqual(-1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show that an image is new", async (done) => {
|
xit("should show that an image is new", (done) => {
|
||||||
await Vue.nextTick();
|
Vue.nextTick(() => {
|
||||||
|
|
||||||
// used in code, need to wait to the 500ms here too
|
// used in code, need to wait to the 500ms here too
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const imageIsNew = isNew(parent);
|
const imageIsNew = isNew(parent);
|
||||||
|
|
||||||
expect(imageIsNew).toBeTrue();
|
expect(imageIsNew).toBeTrue();
|
||||||
done();
|
done();
|
||||||
}, REFRESH_CSS_MS);
|
}, REFRESH_CSS_MS);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("should show that an image is not new", async (done) => {
|
xit("should show that an image is not new", (done) => {
|
||||||
const target = imageTelemetry[2].url;
|
const target = imageTelemetry[2].url;
|
||||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||||
|
|
||||||
await Vue.nextTick();
|
Vue.nextTick(() => {
|
||||||
|
|
||||||
// used in code, need to wait to the 500ms here too
|
// used in code, need to wait to the 500ms here too
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const imageIsNew = isNew(parent);
|
const imageIsNew = isNew(parent);
|
||||||
@ -321,8 +312,9 @@ describe("The Imagery View Layout", () => {
|
|||||||
done();
|
done();
|
||||||
}, REFRESH_CSS_MS);
|
}, REFRESH_CSS_MS);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("should navigate via arrow keys", async () => {
|
it("should navigate via arrow keys", (done) => {
|
||||||
let keyOpts = {
|
let keyOpts = {
|
||||||
element: parent.querySelector('.c-imagery'),
|
element: parent.querySelector('.c-imagery'),
|
||||||
key: 'ArrowLeft',
|
key: 'ArrowLeft',
|
||||||
@ -332,14 +324,15 @@ describe("The Imagery View Layout", () => {
|
|||||||
|
|
||||||
simulateKeyEvent(keyOpts);
|
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 element = parent.querySelector('.c-imagery');
|
||||||
let type = 'keyup';
|
let type = 'keyup';
|
||||||
let leftKeyOpts = {
|
let leftKeyOpts = {
|
||||||
@ -362,12 +355,12 @@ describe("The Imagery View Layout", () => {
|
|||||||
// right once
|
// right once
|
||||||
simulateKeyEvent(rightKeyOpts);
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -55,7 +55,7 @@ describe("The local time", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
localTimeSystem = openmct.time.timeSystem(LOCAL_SYSTEM_KEY, {
|
localTimeSystem = openmct.time.timeSystem(LOCAL_SYSTEM_KEY, {
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 4
|
end: 1
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ describe("The Move Action plugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be defined", () => {
|
it("should be defined", () => {
|
||||||
|
@ -135,6 +135,7 @@ import SearchResults from './SearchResults.vue';
|
|||||||
import Sidebar from './Sidebar.vue';
|
import Sidebar from './Sidebar.vue';
|
||||||
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
|
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
|
||||||
import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
|
import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
|
||||||
|
import { NOTEBOOK_VIEW_TYPE } from '../notebook-constants';
|
||||||
import objectUtils from 'objectUtils';
|
import objectUtils from 'objectUtils';
|
||||||
|
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
@ -189,14 +190,14 @@ export default {
|
|||||||
selectedPage() {
|
selectedPage() {
|
||||||
const pages = this.getPages();
|
const pages = this.getPages();
|
||||||
if (!pages) {
|
if (!pages) {
|
||||||
return null;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return pages.find(page => page.isSelected);
|
return pages.find(page => page.isSelected);
|
||||||
},
|
},
|
||||||
selectedSection() {
|
selectedSection() {
|
||||||
if (!this.sections.length) {
|
if (!this.sections.length) {
|
||||||
return null;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.sections.find(section => section.isSelected);
|
return this.sections.find(section => section.isSelected);
|
||||||
@ -216,6 +217,7 @@ export default {
|
|||||||
|
|
||||||
window.addEventListener('orientationchange', this.formatSidebar);
|
window.addEventListener('orientationchange', this.formatSidebar);
|
||||||
window.addEventListener("hashchange", this.navigateToSectionPage, false);
|
window.addEventListener("hashchange", this.navigateToSectionPage, false);
|
||||||
|
this.openmct.router.on('change:params', this.changeSectionPage);
|
||||||
|
|
||||||
this.navigateToSectionPage();
|
this.navigateToSectionPage();
|
||||||
},
|
},
|
||||||
@ -226,6 +228,7 @@ export default {
|
|||||||
|
|
||||||
window.removeEventListener('orientationchange', this.formatSidebar);
|
window.removeEventListener('orientationchange', this.formatSidebar);
|
||||||
window.removeEventListener("hashchange", this.navigateToSectionPage);
|
window.removeEventListener("hashchange", this.navigateToSectionPage);
|
||||||
|
this.openmct.router.off('change:params', this.changeSectionPage);
|
||||||
},
|
},
|
||||||
updated: function () {
|
updated: function () {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@ -233,6 +236,28 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
changeSectionPage(newParams, oldParams, changedParams) {
|
||||||
|
if (newParams.view !== NOTEBOOK_VIEW_TYPE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pageId = newParams.pageId;
|
||||||
|
let sectionId = newParams.sectionId;
|
||||||
|
|
||||||
|
if (!pageId && !sectionId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sections.forEach(section => {
|
||||||
|
section.isSelected = Boolean(section.id === sectionId);
|
||||||
|
|
||||||
|
if (section.isSelected) {
|
||||||
|
section.pages.forEach(page => {
|
||||||
|
page.isSelected = Boolean(page.id === pageId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
changeSelectedSection({ sectionId, pageId }) {
|
changeSelectedSection({ sectionId, pageId }) {
|
||||||
const sections = this.sections.map(s => {
|
const sections = this.sections.map(s => {
|
||||||
s.isSelected = false;
|
s.isSelected = false;
|
||||||
@ -430,7 +455,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check for no entries first
|
// check for no entries first
|
||||||
if (entries[section.id]) {
|
if (entries[section.id] && entries[section.id][page.id]) {
|
||||||
const pageEntries = entries[section.id][page.id];
|
const pageEntries = entries[section.id][page.id];
|
||||||
|
|
||||||
pageEntries.forEach(entry => {
|
pageEntries.forEach(entry => {
|
||||||
@ -518,9 +543,11 @@ export default {
|
|||||||
return this.sections.find(section => section.isSelected);
|
return this.sections.find(section => section.isSelected);
|
||||||
},
|
},
|
||||||
navigateToSectionPage() {
|
navigateToSectionPage() {
|
||||||
const { pageId, sectionId } = this.openmct.router.getParams();
|
let { pageId, sectionId } = this.openmct.router.getParams();
|
||||||
|
|
||||||
if (!pageId || !sectionId) {
|
if (!pageId || !sectionId) {
|
||||||
return;
|
sectionId = this.selectedSection.id;
|
||||||
|
pageId = this.selectedPage.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sections = this.sections.map(s => {
|
const sections = this.sections.map(s => {
|
||||||
|
@ -145,7 +145,7 @@ export default {
|
|||||||
|
|
||||||
const relativeHash = hash.slice(hash.indexOf('#'));
|
const relativeHash = hash.slice(hash.indexOf('#'));
|
||||||
const url = new URL(relativeHash, `${location.protocol}//${location.host}${location.pathname}`);
|
const url = new URL(relativeHash, `${location.protocol}//${location.host}${location.pathname}`);
|
||||||
window.location.hash = url.hash;
|
this.openmct.router.navigate(url.hash);
|
||||||
},
|
},
|
||||||
formatTime(unixTime, timeFormat) {
|
formatTime(unixTime, timeFormat) {
|
||||||
return Moment.utc(unixTime).format(timeFormat);
|
return Moment.utc(unixTime).format(timeFormat);
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="c-notebook__search-results">
|
<div class="c-notebook__search-results">
|
||||||
<div class="c-notebook__search-results__header">Search Results</div>
|
<div class="c-notebook__search-results__header">Search Results ({{ results.length }})</div>
|
||||||
<div class="c-notebook__entries">
|
<div class="c-notebook__entries">
|
||||||
<NotebookEntry v-for="(result, index) in results"
|
<NotebookEntry v-for="(result, index) in results"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
@ -111,10 +111,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
pages() {
|
pages() {
|
||||||
const selectedSection = this.sections.find(section => section.isSelected);
|
const selectedSection = this.sections.find(section => section.isSelected);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
export const EVENT_SNAPSHOTS_UPDATED = 'SNAPSHOTS_UPDATED';
|
export const EVENT_SNAPSHOTS_UPDATED = 'SNAPSHOTS_UPDATED';
|
||||||
export const NOTEBOOK_DEFAULT = 'DEFAULT';
|
export const NOTEBOOK_DEFAULT = 'DEFAULT';
|
||||||
export const NOTEBOOK_SNAPSHOT = 'SNAPSHOT';
|
export const NOTEBOOK_SNAPSHOT = 'SNAPSHOT';
|
||||||
|
export const NOTEBOOK_VIEW_TYPE = 'notebook-vue';
|
||||||
|
@ -65,7 +65,8 @@ describe("Notebook plugin:", () => {
|
|||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
appHolder.remove();
|
appHolder.remove();
|
||||||
resetApplicationState(openmct);
|
|
||||||
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("has type as Notebook", () => {
|
it("has type as Notebook", () => {
|
||||||
|
@ -140,7 +140,8 @@ describe('Notebook Entries:', () => {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
notebookDomainObject.configuration.entries[selectedSection.id][selectedPage.id] = [];
|
notebookDomainObject.configuration.entries[selectedSection.id][selectedPage.id] = [];
|
||||||
resetApplicationState(openmct);
|
|
||||||
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getNotebookEntries has no entries', () => {
|
it('getNotebookEntries has no entries', () => {
|
||||||
|
@ -83,7 +83,7 @@ describe('Notebook Storage:', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has empty local Storage', () => {
|
it('has empty local Storage', () => {
|
||||||
|
@ -26,6 +26,7 @@ import CouchObjectQueue from "./CouchObjectQueue";
|
|||||||
const REV = "_rev";
|
const REV = "_rev";
|
||||||
const ID = "_id";
|
const ID = "_id";
|
||||||
const HEARTBEAT = 50000;
|
const HEARTBEAT = 50000;
|
||||||
|
const ALL_DOCS = "_all_docs?include_docs=true";
|
||||||
|
|
||||||
export default class CouchObjectProvider {
|
export default class CouchObjectProvider {
|
||||||
// options {
|
// options {
|
||||||
@ -41,6 +42,8 @@ export default class CouchObjectProvider {
|
|||||||
this.objectQueue = {};
|
this.objectQueue = {};
|
||||||
this.observeEnabled = options.disableObserve !== true;
|
this.observeEnabled = options.disableObserve !== true;
|
||||||
this.observers = {};
|
this.observers = {};
|
||||||
|
this.batchIds = [];
|
||||||
|
|
||||||
if (this.observeEnabled) {
|
if (this.observeEnabled) {
|
||||||
this.observeObjectChanges(options.filter);
|
this.observeObjectChanges(options.filter);
|
||||||
}
|
}
|
||||||
@ -67,6 +70,9 @@ export default class CouchObjectProvider {
|
|||||||
// stringify body if needed
|
// stringify body if needed
|
||||||
if (fetchOptions.body) {
|
if (fetchOptions.body) {
|
||||||
fetchOptions.body = JSON.stringify(fetchOptions.body);
|
fetchOptions.body = JSON.stringify(fetchOptions.body);
|
||||||
|
fetchOptions.headers = {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetch(this.url + '/' + subPath, fetchOptions)
|
return fetch(this.url + '/' + subPath, fetchOptions)
|
||||||
@ -78,14 +84,18 @@ export default class CouchObjectProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the response to a create/update/delete request;
|
/**
|
||||||
// track the rev if it's valid, otherwise return false to
|
* Check the response to a create/update/delete request;
|
||||||
// indicate that the request failed.
|
* track the rev if it's valid, otherwise return false to
|
||||||
// persist any queued objects
|
* indicate that the request failed.
|
||||||
checkResponse(response, intermediateResponse) {
|
* persist any queued objects
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
checkResponse(response, intermediateResponse, key) {
|
||||||
let requestSuccess = false;
|
let requestSuccess = false;
|
||||||
const id = response ? response.id : undefined;
|
const id = response ? response.id : undefined;
|
||||||
let rev;
|
let rev;
|
||||||
|
|
||||||
if (response && response.ok) {
|
if (response && response.ok) {
|
||||||
rev = response.rev;
|
rev = response.rev;
|
||||||
requestSuccess = true;
|
requestSuccess = true;
|
||||||
@ -103,9 +113,14 @@ export default class CouchObjectProvider {
|
|||||||
if (this.objectQueue[id].hasNext()) {
|
if (this.objectQueue[id].hasNext()) {
|
||||||
this.updateQueued(id);
|
this.updateQueued(id);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.objectQueue[key].pending = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
getModel(response) {
|
getModel(response) {
|
||||||
if (response && response.model) {
|
if (response && response.model) {
|
||||||
let key = response[ID];
|
let key = response[ID];
|
||||||
@ -119,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
|
//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) {
|
||||||
if (!this.objectQueue[key].pending && !this.objectQueue[key].rev) {
|
|
||||||
this.objectQueue[key].updateRevision(response[REV]);
|
this.objectQueue[key].updateRevision(response[REV]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,10 +145,118 @@ export default class CouchObjectProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get(identifier, abortSignal) {
|
get(identifier, abortSignal) {
|
||||||
return this.request(identifier.key, "GET", undefined, abortSignal).then(this.getModel.bind(this));
|
this.batchIds.push(identifier.key);
|
||||||
|
|
||||||
|
if (this.bulkPromise === undefined) {
|
||||||
|
this.bulkPromise = this.deferBatchedGet(abortSignal);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getObjectsByFilter(filter) {
|
return this.bulkPromise
|
||||||
|
.then((domainObjectMap) => {
|
||||||
|
return domainObjectMap[identifier.key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
deferBatchedGet(abortSignal) {
|
||||||
|
// We until the next event loop cycle to "collect" all of the get
|
||||||
|
// requests triggered in this iteration of the event loop
|
||||||
|
|
||||||
|
return this.waitOneEventCycle().then(() => {
|
||||||
|
let batchIds = this.batchIds;
|
||||||
|
|
||||||
|
this.clearBatch();
|
||||||
|
|
||||||
|
if (batchIds.length === 1) {
|
||||||
|
let objectKey = batchIds[0];
|
||||||
|
|
||||||
|
//If there's only one request, just do a regular get
|
||||||
|
return this.request(objectKey, "GET", undefined, abortSignal)
|
||||||
|
.then(this.returnAsMap(objectKey));
|
||||||
|
} else {
|
||||||
|
return this.bulkGet(batchIds, abortSignal);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
returnAsMap(objectKey) {
|
||||||
|
return (result) => {
|
||||||
|
let objectMap = {};
|
||||||
|
objectMap[objectKey] = this.getModel(result);
|
||||||
|
|
||||||
|
return objectMap;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
clearBatch() {
|
||||||
|
this.batchIds = [];
|
||||||
|
delete this.bulkPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
waitOneEventCycle() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
bulkGet(ids, signal) {
|
||||||
|
ids = this.removeDuplicates(ids);
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
'keys': ids
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.request(ALL_DOCS, 'POST', query, signal).then((response) => {
|
||||||
|
if (response && response.rows !== undefined) {
|
||||||
|
return response.rows.reduce((map, row) => {
|
||||||
|
if (row.doc !== undefined) {
|
||||||
|
map[row.key] = this.getModel(row.doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
removeDuplicates(array) {
|
||||||
|
return Array.from(new Set(array));
|
||||||
|
}
|
||||||
|
|
||||||
|
search(query, abortSignal) {
|
||||||
|
const filter = {
|
||||||
|
"selector": {
|
||||||
|
"model": {
|
||||||
|
"name": {
|
||||||
|
"$regex": `(?i)${query}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.getObjectsByFilter(filter, abortSignal);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getObjectsByFilter(filter, abortSignal) {
|
||||||
let objects = [];
|
let objects = [];
|
||||||
|
|
||||||
let url = `${this.url}/_find`;
|
let url = `${this.url}/_find`;
|
||||||
@ -149,6 +271,7 @@ export default class CouchObjectProvider {
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
|
signal: abortSignal,
|
||||||
body
|
body
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -203,6 +326,9 @@ export default class CouchObjectProvider {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
abortGetChanges() {
|
abortGetChanges() {
|
||||||
if (this.controller) {
|
if (this.controller) {
|
||||||
this.controller.abort();
|
this.controller.abort();
|
||||||
@ -212,6 +338,9 @@ export default class CouchObjectProvider {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
async observeObjectChanges(filter) {
|
async observeObjectChanges(filter) {
|
||||||
let intermediateResponse = this.getIntermediateResponse();
|
let intermediateResponse = this.getIntermediateResponse();
|
||||||
|
|
||||||
@ -292,6 +421,9 @@ export default class CouchObjectProvider {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
getIntermediateResponse() {
|
getIntermediateResponse() {
|
||||||
let intermediateResponse = {};
|
let intermediateResponse = {};
|
||||||
intermediateResponse.promise = new Promise(function (resolve, reject) {
|
intermediateResponse.promise = new Promise(function (resolve, reject) {
|
||||||
@ -302,6 +434,9 @@ export default class CouchObjectProvider {
|
|||||||
return intermediateResponse;
|
return intermediateResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
enqueueObject(key, model, intermediateResponse) {
|
enqueueObject(key, model, intermediateResponse) {
|
||||||
if (this.objectQueue[key]) {
|
if (this.objectQueue[key]) {
|
||||||
this.objectQueue[key].enqueue({
|
this.objectQueue[key].enqueue({
|
||||||
@ -324,19 +459,22 @@ export default class CouchObjectProvider {
|
|||||||
const queued = this.objectQueue[key].dequeue();
|
const queued = this.objectQueue[key].dequeue();
|
||||||
let document = new CouchDocument(key, queued.model);
|
let document = new CouchDocument(key, queued.model);
|
||||||
this.request(key, "PUT", document).then((response) => {
|
this.request(key, "PUT", document).then((response) => {
|
||||||
this.checkResponse(response, queued.intermediateResponse);
|
this.checkResponse(response, queued.intermediateResponse, key);
|
||||||
});
|
});
|
||||||
|
|
||||||
return intermediateResponse.promise;
|
return intermediateResponse.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
updateQueued(key) {
|
updateQueued(key) {
|
||||||
if (!this.objectQueue[key].pending) {
|
if (!this.objectQueue[key].pending) {
|
||||||
this.objectQueue[key].pending = true;
|
this.objectQueue[key].pending = true;
|
||||||
const queued = this.objectQueue[key].dequeue();
|
const queued = this.objectQueue[key].dequeue();
|
||||||
let document = new CouchDocument(key, queued.model, this.objectQueue[key].rev);
|
let document = new CouchDocument(key, queued.model, this.objectQueue[key].rev);
|
||||||
this.request(key, "PUT", document).then((response) => {
|
this.request(key, "PUT", document).then((response) => {
|
||||||
this.checkResponse(response, queued.intermediateResponse);
|
this.checkResponse(response, queued.intermediateResponse, key);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ import {
|
|||||||
createOpenMct,
|
createOpenMct,
|
||||||
resetApplicationState, spyOnBuiltins
|
resetApplicationState, spyOnBuiltins
|
||||||
} from 'utils/testing';
|
} from 'utils/testing';
|
||||||
import CouchObjectProvider from './CouchObjectProvider';
|
|
||||||
|
|
||||||
describe('the plugin', () => {
|
describe('the plugin', () => {
|
||||||
let openmct;
|
let openmct;
|
||||||
@ -42,7 +41,8 @@ describe('the plugin', () => {
|
|||||||
namespace: '',
|
namespace: '',
|
||||||
key: 'some-value'
|
key: 'some-value'
|
||||||
},
|
},
|
||||||
type: 'mock-type'
|
type: 'mock-type',
|
||||||
|
modified: 0
|
||||||
};
|
};
|
||||||
options = {
|
options = {
|
||||||
url: testPath,
|
url: testPath,
|
||||||
@ -95,6 +95,7 @@ describe('the plugin', () => {
|
|||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
_id: 'some-value',
|
_id: 'some-value',
|
||||||
|
id: 'some-value',
|
||||||
_rev: 1,
|
_rev: 1,
|
||||||
model: {}
|
model: {}
|
||||||
};
|
};
|
||||||
@ -104,44 +105,131 @@ describe('the plugin', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('gets an object', () => {
|
it('gets an object', () => {
|
||||||
openmct.objects.get(mockDomainObject.identifier).then((result) => {
|
return openmct.objects.get(mockDomainObject.identifier).then((result) => {
|
||||||
expect(result.identifier.key).toEqual(mockDomainObject.identifier.key);
|
expect(result.identifier.key).toEqual(mockDomainObject.identifier.key);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates an object', () => {
|
it('creates an object', () => {
|
||||||
openmct.objects.save(mockDomainObject).then((result) => {
|
return openmct.objects.save(mockDomainObject).then((result) => {
|
||||||
expect(provider.create).toHaveBeenCalled();
|
expect(provider.create).toHaveBeenCalled();
|
||||||
expect(result).toBeTrue();
|
expect(result).toBeTrue();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates an object', () => {
|
it('updates an object', (done) => {
|
||||||
openmct.objects.save(mockDomainObject).then((result) => {
|
return openmct.objects.save(mockDomainObject).then((result) => {
|
||||||
expect(result).toBeTrue();
|
expect(result).toBeTrue();
|
||||||
expect(provider.create).toHaveBeenCalled();
|
expect(provider.create).toHaveBeenCalled();
|
||||||
openmct.objects.save(mockDomainObject).then((updatedResult) => {
|
|
||||||
|
//Set modified timestamp it detects a change and persists the updated model.
|
||||||
|
mockDomainObject.modified = Date.now();
|
||||||
|
|
||||||
|
return openmct.objects.save(mockDomainObject).then((updatedResult) => {
|
||||||
expect(updatedResult).toBeTrue();
|
expect(updatedResult).toBeTrue();
|
||||||
expect(provider.update).toHaveBeenCalled();
|
expect(provider.update).toHaveBeenCalled();
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
describe('batches requests', () => {
|
||||||
|
let mockPromise;
|
||||||
|
beforeEach(() => {
|
||||||
|
mockPromise = Promise.resolve({
|
||||||
|
json: () => {
|
||||||
|
return {
|
||||||
|
total_rows: 0,
|
||||||
|
rows: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fetch.and.returnValue(mockPromise);
|
||||||
|
});
|
||||||
|
it('for multiple simultaneous gets', () => {
|
||||||
|
const objectIds = [
|
||||||
|
{
|
||||||
|
namespace: '',
|
||||||
|
key: 'object-1'
|
||||||
|
}, {
|
||||||
|
namespace: '',
|
||||||
|
key: 'object-2'
|
||||||
|
}, {
|
||||||
|
namespace: '',
|
||||||
|
key: 'object-3'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const getAllObjects = Promise.all(
|
||||||
|
objectIds.map((identifier) =>
|
||||||
|
openmct.objects.get(identifier)
|
||||||
|
));
|
||||||
|
|
||||||
|
return getAllObjects.then(() => {
|
||||||
|
const requestUrl = fetch.calls.mostRecent().args[0];
|
||||||
|
const requestMethod = fetch.calls.mostRecent().args[1].method;
|
||||||
|
|
||||||
|
expect(fetch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(requestUrl.includes('_all_docs')).toBeTrue();
|
||||||
|
expect(requestMethod).toEqual('POST');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('updates queued objects', () => {
|
it('but not for single gets', () => {
|
||||||
let couchProvider = new CouchObjectProvider(openmct, options, '');
|
const objectId = {
|
||||||
let intermediateResponse = couchProvider.getIntermediateResponse();
|
namespace: '',
|
||||||
spyOn(couchProvider, 'updateQueued');
|
key: 'object-1'
|
||||||
couchProvider.enqueueObject(mockDomainObject.identifier.key, mockDomainObject, intermediateResponse);
|
};
|
||||||
couchProvider.objectQueue[mockDomainObject.identifier.key].updateRevision(1);
|
|
||||||
couchProvider.update(mockDomainObject);
|
|
||||||
expect(couchProvider.objectQueue[mockDomainObject.identifier.key].hasNext()).toBe(2);
|
|
||||||
couchProvider.checkResponse({
|
|
||||||
ok: true,
|
|
||||||
rev: 2,
|
|
||||||
id: mockDomainObject.identifier.key
|
|
||||||
}, intermediateResponse);
|
|
||||||
|
|
||||||
expect(couchProvider.updateQueued).toHaveBeenCalledTimes(2);
|
const getObject = openmct.objects.get(objectId);
|
||||||
|
|
||||||
|
return getObject.then(() => {
|
||||||
|
const requestUrl = fetch.calls.mostRecent().args[0];
|
||||||
|
const requestMethod = fetch.calls.mostRecent().args[1].method;
|
||||||
|
|
||||||
|
expect(fetch).toHaveBeenCalledTimes(1);
|
||||||
|
expect(requestUrl.endsWith(`${objectId.key}`)).toBeTrue();
|
||||||
|
expect(requestMethod).toEqual('GET');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('implements server-side search', () => {
|
||||||
|
let mockPromise;
|
||||||
|
beforeEach(() => {
|
||||||
|
mockPromise = Promise.resolve({
|
||||||
|
body: {
|
||||||
|
getReader() {
|
||||||
|
return {
|
||||||
|
read() {
|
||||||
|
return Promise.resolve({
|
||||||
|
done: true,
|
||||||
|
value: undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fetch.and.returnValue(mockPromise);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("using Couch's 'find' endpoint", () => {
|
||||||
|
return Promise.all(openmct.objects.search('test')).then(() => {
|
||||||
|
const requestUrl = fetch.calls.mostRecent().args[0];
|
||||||
|
|
||||||
|
expect(fetch).toHaveBeenCalled();
|
||||||
|
expect(requestUrl.endsWith('_find')).toBeTrue();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("and supports search by object name", () => {
|
||||||
|
return Promise.all(openmct.objects.search('test')).then(() => {
|
||||||
|
const requestPayload = JSON.parse(fetch.calls.mostRecent().args[1].body);
|
||||||
|
|
||||||
|
expect(requestPayload).toBeDefined();
|
||||||
|
expect(requestPayload.selector.model.name.$regex).toEqual('(?i)test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
:series="config.series.models"
|
:series="config.series.models"
|
||||||
:highlights="highlights"
|
:highlights="highlights"
|
||||||
:legend="config.legend"
|
:legend="config.legend"
|
||||||
|
@legendHoverChanged="legendHoverChanged"
|
||||||
/>
|
/>
|
||||||
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
|
<div class="plot-wrapper-axis-and-display-area flex-elem grows">
|
||||||
<y-axis v-if="config.series.models.length > 0"
|
<y-axis v-if="config.series.models.length > 0"
|
||||||
@ -65,9 +66,9 @@
|
|||||||
<div ref="chartContainer"
|
<div ref="chartContainer"
|
||||||
class="gl-plot-chart-wrapper"
|
class="gl-plot-chart-wrapper"
|
||||||
>
|
>
|
||||||
<mct-chart :series-config="config"
|
<mct-chart :rectangles="rectangles"
|
||||||
:rectangles="rectangles"
|
|
||||||
:highlights="highlights"
|
:highlights="highlights"
|
||||||
|
:show-limit-line-labels="showLimitLineLabels"
|
||||||
@plotReinitializeCanvas="initCanvas"
|
@plotReinitializeCanvas="initCanvas"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -85,8 +86,8 @@
|
|||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-button-set c-button-set--strip-h"
|
<div v-if="plotHistory.length"
|
||||||
:disabled="!plotHistory.length"
|
class="c-button-set c-button-set--strip-h"
|
||||||
>
|
>
|
||||||
<button class="c-button icon-arrow-left"
|
<button class="c-button icon-arrow-left"
|
||||||
title="Restore previous pan/zoom"
|
title="Restore previous pan/zoom"
|
||||||
@ -99,6 +100,31 @@
|
|||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="isRealTime"
|
||||||
|
class="c-button-set c-button-set--strip-h"
|
||||||
|
>
|
||||||
|
<button v-if="!isFrozen"
|
||||||
|
class="c-button icon-pause"
|
||||||
|
title="Pause incoming real-time data"
|
||||||
|
@click="pause()"
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
<button v-if="isFrozen"
|
||||||
|
class="c-button icon-arrow-right pause-play is-paused"
|
||||||
|
title="Resume displaying real-time data"
|
||||||
|
@click="play()"
|
||||||
|
>
|
||||||
|
</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>
|
</div>
|
||||||
|
|
||||||
<!--Cursor guides-->
|
<!--Cursor guides-->
|
||||||
@ -135,6 +161,7 @@ import MctTicks from "./MctTicks.vue";
|
|||||||
import MctChart from "./chart/MctChart.vue";
|
import MctChart from "./chart/MctChart.vue";
|
||||||
import XAxis from "./axis/XAxis.vue";
|
import XAxis from "./axis/XAxis.vue";
|
||||||
import YAxis from "./axis/YAxis.vue";
|
import YAxis from "./axis/YAxis.vue";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -186,10 +213,16 @@ export default {
|
|||||||
xKeyOptions: [],
|
xKeyOptions: [],
|
||||||
config: {},
|
config: {},
|
||||||
pending: 0,
|
pending: 0,
|
||||||
loaded: false
|
isRealTime: this.openmct.time.clock() !== undefined,
|
||||||
|
loaded: false,
|
||||||
|
isTimeOutOfSync: false,
|
||||||
|
showLimitLineLabels: undefined
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isFrozen() {
|
||||||
|
return this.config.xAxis.get('frozen') === true && this.config.yAxis.get('frozen') === true;
|
||||||
|
},
|
||||||
plotLegendPositionClass() {
|
plotLegendPositionClass() {
|
||||||
return `plot-legend-${this.config.legend.get('position')}`;
|
return `plot-legend-${this.config.legend.get('position')}`;
|
||||||
},
|
},
|
||||||
@ -227,6 +260,7 @@ export default {
|
|||||||
'configuration.filters',
|
'configuration.filters',
|
||||||
this.updateFiltersAndResubscribe
|
this.updateFiltersAndResubscribe
|
||||||
);
|
);
|
||||||
|
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.updateStatus);
|
||||||
|
|
||||||
this.openmct.objectViews.on('clearData', this.clearData);
|
this.openmct.objectViews.on('clearData', this.clearData);
|
||||||
this.followTimeConductor();
|
this.followTimeConductor();
|
||||||
@ -243,6 +277,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
followTimeConductor() {
|
followTimeConductor() {
|
||||||
|
this.openmct.time.on('clock', this.updateRealTime);
|
||||||
this.openmct.time.on('bounds', this.updateDisplayBounds);
|
this.openmct.time.on('bounds', this.updateDisplayBounds);
|
||||||
this.synchronized(true);
|
this.synchronized(true);
|
||||||
},
|
},
|
||||||
@ -371,6 +406,9 @@ export default {
|
|||||||
const displayRange = series.getDisplayRange(xKey);
|
const displayRange = series.getDisplayRange(xKey);
|
||||||
this.config.xAxis.set('range', displayRange);
|
this.config.xAxis.set('range', displayRange);
|
||||||
},
|
},
|
||||||
|
updateRealTime(clock) {
|
||||||
|
this.isRealTime = clock !== undefined;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Track latest display bounds. Forces update when not receiving ticks.
|
* Track latest display bounds. Forces update when not receiving ticks.
|
||||||
@ -424,19 +462,30 @@ export default {
|
|||||||
* displays can update accordingly.
|
* displays can update accordingly.
|
||||||
*/
|
*/
|
||||||
synchronized(value) {
|
synchronized(value) {
|
||||||
|
const isLocalClock = this.openmct.time.clock();
|
||||||
|
|
||||||
if (typeof value !== 'undefined') {
|
if (typeof value !== 'undefined') {
|
||||||
this._synchronized = value;
|
this._synchronized = value;
|
||||||
const isUnsynced = !value && this.openmct.time.clock();
|
this.isTimeOutOfSync = value !== true;
|
||||||
const domainObject = this.openmct.legacyObject(this.domainObject);
|
|
||||||
if (domainObject.getCapability('status')) {
|
const isUnsynced = isLocalClock && !value;
|
||||||
domainObject.getCapability('status')
|
this.setStatus(isUnsynced);
|
||||||
.set('timeconductor-unsynced', isUnsynced);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._synchronized;
|
return this._synchronized;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setStatus(isNotInSync) {
|
||||||
|
const outOfSync = isNotInSync === true
|
||||||
|
|| this.isTimeOutOfSync === true
|
||||||
|
|| this.isFrozen === true;
|
||||||
|
if (outOfSync === true) {
|
||||||
|
this.openmct.status.set(this.domainObject.identifier, 'timeconductor-unsynced');
|
||||||
|
} else {
|
||||||
|
this.openmct.status.set(this.domainObject.identifier, '');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
initCanvas() {
|
initCanvas() {
|
||||||
if (this.canvas) {
|
if (this.canvas) {
|
||||||
this.stopListening(this.canvas);
|
this.stopListening(this.canvas);
|
||||||
@ -451,6 +500,10 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
|
_.debounce(this.handleWindowResize, 400);
|
||||||
|
this.plotContainerResizeObserver = new ResizeObserver(this.handleWindowResize);
|
||||||
|
this.plotContainerResizeObserver.observe(this.$parent.$refs.plotWrapper);
|
||||||
|
|
||||||
// Setup canvas etc.
|
// Setup canvas etc.
|
||||||
this.xScale = new LinearScale(this.config.xAxis.get('displayRange'));
|
this.xScale = new LinearScale(this.config.xAxis.get('displayRange'));
|
||||||
this.yScale = new LinearScale(this.config.yAxis.get('displayRange'));
|
this.yScale = new LinearScale(this.config.yAxis.get('displayRange'));
|
||||||
@ -729,7 +782,8 @@ export default {
|
|||||||
const ZOOM_AMT = 0.1;
|
const ZOOM_AMT = 0.1;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (!this.positionOverPlot) {
|
if (event.wheelDelta === undefined
|
||||||
|
|| !this.positionOverPlot) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -847,11 +901,13 @@ export default {
|
|||||||
freeze() {
|
freeze() {
|
||||||
this.config.yAxis.set('frozen', true);
|
this.config.yAxis.set('frozen', true);
|
||||||
this.config.xAxis.set('frozen', true);
|
this.config.xAxis.set('frozen', true);
|
||||||
|
this.setStatus();
|
||||||
},
|
},
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.config.yAxis.set('frozen', false);
|
this.config.yAxis.set('frozen', false);
|
||||||
this.config.xAxis.set('frozen', false);
|
this.config.xAxis.set('frozen', false);
|
||||||
|
this.setStatus();
|
||||||
this.plotHistory = [];
|
this.plotHistory = [];
|
||||||
this.userViewportChangeEnd();
|
this.userViewportChangeEnd();
|
||||||
},
|
},
|
||||||
@ -881,6 +937,59 @@ export default {
|
|||||||
this.config.series.models[0].emit('change:yKey', yKey);
|
this.config.series.models[0].emit('change:yKey', yKey);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pause() {
|
||||||
|
this.freeze();
|
||||||
|
},
|
||||||
|
|
||||||
|
play() {
|
||||||
|
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() {
|
destroy() {
|
||||||
configStore.deleteStore(this.config.id);
|
configStore.deleteStore(this.config.id);
|
||||||
|
|
||||||
@ -894,8 +1003,27 @@ export default {
|
|||||||
this.filterObserver();
|
this.filterObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.removeStatusListener) {
|
||||||
|
this.removeStatusListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.plotContainerResizeObserver.disconnect();
|
||||||
|
|
||||||
|
this.openmct.time.off('clock', this.updateRealTime);
|
||||||
this.openmct.time.off('bounds', this.updateDisplayBounds);
|
this.openmct.time.off('bounds', this.updateDisplayBounds);
|
||||||
this.openmct.objectViews.off('clearData', this.clearData);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -228,8 +228,8 @@ export default {
|
|||||||
|
|
||||||
doTickUpdate() {
|
doTickUpdate() {
|
||||||
if (this.shouldCheckWidth) {
|
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) {
|
const tickWidth = Number([].reduce.call(tickElements, function (memo, first) {
|
||||||
return Math.max(memo, first.offsetWidth);
|
return Math.max(memo, first.offsetWidth);
|
||||||
}, 0));
|
}, 0));
|
||||||
@ -238,6 +238,7 @@ export default {
|
|||||||
this.$emit('plotTickWidth', tickWidth);
|
this.$emit('plotTickWidth', tickWidth);
|
||||||
this.shouldCheckWidth = false;
|
this.shouldCheckWidth = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.tickUpdate = false;
|
this.tickUpdate = false;
|
||||||
}
|
}
|
@ -56,6 +56,7 @@
|
|||||||
|
|
||||||
<div ref="plotContainer"
|
<div ref="plotContainer"
|
||||||
class="l-view-section u-style-receiver js-style-receiver"
|
class="l-view-section u-style-receiver js-style-receiver"
|
||||||
|
:class="{'s-status-timeconductor-unsynced': status && status === 'timeconductor-unsynced'}"
|
||||||
>
|
>
|
||||||
<div v-show="!!loading"
|
<div v-show="!!loading"
|
||||||
class="c-loading--overlay loading"
|
class="c-loading--overlay loading"
|
||||||
@ -64,6 +65,7 @@
|
|||||||
:cursor-guide="cursorGuide"
|
:cursor-guide="cursorGuide"
|
||||||
:options="options"
|
:options="options"
|
||||||
@loadingUpdated="loadingUpdated"
|
@loadingUpdated="loadingUpdated"
|
||||||
|
@statusUpdated="setStatus"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -94,7 +96,8 @@ export default {
|
|||||||
// hideExportButtons: false
|
// hideExportButtons: false
|
||||||
cursorGuide: false,
|
cursorGuide: false,
|
||||||
gridLines: !this.options.compact,
|
gridLines: !this.options.compact,
|
||||||
loading: false
|
loading: false,
|
||||||
|
status: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -131,6 +134,9 @@ export default {
|
|||||||
|
|
||||||
toggleGridLines() {
|
toggleGridLines() {
|
||||||
this.gridLines = !this.gridLines;
|
this.gridLines = !this.gridLines;
|
||||||
|
},
|
||||||
|
setStatus(status) {
|
||||||
|
this.status = status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -48,7 +48,7 @@ export default function PlotViewProvider(openmct) {
|
|||||||
name: 'Plot',
|
name: 'Plot',
|
||||||
cssClass: 'icon-telemetry',
|
cssClass: 'icon-telemetry',
|
||||||
canView(domainObject, objectPath) {
|
canView(domainObject, objectPath) {
|
||||||
return isCompactView(objectPath) && hasTelemetry(domainObject, openmct);
|
return hasTelemetry(domainObject, openmct);
|
||||||
},
|
},
|
||||||
|
|
||||||
view: function (domainObject, objectPath) {
|
view: function (domainObject, objectPath) {
|
46
src/plugins/plot/chart/LimitLabel.vue
Normal file
46
src/plugins/plot/chart/LimitLabel.vue
Normal 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>
|
44
src/plugins/plot/chart/LimitLine.vue
Normal file
44
src/plugins/plot/chart/LimitLine.vue
Normal 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>
|
105
src/plugins/plot/chart/MCTChartAlarmLineSet.js
Normal file
105
src/plugins/plot/chart/MCTChartAlarmLineSet.js
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user