mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 18:50:11 +00:00
Compare commits
2 Commits
issue#3926
...
remove-ang
Author | SHA1 | Date | |
---|---|---|---|
cf5ef9fa60 | |||
756e5d301e |
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
43
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,43 +0,0 @@
|
|||||||
<!--- This is for filing bugs. If you have a general question, please -->
|
|
||||||
<!--- visit https://github.com/nasa/openmct/discussions -->
|
|
||||||
|
|
||||||
---
|
|
||||||
name: Bug Report
|
|
||||||
about: File a Bug !
|
|
||||||
|
|
||||||
---
|
|
||||||
<!--- Focus on user impact in the title. Use the Summary Field to -->
|
|
||||||
<!--- describe the problem technically. -->
|
|
||||||
|
|
||||||
#### Summary
|
|
||||||
<!--- A description of the issue encountered. When possible, a description -->
|
|
||||||
<!--- of the impact of the issue. What use case does it impede?-->
|
|
||||||
|
|
||||||
#### Expected vs Current Behavior
|
|
||||||
<!--- Tell us what should have happened -->
|
|
||||||
|
|
||||||
#### Impact Check List
|
|
||||||
<!--- Please select from the following options -->
|
|
||||||
|
|
||||||
- [ ] Data loss or misrepresented data?
|
|
||||||
- [ ] Regression? Did this used to work or has it always been broken?
|
|
||||||
- [ ] Is there a workaround available?
|
|
||||||
- [ ] Does this impact a critical component?
|
|
||||||
- [ ] Is this just a visual bug?
|
|
||||||
|
|
||||||
#### Steps to Reproduce
|
|
||||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
|
||||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
4.
|
|
||||||
|
|
||||||
#### Environment
|
|
||||||
* Open MCT Version: <!--- date of build, version, or SHA -->
|
|
||||||
* Deployment Type: <!--- npm dev? VIPER Dev? openmct-yams? -->
|
|
||||||
* OS:
|
|
||||||
* Browser:
|
|
||||||
|
|
||||||
#### Additional Information
|
|
||||||
<!--- Include any screenshots, gifs, or logs which will expedite triage -->
|
|
1
.github/ISSUE_TEMPLATE/config.yml
vendored
1
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
23
.github/ISSUE_TEMPLATE/feature_request.md
vendored
23
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,23 +0,0 @@
|
|||||||
<!--- This is for filing enhancements or features. If you have a general -->
|
|
||||||
<!--- question, please visit https://github.com/nasa/openmct/discussions -->
|
|
||||||
|
|
||||||
---
|
|
||||||
name: Feature Request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Thank you for suggesting an idea to make Open MCT better.
|
|
||||||
|
|
||||||
Please fill in as much of the template below as you're able.
|
|
||||||
-->
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
<!-- Please describe the problem you are trying to solve. -->
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
<!--- Please describe the desired behavior. -->
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
<!--- Please describe alternative solutions or features you have considered. -->
|
|
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,12 +0,0 @@
|
|||||||
### All Submissions:
|
|
||||||
|
|
||||||
* [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)?
|
|
||||||
* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/nasa/openmct/pulls) for the same update/change?
|
|
||||||
|
|
||||||
### Author Checklist
|
|
||||||
|
|
||||||
* [ ] Changes address original issue?
|
|
||||||
* [ ] Unit tests included and/or updated with changes?
|
|
||||||
* [ ] Command line build passes?
|
|
||||||
* [ ] Has this been smoke tested?
|
|
||||||
* [ ] Testing instructions included in associated issue?
|
|
3
API.md
3
API.md
@ -423,14 +423,13 @@ attribute | type | flags | notes
|
|||||||
|
|
||||||
###### Value Hints
|
###### Value Hints
|
||||||
|
|
||||||
Each telemetry value description has an object defining hints. Keys in this object represent the hint itself, and the value represents the weight of that hint. A lower weight means the hint has a higher priority. For example, multiple values could be hinted for use as the y-axis of a plot (raw, engineering), but the highest priority would be the default choice. Likewise, a table will use hints to determine the default order of columns.
|
Each telemetry value description has an object defining hints. Keys in this this object represent the hint itself, and the value represents the weight of that hint. A lower weight means the hint has a higher priority. For example, multiple values could be hinted for use as the y-axis of a plot (raw, engineering), but the highest priority would be the default choice. Likewise, a table will use hints to determine the default order of columns.
|
||||||
|
|
||||||
Known hints:
|
Known hints:
|
||||||
|
|
||||||
* `domain`: Values with a `domain` hint will be used for the x-axis of a plot, and tables will render columns for these values first.
|
* `domain`: Values with a `domain` hint will be used for the x-axis of a plot, and tables will render columns for these values first.
|
||||||
* `range`: Values with a `range` hint will be used as the y-axis on a plot, and tables will render columns for these values after the `domain` values.
|
* `range`: Values with a `range` hint will be used as the y-axis on a plot, and tables will render columns for these values after the `domain` values.
|
||||||
* `image`: Indicates that the value may be interpreted as the URL to an image file, in which case appropriate views will be made available.
|
* `image`: Indicates that the value may be interpreted as the URL to an image file, in which case appropriate views will be made available.
|
||||||
* `imageDownloadName`: Indicates that the value may be interpreted as the name of the image file.
|
|
||||||
|
|
||||||
##### The Time Conductor and Telemetry
|
##### The Time Conductor and Telemetry
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ accept changes from external contributors.
|
|||||||
|
|
||||||
The short version:
|
The short version:
|
||||||
|
|
||||||
1. Write your contribution or describe your idea in the form of an [GitHub issue](https://github.com/nasa/openmct/issues/new/choose) or [Starting a GitHub Discussion](https://github.com/nasa/openmct/discussions)
|
1. Write your contribution.
|
||||||
2. Make sure your contribution meets code, test, and commit message
|
2. Make sure your contribution meets code, test, and commit message
|
||||||
standards as described below.
|
standards as described below.
|
||||||
3. Submit a pull request from a topic branch back to `master`. Include a check
|
3. Submit a pull request from a topic branch back to `master`. Include a check
|
||||||
@ -18,7 +18,6 @@ The short version:
|
|||||||
for review.)
|
for review.)
|
||||||
4. Respond to any discussion. When the reviewer decides it's ready, they
|
4. Respond to any discussion. When the reviewer decides it's ready, they
|
||||||
will merge back `master` and fill out their own check list.
|
will merge back `master` and fill out their own check list.
|
||||||
5. If you are a first-time contributor, please see [this discussion](https://github.com/nasa/openmct/discussions/3821) for further information.
|
|
||||||
|
|
||||||
## Contribution Process
|
## Contribution Process
|
||||||
|
|
||||||
@ -116,7 +115,7 @@ the pull request containing the reviewer checklist (from below) and complete
|
|||||||
the merge back to the master branch.
|
the merge back to the master branch.
|
||||||
|
|
||||||
Additionally:
|
Additionally:
|
||||||
* Every pull request must link to the issue that it addresses. Eg. “Addresses #1234” or “Closes #1234”. This is the responsibility of the pull request’s __author__. If no issue exists, [create one](https://github.com/nasa/openmct/issues/new/choose).
|
* Every pull request must link to the issue that it addresses. Eg. “Addresses #1234” or “Closes #1234”. This is the responsibility of the pull request’s __author__. If no issue exists, create one.
|
||||||
* Every __author__ must include testing instructions. These instructions should identify the areas of code affected, and some minimal test steps. If addressing a bug, reproduction steps should be included, if they were not included in the original issue. If reproduction steps were included on the original issue, and are sufficient, refer to them.
|
* Every __author__ must include testing instructions. These instructions should identify the areas of code affected, and some minimal test steps. If addressing a bug, reproduction steps should be included, if they were not included in the original issue. If reproduction steps were included on the original issue, and are sufficient, refer to them.
|
||||||
* A pull request that closes an issue should say so in the description. Including the text “Closes #1234” will cause the linked issue to be automatically closed when the pull request is merged. This is the responsibility of the pull 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.
|
||||||
@ -297,12 +296,23 @@ these standards.
|
|||||||
|
|
||||||
Issues are tracked at https://github.com/nasa/openmct/issues.
|
Issues are tracked at https://github.com/nasa/openmct/issues.
|
||||||
|
|
||||||
|
Issues should include:
|
||||||
|
|
||||||
|
* A short description of the issue encountered.
|
||||||
|
* A longer-form description of the issue encountered. When possible, steps to
|
||||||
|
reproduce the issue.
|
||||||
|
* When possible, a description of the impact of the issue. What use case does
|
||||||
|
it impede?
|
||||||
|
* An assessment of the severity of the issue.
|
||||||
|
|
||||||
Issue severity is categorized as follows (in ascending order):
|
Issue severity is categorized as follows (in ascending order):
|
||||||
|
|
||||||
* _Trivial_: Minimal impact on the usefulness and functionality of the software; a "nice-to-have." Visual impact without functional impact,
|
* _Trivial_: Minimal impact on the usefulness and functionality of the
|
||||||
* _Medium_: Some impairment of use, but simple workarounds exist
|
software; a "nice-to-have."
|
||||||
* _Critical_: Significant loss of functionality or impairment of use. Display of telemetry data is not affected though.
|
* _(Unspecified)_: Major loss of functionality or impairment of use.
|
||||||
* _Blocker_: Major functionality is impaired or lost, threatening mission success. Display of telemetry data is impaired or blocked by the bug, which could lead to loss of situational awareness.
|
* _Critical_: Large-scale loss of functionality or impairment of use,
|
||||||
|
such that remaining utility becomes marginal.
|
||||||
|
* _Blocker_: Harmful or otherwise unacceptable behavior. Must fix.
|
||||||
|
|
||||||
## Check Lists
|
## Check Lists
|
||||||
|
|
||||||
@ -312,19 +322,16 @@ checklist).
|
|||||||
|
|
||||||
### Author Checklist
|
### Author Checklist
|
||||||
|
|
||||||
[Within PR Template](.github/PULL_REQUEST_TEMPLATE.md)
|
1. Changes address original issue?
|
||||||
|
2. Unit tests included and/or updated with changes?
|
||||||
|
3. Command line build passes?
|
||||||
|
4. Changes have been smoke-tested?
|
||||||
|
5. Testing instructions included?
|
||||||
|
|
||||||
### Reviewer Checklist
|
### Reviewer Checklist
|
||||||
|
|
||||||
* [ ] Changes appear to address issue?
|
1. Changes appear to address issue?
|
||||||
* [ ] Appropriate unit tests included?
|
2. Appropriate unit tests included?
|
||||||
* [ ] Code style and in-line documentation are appropriate?
|
3. Code style and in-line documentation are appropriate?
|
||||||
* [ ] Commit messages meet standards?
|
4. Commit messages meet standards?
|
||||||
* [ ] Has associated issue been labelled `unverified`? (only applicable if this PR closes the issue)
|
5. Has associated issue been labelled `unverified`? (only applicable if this PR closes the issue)
|
||||||
* [ ] Has associated issue been labelled `bug`? (only applicable if this PR is for a bug fix)
|
|
||||||
* [ ] List of Acceptance Tests Performed.
|
|
||||||
|
|
||||||
Write out a small list of tests performed with just enough detail for another developer on the team
|
|
||||||
to execute.
|
|
||||||
|
|
||||||
i.e. ```When Clicking on Add button, new `object` appears in dropdown.```
|
|
||||||
|
@ -44,7 +44,7 @@ The clearest examples for developing Open MCT plugins are in the
|
|||||||
our documentation.
|
our documentation.
|
||||||
|
|
||||||
We want Open MCT to be as easy to use, install, run, and develop for as
|
We want Open MCT to be as easy to use, install, run, and develop for as
|
||||||
possible, and your feedback will help us get there! Feedback can be provided via [GitHub issues](https://github.com/nasa/openmct/issues/new/choose), [Starting a GitHub Discussion](https://github.com/nasa/openmct/discussions), or by emailing us at [arc-dl-openmct@mail.nasa.gov](mailto:arc-dl-openmct@mail.nasa.gov).
|
possible, and your feedback will help us get there! Feedback can be provided via [GitHub issues](https://github.com/nasa/openmct/issues), or by emailing us at [arc-dl-openmct@mail.nasa.gov](mailto:arc-dl-openmct@mail.nasa.gov).
|
||||||
|
|
||||||
## Building Applications With Open MCT
|
## Building Applications With Open MCT
|
||||||
|
|
||||||
|
@ -73,11 +73,11 @@ acceptance testing (e.g. by resolving any blockers found); any
|
|||||||
resources not needed for this effort should be used to begin work
|
resources not needed for this effort should be used to begin work
|
||||||
for the subsequent sprint.
|
for the subsequent sprint.
|
||||||
|
|
||||||
| Week | Mon | Tue | Wed | Thu | Fri |
|
| Week | Mon | Tue | Wed | Thu | Fri |
|
||||||
|:-----:|:-------------------------:|:------:|:---:|:----------------------------:|:-------------------------------------:|
|
|:-----:|:-------------------------:|:------:|:---:|:----------------------------:|:-----------:|
|
||||||
| __1__ | Sprint plan | Tag-up | | | |
|
| __1__ | Sprint plan | Tag-up | | | |
|
||||||
| __2__ | | Tag-up | | | Code freeze and sprint branch |
|
| __2__ | | Tag-up | | | Code freeze |
|
||||||
| __3__ | Per-sprint testing | Triage | | _Per-sprint testing*_ | Ship and merge sprint branch to master|
|
| __3__ | Per-sprint testing | Triage | | _Per-sprint testing*_ | Ship |
|
||||||
|
|
||||||
* If necessary.
|
* If necessary.
|
||||||
|
|
||||||
@ -105,20 +105,14 @@ emphasis on testing.
|
|||||||
that team may begin work for that sprint during the
|
that team may begin work for that sprint during the
|
||||||
third week, since testing and blocker resolution is unlikely
|
third week, since testing and blocker resolution is unlikely
|
||||||
to require all available resources.
|
to require all available resources.
|
||||||
* Testing success criteria identified per issue (where necessary). This could be in the form of acceptance tests on the issue or detailing performance tests, for example.
|
|
||||||
* __Tag-up.__ Check in and status update among development team.
|
* __Tag-up.__ Check in and status update among development team.
|
||||||
May amend plan for sprint as-needed.
|
May amend plan for sprint as-needed.
|
||||||
* __Code freeze.__ Any new work from this sprint
|
* __Code freeze.__ Any new work from this sprint
|
||||||
(features, bug fixes, enhancements) must be integrated by the
|
(features, bug fixes, enhancements) must be integrated by the
|
||||||
end of the second week of the sprint. After code freeze, a sprint
|
end of the second week of the sprint. After code freeze
|
||||||
branch will be created (and until the end of the sprint) the only
|
(and until the end of the sprint) the only changes that should be
|
||||||
changes that should be merged into the sprint branch should
|
merged into the master branch should directly address issues
|
||||||
directly address issues needed to pass acceptance testing.
|
needed to pass acceptance testing.
|
||||||
During this time, any other feature development will continue to
|
|
||||||
be merged into the master branch for the next sprint.
|
|
||||||
* __Sprint branch merge to master.__ After acceptance testing, the sprint branch
|
|
||||||
will be merged back to the master branch. Any code conflicts that
|
|
||||||
arise will be resolved by the team.
|
|
||||||
* [__Per-release Testing.__](testing/plan.md#per-release-testing)
|
* [__Per-release Testing.__](testing/plan.md#per-release-testing)
|
||||||
Structured testing with predefined
|
Structured testing with predefined
|
||||||
success criteria. No release should ship without passing
|
success criteria. No release should ship without passing
|
||||||
@ -132,8 +126,8 @@ emphasis on testing.
|
|||||||
* [__Testathon.__](testing/plan.md#user-testing)
|
* [__Testathon.__](testing/plan.md#user-testing)
|
||||||
Multi-user testing, involving as many users as
|
Multi-user testing, involving as many users as
|
||||||
is feasible, plus development team. Open-ended; should verify
|
is feasible, plus development team. Open-ended; should verify
|
||||||
completed work from this sprint using the sprint branch, test
|
completed work from this sprint, test exploratorily for
|
||||||
exploratorily for regressions, et cetera.
|
regressions, et cetera.
|
||||||
* [__Long-Duration Test.__](testing/plan.md#long-duration-testing) A
|
* [__Long-Duration Test.__](testing/plan.md#long-duration-testing) A
|
||||||
test to verify that the software remains
|
test to verify that the software remains
|
||||||
stable after running for longer durations. May include some
|
stable after running for longer durations. May include some
|
||||||
@ -149,7 +143,7 @@ emphasis on testing.
|
|||||||
Subset of Pre-release Testing
|
Subset of Pre-release Testing
|
||||||
which should be performed before shipping at the end of any
|
which should be performed before shipping at the end of any
|
||||||
sprint. Time is allocated for a second round of
|
sprint. Time is allocated for a second round of
|
||||||
Pre-release Testing if the first round is not passed. Smoke tests collected from issues/PRs
|
Pre-release Testing if the first round is not passed.
|
||||||
* __Triage.__ Team reviews issues from acceptance testing and uses
|
* __Triage.__ Team reviews issues from acceptance testing and uses
|
||||||
success criteria to determine whether or not they should block
|
success criteria to determine whether or not they should block
|
||||||
release, then formulates a plan to address these issues before
|
release, then formulates a plan to address these issues before
|
||||||
|
@ -19,7 +19,7 @@ Testing for Open MCT includes:
|
|||||||
|
|
||||||
Manual, non-rigorous testing of the software and/or specific features
|
Manual, non-rigorous testing of the software and/or specific features
|
||||||
of interest. Verifies that the software runs and that basic functionality
|
of interest. Verifies that the software runs and that basic functionality
|
||||||
is present. The outcome of Smoke Testing should be a simplified list of Acceptance Tests which could be executed by another team member with sufficient context.
|
is present.
|
||||||
|
|
||||||
### Unit Testing
|
### Unit Testing
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ User testing will focus on the following activities:
|
|||||||
* General "trying to break things."
|
* General "trying to break things."
|
||||||
|
|
||||||
During user testing, users will
|
During user testing, users will
|
||||||
[report issues](https://github.com/nasa/openmct/issues/new/choose)
|
[report issues](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#issue-reporting)
|
||||||
as they are encountered.
|
as they are encountered.
|
||||||
|
|
||||||
Desired outcomes of user testing are:
|
Desired outcomes of user testing are:
|
||||||
@ -71,7 +71,7 @@ usage. After twenty-four hours, the software is evaluated for:
|
|||||||
at the start of the test? Is it as responsive?
|
at the start of the test? Is it as responsive?
|
||||||
|
|
||||||
Any defects or unexpected behavior identified during testing should be
|
Any defects or unexpected behavior identified during testing should be
|
||||||
[reported as issues](https://github.com/nasa/openmct/issues/new/choose)
|
[reported as issues](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#issue-reporting)
|
||||||
and reviewed for severity.
|
and reviewed for severity.
|
||||||
|
|
||||||
## Test Performance
|
## Test Performance
|
||||||
|
@ -92,8 +92,8 @@ should update (or delegate the task of updating) Open MCT version
|
|||||||
numbers by the following process:
|
numbers by the following process:
|
||||||
|
|
||||||
1. Update version number in `package.json`
|
1. Update version number in `package.json`
|
||||||
1. Checkout branch created for the last sprint that has been successfully tested.
|
1. Create a new branch off the `master` branch.
|
||||||
2. Remove a `-SNAPSHOT` suffix from the version in `package.json`.
|
2. Remove `-SNAPSHOT` suffix from the version in `package.json`.
|
||||||
3. Verify that resulting version number meets semantic versioning
|
3. Verify that resulting version number meets semantic versioning
|
||||||
requirements relative to previous stable version. Increment the
|
requirements relative to previous stable version. Increment the
|
||||||
version number if necessary.
|
version number if necessary.
|
||||||
|
@ -93,36 +93,5 @@ define([
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
SinewaveLimitProvider.prototype.getLimits = function (domainObject) {
|
|
||||||
return {
|
|
||||||
limits: function () {
|
|
||||||
return {
|
|
||||||
WARNING: {
|
|
||||||
low: {
|
|
||||||
cssClass: "is-limit--lwr is-limit--yellow",
|
|
||||||
sin: -YELLOW.sin,
|
|
||||||
cos: -YELLOW.cos
|
|
||||||
},
|
|
||||||
high: {
|
|
||||||
cssClass: "is-limit--upr is-limit--yellow",
|
|
||||||
...YELLOW
|
|
||||||
}
|
|
||||||
},
|
|
||||||
DISTRESS: {
|
|
||||||
low: {
|
|
||||||
cssClass: "is-limit--lwr is-limit--red",
|
|
||||||
sin: -RED.sin,
|
|
||||||
cos: -RED.cos
|
|
||||||
},
|
|
||||||
high: {
|
|
||||||
cssClass: "is-limit--upr is-limit--red",
|
|
||||||
...RED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return SinewaveLimitProvider;
|
return SinewaveLimitProvider;
|
||||||
});
|
});
|
||||||
|
@ -50,16 +50,11 @@ define([
|
|||||||
const IMAGE_DELAY = 20000;
|
const IMAGE_DELAY = 20000;
|
||||||
|
|
||||||
function pointForTimestamp(timestamp, name) {
|
function pointForTimestamp(timestamp, name) {
|
||||||
const url = IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length];
|
|
||||||
const urlItems = url.split('/');
|
|
||||||
const imageDownloadName = `example.imagery.${urlItems[urlItems.length - 1]}`;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
name: name,
|
||||||
utc: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
utc: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
||||||
local: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
local: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
|
||||||
url,
|
url: IMAGE_SAMPLES[Math.floor(timestamp / IMAGE_DELAY) % IMAGE_SAMPLES.length]
|
||||||
imageDownloadName
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,14 +139,6 @@ define([
|
|||||||
hints: {
|
hints: {
|
||||||
image: 1
|
image: 1
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Image Download Name',
|
|
||||||
key: 'imageDownloadName',
|
|
||||||
format: 'imageDownloadName',
|
|
||||||
hints: {
|
|
||||||
imageDownloadName: 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
@ -88,6 +88,7 @@
|
|||||||
openmct.install(openmct.plugins.ExampleImagery());
|
openmct.install(openmct.plugins.ExampleImagery());
|
||||||
openmct.install(openmct.plugins.PlanLayout());
|
openmct.install(openmct.plugins.PlanLayout());
|
||||||
openmct.install(openmct.plugins.Timeline());
|
openmct.install(openmct.plugins.Timeline());
|
||||||
|
openmct.install(openmct.plugins.PlotVue());
|
||||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||||
openmct.install(openmct.plugins.AutoflowView({
|
openmct.install(openmct.plugins.AutoflowView({
|
||||||
type: "telemetry.panel"
|
type: "telemetry.panel"
|
||||||
|
@ -78,7 +78,6 @@ module.exports = (config) => {
|
|||||||
preserveDescribeNesting: true,
|
preserveDescribeNesting: true,
|
||||||
foldAll: false
|
foldAll: false
|
||||||
},
|
},
|
||||||
browserConsoleLogOptions: { level: "error", format: "%b %T: %m", terminal: true },
|
|
||||||
coverageIstanbulReporter: {
|
coverageIstanbulReporter: {
|
||||||
fixWebpackSourcePaths: true,
|
fixWebpackSourcePaths: true,
|
||||||
dir: process.env.CIRCLE_ARTIFACTS ?
|
dir: process.env.CIRCLE_ARTIFACTS ?
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "1.7.3-SNAPSHOT",
|
"version": "1.6.3-SNAPSHOT",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -78,8 +78,7 @@
|
|||||||
"zepto": "^1.2.0"
|
"zepto": "^1.2.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf ./dist /node_modules; rm package-lock.json",
|
"clean": "rm -rf ./dist",
|
||||||
"clean-test-lint": "npm run clean; npm install ; npm run test; npm run lint",
|
|
||||||
"start": "node app.js",
|
"start": "node app.js",
|
||||||
"lint": "eslint platform example src --ext .js,.vue openmct.js",
|
"lint": "eslint platform example src --ext .js,.vue openmct.js",
|
||||||
"lint:fix": "eslint platform example src --ext .js,.vue openmct.js --fix",
|
"lint:fix": "eslint platform example src --ext .js,.vue openmct.js --fix",
|
||||||
|
@ -86,7 +86,7 @@ define(
|
|||||||
})
|
})
|
||||||
.join('/');
|
.join('/');
|
||||||
|
|
||||||
openmct.router.navigate(url);
|
window.location.href = url;
|
||||||
|
|
||||||
if (isFirstViewEditable(object.useCapability('adapter'), objectPath)) {
|
if (isFirstViewEditable(object.useCapability('adapter'), objectPath)) {
|
||||||
openmct.editor.edit();
|
openmct.editor.edit();
|
||||||
|
@ -141,17 +141,11 @@ define(
|
|||||||
if (mutationResult !== false) {
|
if (mutationResult !== false) {
|
||||||
// Copy values if result was a different object
|
// Copy values if result was a different object
|
||||||
// (either our clone or some other new thing)
|
// (either our clone or some other new thing)
|
||||||
let modelHasChanged = _.isEqual(model, result) === false;
|
if (model !== result) {
|
||||||
if (modelHasChanged) {
|
|
||||||
copyValues(model, result);
|
copyValues(model, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelHasChanged
|
model.modified = useTimestamp ? timestamp : now();
|
||||||
|| (useTimestamp !== undefined)
|
|
||||||
|| (model.modified === undefined)) {
|
|
||||||
model.modified = useTimestamp ? timestamp : now();
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyListeners(model);
|
notifyListeners(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,11 +23,13 @@
|
|||||||
define([
|
define([
|
||||||
"moment-timezone",
|
"moment-timezone",
|
||||||
"./src/indicators/ClockIndicator",
|
"./src/indicators/ClockIndicator",
|
||||||
|
"./src/indicators/FollowIndicator",
|
||||||
"./src/services/TickerService",
|
"./src/services/TickerService",
|
||||||
"./src/services/TimerService",
|
"./src/services/TimerService",
|
||||||
"./src/controllers/ClockController",
|
"./src/controllers/ClockController",
|
||||||
"./src/controllers/TimerController",
|
"./src/controllers/TimerController",
|
||||||
"./src/controllers/RefreshingController",
|
"./src/controllers/RefreshingController",
|
||||||
|
"./src/actions/FollowTimerAction",
|
||||||
"./src/actions/StartTimerAction",
|
"./src/actions/StartTimerAction",
|
||||||
"./src/actions/RestartTimerAction",
|
"./src/actions/RestartTimerAction",
|
||||||
"./src/actions/StopTimerAction",
|
"./src/actions/StopTimerAction",
|
||||||
@ -37,11 +39,13 @@ define([
|
|||||||
], function (
|
], function (
|
||||||
MomentTimezone,
|
MomentTimezone,
|
||||||
ClockIndicator,
|
ClockIndicator,
|
||||||
|
FollowIndicator,
|
||||||
TickerService,
|
TickerService,
|
||||||
TimerService,
|
TimerService,
|
||||||
ClockController,
|
ClockController,
|
||||||
TimerController,
|
TimerController,
|
||||||
RefreshingController,
|
RefreshingController,
|
||||||
|
FollowTimerAction,
|
||||||
StartTimerAction,
|
StartTimerAction,
|
||||||
RestartTimerAction,
|
RestartTimerAction,
|
||||||
StopTimerAction,
|
StopTimerAction,
|
||||||
@ -140,6 +144,15 @@ define([
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"actions": [
|
"actions": [
|
||||||
|
{
|
||||||
|
"key": "timer.follow",
|
||||||
|
"implementation": FollowTimerAction,
|
||||||
|
"depends": ["timerService"],
|
||||||
|
"category": "contextual",
|
||||||
|
"name": "Follow Timer",
|
||||||
|
"cssClass": "icon-clock",
|
||||||
|
"priority": "optional"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "timer.start",
|
"key": "timer.start",
|
||||||
"implementation": StartTimerAction,
|
"implementation": StartTimerAction,
|
||||||
@ -286,7 +299,10 @@ define([
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"runs": [],
|
"runs": [{
|
||||||
|
"implementation": FollowIndicator,
|
||||||
|
"depends": ["openmct", "timerService"]
|
||||||
|
}],
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
"name": "moment-duration-format",
|
"name": "moment-duration-format",
|
||||||
|
56
platform/features/clock/src/actions/FollowTimerAction.js
Normal file
56
platform/features/clock/src/actions/FollowTimerAction.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2009-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Designates a specific timer for following. Timelines, for example,
|
||||||
|
* use the actively followed timer to display a time-of-interest line
|
||||||
|
* and interpret time conductor bounds in the Timeline's relative
|
||||||
|
* time frame.
|
||||||
|
*
|
||||||
|
* @implements {Action}
|
||||||
|
* @memberof platform/features/clock
|
||||||
|
* @constructor
|
||||||
|
* @param {ActionContext} context the context for this action
|
||||||
|
*/
|
||||||
|
function FollowTimerAction(timerService, context) {
|
||||||
|
var domainObject =
|
||||||
|
context.domainObject
|
||||||
|
&& context.domainObject.useCapability('adapter');
|
||||||
|
this.perform =
|
||||||
|
timerService.setTimer.bind(timerService, domainObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
FollowTimerAction.appliesTo = function (context) {
|
||||||
|
var model =
|
||||||
|
(context.domainObject && context.domainObject.getModel())
|
||||||
|
|| {};
|
||||||
|
|
||||||
|
return model.type === 'timer';
|
||||||
|
};
|
||||||
|
|
||||||
|
return FollowTimerAction;
|
||||||
|
}
|
||||||
|
);
|
51
platform/features/clock/src/indicators/FollowIndicator.js
Normal file
51
platform/features/clock/src/indicators/FollowIndicator.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2009-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define([], function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicator that displays the active timer, as well as its
|
||||||
|
* current state.
|
||||||
|
* @memberof platform/features/clock
|
||||||
|
*/
|
||||||
|
return function installFollowIndicator(openmct, timerService) {
|
||||||
|
var indicator = openmct.indicators.simpleIndicator();
|
||||||
|
var timer = timerService.getTimer();
|
||||||
|
setIndicatorStatus(timer);
|
||||||
|
|
||||||
|
function setIndicatorStatus(newTimer) {
|
||||||
|
if (newTimer !== undefined) {
|
||||||
|
indicator.iconClass('icon-timer');
|
||||||
|
indicator.statusClass('s-status-on');
|
||||||
|
indicator.text('Following timer ' + newTimer.name);
|
||||||
|
} else {
|
||||||
|
indicator.iconClass('icon-timer');
|
||||||
|
indicator.statusClass('s-status-disabled');
|
||||||
|
indicator.text('No timer being followed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timerService.on('change', setIndicatorStatus);
|
||||||
|
|
||||||
|
openmct.indicators.add(indicator);
|
||||||
|
};
|
||||||
|
});
|
@ -0,0 +1,89 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2009-2016, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define([
|
||||||
|
"../../src/actions/FollowTimerAction"
|
||||||
|
], function (FollowTimerAction) {
|
||||||
|
var TIMER_SERVICE_METHODS =
|
||||||
|
['setTimer', 'getTimer', 'clearTimer', 'on', 'off'];
|
||||||
|
|
||||||
|
describe("The Follow Timer action", function () {
|
||||||
|
var testContext;
|
||||||
|
var testModel;
|
||||||
|
var testAdaptedObject;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testModel = {};
|
||||||
|
testContext = {
|
||||||
|
domainObject: jasmine.createSpyObj('domainObject', [
|
||||||
|
'getModel',
|
||||||
|
'useCapability'
|
||||||
|
])
|
||||||
|
};
|
||||||
|
testAdaptedObject = { foo: 'bar' };
|
||||||
|
testContext.domainObject.getModel.and.returnValue(testModel);
|
||||||
|
testContext.domainObject.useCapability.and.callFake(function (c) {
|
||||||
|
return c === 'adapter' && testAdaptedObject;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is applicable to timers", function () {
|
||||||
|
testModel.type = "timer";
|
||||||
|
expect(FollowTimerAction.appliesTo(testContext)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is inapplicable to non-timers", function () {
|
||||||
|
testModel.type = "folder";
|
||||||
|
expect(FollowTimerAction.appliesTo(testContext)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when instantiated", function () {
|
||||||
|
var mockTimerService;
|
||||||
|
var action;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockTimerService = jasmine.createSpyObj(
|
||||||
|
'timerService',
|
||||||
|
TIMER_SERVICE_METHODS
|
||||||
|
);
|
||||||
|
action = new FollowTimerAction(mockTimerService, testContext);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not interact with the timer service", function () {
|
||||||
|
TIMER_SERVICE_METHODS.forEach(function (method) {
|
||||||
|
expect(mockTimerService[method]).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("and performed", function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
action.perform();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets the active timer", function () {
|
||||||
|
expect(mockTimerService.setTimer)
|
||||||
|
.toHaveBeenCalledWith(testAdaptedObject);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,96 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2009-2018, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define([
|
||||||
|
"../../src/indicators/FollowIndicator",
|
||||||
|
"../../src/services/TimerService",
|
||||||
|
"../../../../../src/MCT",
|
||||||
|
'zepto'
|
||||||
|
], function (
|
||||||
|
FollowIndicator,
|
||||||
|
TimerService,
|
||||||
|
MCT,
|
||||||
|
$
|
||||||
|
) {
|
||||||
|
xdescribe("The timer-following indicator", function () {
|
||||||
|
var timerService;
|
||||||
|
var openmct;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
openmct = new MCT();
|
||||||
|
timerService = new TimerService(openmct);
|
||||||
|
spyOn(openmct.indicators, "add");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds an indicator when installed", function () {
|
||||||
|
FollowIndicator(openmct, timerService);
|
||||||
|
expect(openmct.indicators.add).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("indicates that no timer is being followed", function () {
|
||||||
|
FollowIndicator(openmct, timerService);
|
||||||
|
var simpleIndicator = openmct.indicators.add.calls.mostRecent().args[0];
|
||||||
|
var element = simpleIndicator.element;
|
||||||
|
var text = $('.indicator-text', element).text().trim();
|
||||||
|
expect(text).toEqual('No timer being followed');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when a timer is set", function () {
|
||||||
|
var testObject;
|
||||||
|
var simpleIndicator;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
testObject = {
|
||||||
|
identifier: {
|
||||||
|
namespace: 'namespace',
|
||||||
|
key: 'key'
|
||||||
|
},
|
||||||
|
name: "some timer!"
|
||||||
|
};
|
||||||
|
timerService.setTimer(testObject);
|
||||||
|
FollowIndicator(openmct, timerService);
|
||||||
|
simpleIndicator = openmct.indicators.add.calls.mostRecent().args[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays the timer's name", function () {
|
||||||
|
var element = simpleIndicator.element;
|
||||||
|
var text = $('.indicator-text', element).text().trim();
|
||||||
|
expect(text).toEqual('Following timer ' + testObject.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays the timer's name when it changes", function () {
|
||||||
|
var secondTimer = {
|
||||||
|
identifier: {
|
||||||
|
namespace: 'namespace',
|
||||||
|
key: 'key2'
|
||||||
|
},
|
||||||
|
name: "Some other timer"
|
||||||
|
};
|
||||||
|
var element = simpleIndicator.element;
|
||||||
|
timerService.setTimer(secondTimer);
|
||||||
|
var text = $('.indicator-text', element).text().trim();
|
||||||
|
expect(text).toEqual('Following timer ' + secondTimer.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -154,9 +154,7 @@ define(['zepto', 'objectUtils'], function ($, objectUtils) {
|
|||||||
tree = JSON.stringify(tree).replace(new RegExp(oldIdKeyString, 'g'), newIdKeyString);
|
tree = JSON.stringify(tree).replace(new RegExp(oldIdKeyString, 'g'), newIdKeyString);
|
||||||
|
|
||||||
return JSON.parse(tree, (key, value) => {
|
return JSON.parse(tree, (key, value) => {
|
||||||
if (value !== undefined
|
if (Object.prototype.hasOwnProperty.call(value, 'key')
|
||||||
&& value !== null
|
|
||||||
&& Object.prototype.hasOwnProperty.call(value, 'key')
|
|
||||||
&& Object.prototype.hasOwnProperty.call(value, 'namespace')
|
&& Object.prototype.hasOwnProperty.call(value, 'namespace')
|
||||||
&& value.key === oldId.key
|
&& value.key === oldId.key
|
||||||
&& value.namespace === oldId.namespace) {
|
&& value.namespace === oldId.namespace) {
|
||||||
|
8
platform/persistence/couch/README.md
Normal file
8
platform/persistence/couch/README.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Couch DB Persistence Plugin
|
||||||
|
An adapter for using CouchDB for persistence of user-created objects. The plugin installation function takes the URL
|
||||||
|
for the CouchDB database as a parameter.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
```js
|
||||||
|
openmct.install(openmct.plugins.CouchDB('http://localhost:5984/openmct'))
|
||||||
|
```
|
78
platform/persistence/couch/bundle.js
Normal file
78
platform/persistence/couch/bundle.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define([
|
||||||
|
"./src/CouchPersistenceProvider",
|
||||||
|
"./src/CouchIndicator"
|
||||||
|
], function (
|
||||||
|
CouchPersistenceProvider,
|
||||||
|
CouchIndicator
|
||||||
|
) {
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "platform/persistence/couch",
|
||||||
|
definition: {
|
||||||
|
"name": "Couch Persistence",
|
||||||
|
"description": "Adapter to read and write objects using a CouchDB instance.",
|
||||||
|
"extensions": {
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"provides": "persistenceService",
|
||||||
|
"type": "provider",
|
||||||
|
"implementation": CouchPersistenceProvider,
|
||||||
|
"depends": [
|
||||||
|
"$http",
|
||||||
|
"$q",
|
||||||
|
"PERSISTENCE_SPACE",
|
||||||
|
"COUCHDB_PATH"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"constants": [
|
||||||
|
{
|
||||||
|
"key": "PERSISTENCE_SPACE",
|
||||||
|
"value": "mct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "COUCHDB_PATH",
|
||||||
|
"value": "/couch/openmct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "COUCHDB_INDICATOR_INTERVAL",
|
||||||
|
"value": 15000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indicators": [
|
||||||
|
{
|
||||||
|
"implementation": CouchIndicator,
|
||||||
|
"depends": [
|
||||||
|
"$http",
|
||||||
|
"$interval",
|
||||||
|
"COUCHDB_PATH",
|
||||||
|
"COUCHDB_INDICATOR_INTERVAL"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
61
platform/persistence/couch/src/CouchDocument.js
Normal file
61
platform/persistence/couch/src/CouchDocument.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A CouchDocument describes domain object model in a format
|
||||||
|
* which is easily read-written to CouchDB. This includes
|
||||||
|
* Couch's _id and _rev fields, as well as a separate
|
||||||
|
* metadata field which contains a subset of information found
|
||||||
|
* in the model itself (to support search optimization with
|
||||||
|
* CouchDB views.)
|
||||||
|
* @memberof platform/persistence/couch
|
||||||
|
* @constructor
|
||||||
|
* @param {string} id the id under which to store this mode
|
||||||
|
* @param {object} model the model to store
|
||||||
|
* @param {string} rev the revision to include (or undefined,
|
||||||
|
* if no revision should be noted for couch)
|
||||||
|
* @param {boolean} whether or not to mark this document as
|
||||||
|
* deleted (see CouchDB docs for _deleted)
|
||||||
|
*/
|
||||||
|
function CouchDocument(id, model, rev, markDeleted) {
|
||||||
|
return {
|
||||||
|
"_id": id,
|
||||||
|
"_rev": rev,
|
||||||
|
"_deleted": markDeleted,
|
||||||
|
"metadata": {
|
||||||
|
"category": "domain object",
|
||||||
|
"type": model.type,
|
||||||
|
"owner": "admin",
|
||||||
|
"name": model.name,
|
||||||
|
"created": Date.now()
|
||||||
|
},
|
||||||
|
"model": model
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return CouchDocument;
|
||||||
|
}
|
||||||
|
);
|
119
platform/persistence/couch/src/CouchIndicator.js
Normal file
119
platform/persistence/couch/src/CouchIndicator.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
|
||||||
|
// Set of connection states; changing among these states will be
|
||||||
|
// reflected in the indicator's appearance.
|
||||||
|
// CONNECTED: Everything nominal, expect to be able to read/write.
|
||||||
|
// DISCONNECTED: HTTP failed; maybe misconfigured, disconnected.
|
||||||
|
// SEMICONNECTED: Connected to the database, but it reported an error.
|
||||||
|
// PENDING: Still trying to connect, and haven't failed yet.
|
||||||
|
var CONNECTED = {
|
||||||
|
text: "Connected",
|
||||||
|
glyphClass: "ok",
|
||||||
|
statusClass: "s-status-on",
|
||||||
|
description: "Connected to the domain object database."
|
||||||
|
},
|
||||||
|
DISCONNECTED = {
|
||||||
|
text: "Disconnected",
|
||||||
|
glyphClass: "err",
|
||||||
|
statusClass: "s-status-caution",
|
||||||
|
description: "Unable to connect to the domain object database."
|
||||||
|
},
|
||||||
|
SEMICONNECTED = {
|
||||||
|
text: "Unavailable",
|
||||||
|
glyphClass: "caution",
|
||||||
|
statusClass: "s-status-caution",
|
||||||
|
description: "Database does not exist or is unavailable."
|
||||||
|
},
|
||||||
|
PENDING = {
|
||||||
|
text: "Checking connection...",
|
||||||
|
statusClass: "s-status-caution"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicator for the current CouchDB connection. Polls CouchDB
|
||||||
|
* at a regular interval (defined by bundle constants) to ensure
|
||||||
|
* that the database is available.
|
||||||
|
* @constructor
|
||||||
|
* @memberof platform/persistence/couch
|
||||||
|
* @implements {Indicator}
|
||||||
|
* @param $http Angular's $http service
|
||||||
|
* @param $interval Angular's $interval service
|
||||||
|
* @param {string} path the URL to poll to check for couch availability
|
||||||
|
* @param {number} interval the interval, in milliseconds, to poll at
|
||||||
|
*/
|
||||||
|
function CouchIndicator($http, $interval, path, interval) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Track the current connection state
|
||||||
|
this.state = PENDING;
|
||||||
|
|
||||||
|
this.$http = $http;
|
||||||
|
this.$interval = $interval;
|
||||||
|
this.path = path;
|
||||||
|
this.interval = interval;
|
||||||
|
|
||||||
|
// Callback if the HTTP request to Couch fails
|
||||||
|
function handleError() {
|
||||||
|
self.state = DISCONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback if the HTTP request succeeds. CouchDB may
|
||||||
|
// report an error, so check for that.
|
||||||
|
function handleResponse(response) {
|
||||||
|
var data = response.data;
|
||||||
|
self.state = data.error ? SEMICONNECTED : CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to connect to CouchDB, and update the indicator.
|
||||||
|
function updateIndicator() {
|
||||||
|
$http.get(path).then(handleResponse, handleError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the indicator initially, and start polling.
|
||||||
|
updateIndicator();
|
||||||
|
$interval(updateIndicator, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
CouchIndicator.prototype.getCssClass = function () {
|
||||||
|
return "c-indicator--clickable icon-suitcase " + this.state.statusClass;
|
||||||
|
};
|
||||||
|
|
||||||
|
CouchIndicator.prototype.getGlyphClass = function () {
|
||||||
|
return this.state.glyphClass;
|
||||||
|
};
|
||||||
|
|
||||||
|
CouchIndicator.prototype.getText = function () {
|
||||||
|
return this.state.text;
|
||||||
|
};
|
||||||
|
|
||||||
|
CouchIndicator.prototype.getDescription = function () {
|
||||||
|
return this.state.description;
|
||||||
|
};
|
||||||
|
|
||||||
|
return CouchIndicator;
|
||||||
|
}
|
||||||
|
);
|
145
platform/persistence/couch/src/CouchPersistenceProvider.js
Normal file
145
platform/persistence/couch/src/CouchPersistenceProvider.js
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This bundle implements a persistence service which uses CouchDB to
|
||||||
|
* store documents.
|
||||||
|
* @namespace platform/persistence/cache
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["./CouchDocument"],
|
||||||
|
function (CouchDocument) {
|
||||||
|
|
||||||
|
// JSLint doesn't like dangling _'s, but CouchDB uses these, so
|
||||||
|
// hide this behind variables.
|
||||||
|
var REV = "_rev",
|
||||||
|
ID = "_id";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The CouchPersistenceProvider reads and writes JSON documents
|
||||||
|
* (more specifically, domain object models) to/from a CouchDB
|
||||||
|
* instance.
|
||||||
|
* @memberof platform/persistence/couch
|
||||||
|
* @constructor
|
||||||
|
* @implements {PersistenceService}
|
||||||
|
* @param $http Angular's $http service
|
||||||
|
* @param $interval Angular's $interval service
|
||||||
|
* @param {string} space the name of the persistence space being served
|
||||||
|
* @param {string} path the path to the CouchDB instance
|
||||||
|
*/
|
||||||
|
function CouchPersistenceProvider($http, $q, space, path) {
|
||||||
|
this.spaces = [space];
|
||||||
|
this.revs = {};
|
||||||
|
this.$q = $q;
|
||||||
|
this.$http = $http;
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull out a list of document IDs from CouchDB's
|
||||||
|
// _all_docs response
|
||||||
|
function getIdsFromAllDocs(allDocs) {
|
||||||
|
return allDocs.rows.map(function (r) {
|
||||||
|
return r.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the response to a create/update/delete request;
|
||||||
|
// track the rev if it's valid, otherwise return false to
|
||||||
|
// indicate that the request failed.
|
||||||
|
CouchPersistenceProvider.prototype.checkResponse = function (response) {
|
||||||
|
if (response && response.ok) {
|
||||||
|
this.revs[response.id] = response.rev;
|
||||||
|
|
||||||
|
return response.ok;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get a domain object model out of CouchDB's response
|
||||||
|
CouchPersistenceProvider.prototype.getModel = function (response) {
|
||||||
|
if (response && response.model) {
|
||||||
|
this.revs[response[ID]] = response[REV];
|
||||||
|
|
||||||
|
return response.model;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Issue a request using $http; get back the plain JS object
|
||||||
|
// from the expected JSON response
|
||||||
|
CouchPersistenceProvider.prototype.request = function (subpath, method, value) {
|
||||||
|
return this.$http({
|
||||||
|
method: method,
|
||||||
|
url: this.path + '/' + subpath,
|
||||||
|
data: value
|
||||||
|
}).then(function (response) {
|
||||||
|
return response.data;
|
||||||
|
}, function () {
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Shorthand methods for GET/PUT methods
|
||||||
|
CouchPersistenceProvider.prototype.get = function (subpath) {
|
||||||
|
return this.request(subpath, "GET");
|
||||||
|
};
|
||||||
|
|
||||||
|
CouchPersistenceProvider.prototype.put = function (subpath, value) {
|
||||||
|
return this.request(subpath, "PUT", value);
|
||||||
|
};
|
||||||
|
|
||||||
|
CouchPersistenceProvider.prototype.listSpaces = function () {
|
||||||
|
return this.$q.when(this.spaces);
|
||||||
|
};
|
||||||
|
|
||||||
|
CouchPersistenceProvider.prototype.listObjects = function () {
|
||||||
|
return this.get("_all_docs").then(getIdsFromAllDocs.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
CouchPersistenceProvider.prototype.createObject = function (space, key, value) {
|
||||||
|
return this.put(key, new CouchDocument(key, value))
|
||||||
|
.then(this.checkResponse.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
CouchPersistenceProvider.prototype.readObject = function (space, key) {
|
||||||
|
return this.get(key).then(this.getModel.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
CouchPersistenceProvider.prototype.updateObject = function (space, key, value) {
|
||||||
|
var rev = this.revs[key];
|
||||||
|
|
||||||
|
return this.put(key, new CouchDocument(key, value, rev))
|
||||||
|
.then(this.checkResponse.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
CouchPersistenceProvider.prototype.deleteObject = function (space, key, value) {
|
||||||
|
var rev = this.revs[key];
|
||||||
|
|
||||||
|
return this.put(key, new CouchDocument(key, value, rev, true))
|
||||||
|
.then(this.checkResponse.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
return CouchPersistenceProvider;
|
||||||
|
}
|
||||||
|
);
|
63
platform/persistence/couch/test/CouchDocumentSpec.js
Normal file
63
platform/persistence/couch/test/CouchDocumentSpec.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DomainObjectProviderSpec. Created by vwoeltje on 11/6/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../src/CouchDocument"],
|
||||||
|
function (CouchDocument) {
|
||||||
|
|
||||||
|
// JSLint doesn't like dangling _'s, but CouchDB uses these, so
|
||||||
|
// hide this behind variables.
|
||||||
|
var REV = "_rev",
|
||||||
|
ID = "_id",
|
||||||
|
DELETED = "_deleted";
|
||||||
|
|
||||||
|
describe("A couch document", function () {
|
||||||
|
it("includes an id", function () {
|
||||||
|
expect(new CouchDocument("testId", {})[ID])
|
||||||
|
.toEqual("testId");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes a rev only when one is provided", function () {
|
||||||
|
expect(new CouchDocument("testId", {})[REV])
|
||||||
|
.not.toBeDefined();
|
||||||
|
expect(new CouchDocument("testId", {}, "testRev")[REV])
|
||||||
|
.toEqual("testRev");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes the provided model", function () {
|
||||||
|
var model = { someKey: "some value" };
|
||||||
|
expect(new CouchDocument("testId", model).model)
|
||||||
|
.toEqual(model);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("marks documents as deleted only on request", function () {
|
||||||
|
expect(new CouchDocument("testId", {}, "testRev")[DELETED])
|
||||||
|
.not.toBeDefined();
|
||||||
|
expect(new CouchDocument("testId", {}, "testRev", true)[DELETED])
|
||||||
|
.toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
129
platform/persistence/couch/test/CouchIndicatorSpec.js
Normal file
129
platform/persistence/couch/test/CouchIndicatorSpec.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../src/CouchIndicator"],
|
||||||
|
function (CouchIndicator) {
|
||||||
|
|
||||||
|
xdescribe("The CouchDB status indicator", function () {
|
||||||
|
var mockHttp,
|
||||||
|
mockInterval,
|
||||||
|
testPath,
|
||||||
|
testInterval,
|
||||||
|
mockPromise,
|
||||||
|
indicator;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockHttp = jasmine.createSpyObj("$http", ["get"]);
|
||||||
|
mockInterval = jasmine.createSpy("$interval");
|
||||||
|
mockPromise = jasmine.createSpyObj("promise", ["then"]);
|
||||||
|
testPath = "/test/path";
|
||||||
|
testInterval = 12321; // Some number
|
||||||
|
|
||||||
|
mockHttp.get.and.returnValue(mockPromise);
|
||||||
|
|
||||||
|
indicator = new CouchIndicator(
|
||||||
|
mockHttp,
|
||||||
|
mockInterval,
|
||||||
|
testPath,
|
||||||
|
testInterval
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("polls for changes", function () {
|
||||||
|
expect(mockInterval).toHaveBeenCalledWith(
|
||||||
|
jasmine.any(Function),
|
||||||
|
testInterval
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has a database icon", function () {
|
||||||
|
expect(indicator.getCssClass()).toEqual("icon-database s-status-caution");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("consults the database at the configured path", function () {
|
||||||
|
expect(mockHttp.get).toHaveBeenCalledWith(testPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes when the database connection is nominal", function () {
|
||||||
|
var initialText = indicator.getText(),
|
||||||
|
initialDescrption = indicator.getDescription(),
|
||||||
|
initialGlyphClass = indicator.getGlyphClass();
|
||||||
|
|
||||||
|
// Nominal just means getting back an object, without
|
||||||
|
// an error field.
|
||||||
|
mockPromise.then.calls.mostRecent().args[0]({ data: {} });
|
||||||
|
|
||||||
|
// Verify that these values changed;
|
||||||
|
// don't test for specific text.
|
||||||
|
expect(indicator.getText()).not.toEqual(initialText);
|
||||||
|
expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass);
|
||||||
|
expect(indicator.getDescription()).not.toEqual(initialDescrption);
|
||||||
|
|
||||||
|
// Do check for specific class
|
||||||
|
expect(indicator.getGlyphClass()).toEqual("ok");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes when the server reports an error", function () {
|
||||||
|
var initialText = indicator.getText(),
|
||||||
|
initialDescrption = indicator.getDescription(),
|
||||||
|
initialGlyphClass = indicator.getGlyphClass();
|
||||||
|
|
||||||
|
// Nominal just means getting back an object, with
|
||||||
|
// an error field.
|
||||||
|
mockPromise.then.calls.mostRecent().args[0](
|
||||||
|
{ data: { error: "Uh oh." } }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify that these values changed;
|
||||||
|
// don't test for specific text.
|
||||||
|
expect(indicator.getText()).not.toEqual(initialText);
|
||||||
|
expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass);
|
||||||
|
expect(indicator.getDescription()).not.toEqual(initialDescrption);
|
||||||
|
|
||||||
|
// Do check for specific class
|
||||||
|
expect(indicator.getGlyphClass()).toEqual("caution");
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes when the server cannot be reached", function () {
|
||||||
|
var initialText = indicator.getText(),
|
||||||
|
initialDescrption = indicator.getDescription(),
|
||||||
|
initialGlyphClass = indicator.getGlyphClass();
|
||||||
|
|
||||||
|
// Nominal just means getting back an object, without
|
||||||
|
// an error field.
|
||||||
|
mockPromise.then.calls.mostRecent().args[1]({ data: {} });
|
||||||
|
|
||||||
|
// Verify that these values changed;
|
||||||
|
// don't test for specific text.
|
||||||
|
expect(indicator.getText()).not.toEqual(initialText);
|
||||||
|
expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass);
|
||||||
|
expect(indicator.getDescription()).not.toEqual(initialDescrption);
|
||||||
|
|
||||||
|
// Do check for specific class
|
||||||
|
expect(indicator.getGlyphClass()).toEqual("err");
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
223
platform/persistence/couch/test/CouchPersistenceProviderSpec.js
Normal file
223
platform/persistence/couch/test/CouchPersistenceProviderSpec.js
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DomainObjectProviderSpec. Created by vwoeltje on 11/6/14.
|
||||||
|
*/
|
||||||
|
define(
|
||||||
|
["../src/CouchPersistenceProvider"],
|
||||||
|
function (CouchPersistenceProvider) {
|
||||||
|
|
||||||
|
describe("The couch persistence provider", function () {
|
||||||
|
var mockHttp,
|
||||||
|
mockQ,
|
||||||
|
testSpace = "testSpace",
|
||||||
|
testPath = "/test/db",
|
||||||
|
capture,
|
||||||
|
provider;
|
||||||
|
|
||||||
|
function mockPromise(value) {
|
||||||
|
return {
|
||||||
|
then: function (callback) {
|
||||||
|
return mockPromise(callback(value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockHttp = jasmine.createSpy("$http");
|
||||||
|
mockQ = jasmine.createSpyObj("$q", ["when"]);
|
||||||
|
|
||||||
|
mockQ.when.and.callFake(mockPromise);
|
||||||
|
|
||||||
|
// Capture promise results
|
||||||
|
capture = jasmine.createSpy("capture");
|
||||||
|
|
||||||
|
provider = new CouchPersistenceProvider(
|
||||||
|
mockHttp,
|
||||||
|
mockQ,
|
||||||
|
testSpace,
|
||||||
|
testPath
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports available spaces", function () {
|
||||||
|
provider.listSpaces().then(capture);
|
||||||
|
expect(capture).toHaveBeenCalledWith([testSpace]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// General pattern of tests below is to simulate CouchDB's
|
||||||
|
// response, verify that request looks like what CouchDB
|
||||||
|
// would expect, and finally verify that CouchPersistenceProvider's
|
||||||
|
// return values match what is expected.
|
||||||
|
it("lists all available documents", function () {
|
||||||
|
mockHttp.and.returnValue(mockPromise({
|
||||||
|
data: { rows: [{ id: "a" }, { id: "b" }, { id: "c" }] }
|
||||||
|
}));
|
||||||
|
provider.listObjects().then(capture);
|
||||||
|
expect(mockHttp).toHaveBeenCalledWith({
|
||||||
|
url: "/test/db/_all_docs", // couch document listing
|
||||||
|
method: "GET",
|
||||||
|
data: undefined
|
||||||
|
});
|
||||||
|
expect(capture).toHaveBeenCalledWith(["a", "b", "c"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows object creation", function () {
|
||||||
|
var model = { someKey: "some value" };
|
||||||
|
mockHttp.and.returnValue(mockPromise({
|
||||||
|
data: {
|
||||||
|
"_id": "abc",
|
||||||
|
"_rev": "xyz",
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
provider.createObject("testSpace", "abc", model).then(capture);
|
||||||
|
expect(mockHttp).toHaveBeenCalledWith({
|
||||||
|
url: "/test/db/abc",
|
||||||
|
method: "PUT",
|
||||||
|
data: {
|
||||||
|
"_id": "abc",
|
||||||
|
"_rev": undefined,
|
||||||
|
"_deleted": undefined,
|
||||||
|
metadata: jasmine.any(Object),
|
||||||
|
model: model
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(capture).toHaveBeenCalledWith(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows object models to be read back", function () {
|
||||||
|
var model = { someKey: "some value" };
|
||||||
|
mockHttp.and.returnValue(mockPromise({
|
||||||
|
data: {
|
||||||
|
"_id": "abc",
|
||||||
|
"_rev": "xyz",
|
||||||
|
"model": model
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
provider.readObject("testSpace", "abc").then(capture);
|
||||||
|
expect(mockHttp).toHaveBeenCalledWith({
|
||||||
|
url: "/test/db/abc",
|
||||||
|
method: "GET",
|
||||||
|
data: undefined
|
||||||
|
});
|
||||||
|
expect(capture).toHaveBeenCalledWith(model);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows object update", function () {
|
||||||
|
var model = { someKey: "some value" };
|
||||||
|
|
||||||
|
// First do a read to populate rev tags...
|
||||||
|
mockHttp.and.returnValue(mockPromise({
|
||||||
|
data: {
|
||||||
|
"_id": "abc",
|
||||||
|
"_rev": "xyz",
|
||||||
|
"model": {}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
provider.readObject("testSpace", "abc");
|
||||||
|
|
||||||
|
// Now perform an update
|
||||||
|
mockHttp.and.returnValue(mockPromise({
|
||||||
|
data: {
|
||||||
|
"_id": "abc",
|
||||||
|
"_rev": "uvw",
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
provider.updateObject("testSpace", "abc", model).then(capture);
|
||||||
|
expect(mockHttp).toHaveBeenCalledWith({
|
||||||
|
url: "/test/db/abc",
|
||||||
|
method: "PUT",
|
||||||
|
data: {
|
||||||
|
"_id": "abc",
|
||||||
|
"_rev": "xyz",
|
||||||
|
"_deleted": undefined,
|
||||||
|
metadata: jasmine.any(Object),
|
||||||
|
model: model
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(capture).toHaveBeenCalledWith(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows object deletion", function () {
|
||||||
|
// First do a read to populate rev tags...
|
||||||
|
mockHttp.and.returnValue(mockPromise({
|
||||||
|
data: {
|
||||||
|
"_id": "abc",
|
||||||
|
"_rev": "xyz",
|
||||||
|
"model": {}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
provider.readObject("testSpace", "abc");
|
||||||
|
|
||||||
|
// Now perform an update
|
||||||
|
mockHttp.and.returnValue(mockPromise({
|
||||||
|
data: {
|
||||||
|
"_id": "abc",
|
||||||
|
"_rev": "uvw",
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
provider.deleteObject("testSpace", "abc", {}).then(capture);
|
||||||
|
expect(mockHttp).toHaveBeenCalledWith({
|
||||||
|
url: "/test/db/abc",
|
||||||
|
method: "PUT",
|
||||||
|
data: {
|
||||||
|
"_id": "abc",
|
||||||
|
"_rev": "xyz",
|
||||||
|
"_deleted": true,
|
||||||
|
metadata: jasmine.any(Object),
|
||||||
|
model: {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(capture).toHaveBeenCalledWith(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reports failure to create objects", function () {
|
||||||
|
var model = { someKey: "some value" };
|
||||||
|
mockHttp.and.returnValue(mockPromise({
|
||||||
|
data: {
|
||||||
|
"_id": "abc",
|
||||||
|
"_rev": "xyz",
|
||||||
|
"ok": false
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
provider.createObject("testSpace", "abc", model).then(capture);
|
||||||
|
expect(capture).toHaveBeenCalledWith(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined when objects are not found", function () {
|
||||||
|
// Act like a 404
|
||||||
|
mockHttp.and.returnValue({
|
||||||
|
then: function (success, fail) {
|
||||||
|
return mockPromise(fail());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
provider.readObject("testSpace", "abc").then(capture);
|
||||||
|
expect(capture).toHaveBeenCalledWith(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
50
src/MCT.js
50
src/MCT.js
@ -252,7 +252,7 @@ define([
|
|||||||
|
|
||||||
this.status = new api.StatusAPI(this);
|
this.status = new api.StatusAPI(this);
|
||||||
|
|
||||||
this.router = new ApplicationRouter(this);
|
this.router = new ApplicationRouter();
|
||||||
|
|
||||||
this.branding = BrandingAPI.default;
|
this.branding = BrandingAPI.default;
|
||||||
|
|
||||||
@ -284,6 +284,7 @@ define([
|
|||||||
this.install(this.plugins.ViewDatumAction());
|
this.install(this.plugins.ViewDatumAction());
|
||||||
this.install(this.plugins.ObjectInterceptors());
|
this.install(this.plugins.ObjectInterceptors());
|
||||||
this.install(this.plugins.NonEditableFolder());
|
this.install(this.plugins.NonEditableFolder());
|
||||||
|
this.install(this.plugins.Devices());
|
||||||
}
|
}
|
||||||
|
|
||||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||||
@ -403,39 +404,24 @@ define([
|
|||||||
this.router.setPath('/browse/');
|
this.router.setPath('/browse/');
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
if (!isHeadlessMode) {
|
||||||
* Fired by [MCT]{@link module:openmct.MCT} when the application
|
const appLayout = new Vue({
|
||||||
* is started.
|
components: {
|
||||||
* @event start
|
'Layout': Layout.default
|
||||||
* @memberof module:openmct.MCT~
|
},
|
||||||
*/
|
provide: {
|
||||||
const startPromise = new Main();
|
openmct: this
|
||||||
startPromise.run(this)
|
},
|
||||||
.then(function (angular) {
|
template: '<Layout ref="layout"></Layout>'
|
||||||
this.$angular = angular;
|
});
|
||||||
// OpenMCT Object provider doesn't operate properly unless
|
domElement.appendChild(appLayout.$mount().$el);
|
||||||
// something has depended upon objectService. Cool, right?
|
|
||||||
this.$injector.get('objectService');
|
|
||||||
|
|
||||||
if (!isHeadlessMode) {
|
this.layout = appLayout.$refs.layout;
|
||||||
const appLayout = new Vue({
|
Browse(this);
|
||||||
components: {
|
}
|
||||||
'Layout': Layout.default
|
|
||||||
},
|
|
||||||
provide: {
|
|
||||||
openmct: this
|
|
||||||
},
|
|
||||||
template: '<Layout ref="layout"></Layout>'
|
|
||||||
});
|
|
||||||
domElement.appendChild(appLayout.$mount().$el);
|
|
||||||
|
|
||||||
this.layout = appLayout.$refs.layout;
|
this.router.start();
|
||||||
Browse(this);
|
this.emit('start');
|
||||||
}
|
|
||||||
|
|
||||||
this.router.start();
|
|
||||||
this.emit('start');
|
|
||||||
}.bind(this));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
MCT.prototype.startHeadless = function () {
|
MCT.prototype.startHeadless = function () {
|
||||||
|
@ -36,8 +36,7 @@ define([
|
|||||||
'./views/installLegacyViews',
|
'./views/installLegacyViews',
|
||||||
'./policies/LegacyCompositionPolicyAdapter',
|
'./policies/LegacyCompositionPolicyAdapter',
|
||||||
'./actions/LegacyActionAdapter',
|
'./actions/LegacyActionAdapter',
|
||||||
'./services/LegacyPersistenceAdapter',
|
'./services/LegacyPersistenceAdapter'
|
||||||
'./services/ExportImageService'
|
|
||||||
], function (
|
], function (
|
||||||
ActionDialogDecorator,
|
ActionDialogDecorator,
|
||||||
AdapterCapability,
|
AdapterCapability,
|
||||||
@ -54,8 +53,7 @@ define([
|
|||||||
installLegacyViews,
|
installLegacyViews,
|
||||||
legacyCompositionPolicyAdapter,
|
legacyCompositionPolicyAdapter,
|
||||||
LegacyActionAdapter,
|
LegacyActionAdapter,
|
||||||
LegacyPersistenceAdapter,
|
LegacyPersistenceAdapter
|
||||||
ExportImageService
|
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
name: 'src/adapter',
|
name: 'src/adapter',
|
||||||
@ -84,13 +82,6 @@ define([
|
|||||||
"identifierService",
|
"identifierService",
|
||||||
"cacheService"
|
"cacheService"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "exportImageService",
|
|
||||||
"implementation": ExportImageService,
|
|
||||||
"depends": [
|
|
||||||
"dialogService"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
components: [
|
components: [
|
||||||
|
@ -161,22 +161,6 @@ define([
|
|||||||
evaluate: function (datum, property) {
|
evaluate: function (datum, property) {
|
||||||
return limitEvaluator.evaluate(datum, property && property.key);
|
return limitEvaluator.evaluate(datum, property && property.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
LegacyTelemetryProvider.prototype.getLimits = function (domainObject) {
|
|
||||||
const oldObject = this.instantiate(
|
|
||||||
utils.toOldFormat(domainObject),
|
|
||||||
utils.makeKeyString(domainObject.identifier)
|
|
||||||
);
|
|
||||||
const limitEvaluator = oldObject.getCapability("limit");
|
|
||||||
|
|
||||||
return {
|
|
||||||
limits: function () {
|
|
||||||
return limitEvaluator.limits();
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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,8 +119,7 @@ describe('The ActionCollection', () => {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
actionCollection.destroy();
|
actionCollection.destroy();
|
||||||
|
resetApplicationState(openmct);
|
||||||
return resetApplicationState(openmct);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("disable method invoked with action keys", () => {
|
describe("disable method invoked with action keys", () => {
|
||||||
|
@ -99,7 +99,7 @@ describe('The Actions API', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
return resetApplicationState(openmct);
|
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 (abortSignal) {
|
CompositionCollection.prototype.load = function () {
|
||||||
this.cleanUpMutables();
|
this.cleanUpMutables();
|
||||||
|
|
||||||
return this.provider.load(this.domainObject)
|
return this.provider.load(this.domainObject)
|
||||||
.then(function (children) {
|
.then(function (children) {
|
||||||
return Promise.all(children.map((c) => this.publicAPI.objects.get(c, abortSignal)));
|
return Promise.all(children.map((c) => this.publicAPI.objects.get(c)));
|
||||||
}.bind(this))
|
}.bind(this))
|
||||||
.then(function (childObjects) {
|
.then(function (childObjects) {
|
||||||
childObjects.forEach(c => this.add(c, true));
|
childObjects.forEach(c => this.add(c, true));
|
||||||
|
@ -76,7 +76,7 @@ describe ('The Menu API', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
return resetApplicationState(openmct);
|
resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("showMenu method", () => {
|
describe("showMenu method", () => {
|
||||||
|
@ -76,10 +76,7 @@ class MutableDomainObject {
|
|||||||
}
|
}
|
||||||
$set(path, value) {
|
$set(path, value) {
|
||||||
_.set(this, path, value);
|
_.set(this, path, value);
|
||||||
|
_.set(this, 'modified', Date.now());
|
||||||
if (path !== 'persisted' && path !== 'modified') {
|
|
||||||
_.set(this, 'modified', Date.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
//Emit secret synchronization event first, so that all objects are in sync before subsequent events fired.
|
//Emit secret synchronization event first, so that all objects are in sync before subsequent events fired.
|
||||||
this._globalEventEmitter.emit(qualifiedEventName(this, '$_synchronize_model'), this);
|
this._globalEventEmitter.emit(qualifiedEventName(this, '$_synchronize_model'), this);
|
||||||
@ -115,11 +112,9 @@ class MutableDomainObject {
|
|||||||
return () => this._instanceEventEmitter.off(event, callback);
|
return () => this._instanceEventEmitter.off(event, callback);
|
||||||
}
|
}
|
||||||
$destroy() {
|
$destroy() {
|
||||||
while (this._observers.length > 0) {
|
this._observers.forEach(observer => observer());
|
||||||
const observer = this._observers.pop();
|
delete this._globalEventEmitter;
|
||||||
observer();
|
delete this._observers;
|
||||||
}
|
|
||||||
|
|
||||||
this._instanceEventEmitter.emit('$_destroy');
|
this._instanceEventEmitter.emit('$_destroy');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,9 +38,6 @@ function ObjectAPI(typeRegistry, openmct) {
|
|||||||
this.eventEmitter = new EventEmitter();
|
this.eventEmitter = new EventEmitter();
|
||||||
this.providers = {};
|
this.providers = {};
|
||||||
this.rootRegistry = new RootRegistry();
|
this.rootRegistry = new RootRegistry();
|
||||||
this.injectIdentifierService = function () {
|
|
||||||
this.identifierService = openmct.$injector.get("identifierService");
|
|
||||||
};
|
|
||||||
|
|
||||||
this.rootProvider = new RootObjectProvider(this.rootRegistry);
|
this.rootProvider = new RootObjectProvider(this.rootRegistry);
|
||||||
this.cache = {};
|
this.cache = {};
|
||||||
@ -55,33 +52,16 @@ ObjectAPI.prototype.supersecretSetFallbackProvider = function (p) {
|
|||||||
this.fallbackProvider = p;
|
this.fallbackProvider = p;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ObjectAPI.prototype.getIdentifierService = function () {
|
|
||||||
// Lazily acquire identifier service
|
|
||||||
if (!this.identifierService) {
|
|
||||||
this.injectIdentifierService();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.identifierService;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the provider for a given identifier.
|
* Retrieve the provider for a given identifier.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
ObjectAPI.prototype.getProvider = function (identifier) {
|
ObjectAPI.prototype.getProvider = function (identifier) {
|
||||||
//handles the '' vs 'mct' namespace issue
|
|
||||||
const keyString = utils.makeKeyString(identifier);
|
|
||||||
const identifierService = this.getIdentifierService();
|
|
||||||
const namespace = identifierService.parse(keyString).getSpace();
|
|
||||||
|
|
||||||
if (identifier.key === 'ROOT') {
|
if (identifier.key === 'ROOT') {
|
||||||
return this.rootProvider;
|
return this.rootProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.providers[namespace] || this.fallbackProvider;
|
return this.providers[identifier.namespace] || this.fallbackProvider;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -161,7 +141,6 @@ ObjectAPI.prototype.addProvider = function (namespace, provider) {
|
|||||||
|
|
||||||
ObjectAPI.prototype.get = function (identifier, abortSignal) {
|
ObjectAPI.prototype.get = function (identifier, abortSignal) {
|
||||||
let keystring = this.makeKeyString(identifier);
|
let keystring = this.makeKeyString(identifier);
|
||||||
|
|
||||||
if (this.cache[keystring] !== undefined) {
|
if (this.cache[keystring] !== undefined) {
|
||||||
return this.cache[keystring];
|
return this.cache[keystring];
|
||||||
}
|
}
|
||||||
@ -177,16 +156,15 @@ ObjectAPI.prototype.get = function (identifier, abortSignal) {
|
|||||||
throw new Error('Provider does not support get!');
|
throw new Error('Provider does not support get!');
|
||||||
}
|
}
|
||||||
|
|
||||||
let objectPromise = provider.get(identifier, abortSignal).then(result => {
|
let objectPromise = provider.get(identifier, abortSignal);
|
||||||
|
this.cache[keystring] = objectPromise;
|
||||||
|
|
||||||
|
return objectPromise.then(result => {
|
||||||
delete this.cache[keystring];
|
delete this.cache[keystring];
|
||||||
result = this.applyGetInterceptors(identifier, result);
|
result = this.applyGetInterceptors(identifier, result);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.cache[keystring] = objectPromise;
|
|
||||||
|
|
||||||
return objectPromise;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -486,12 +464,6 @@ ObjectAPI.prototype.getOriginalPath = function (identifier, path = []) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
ObjectAPI.prototype.isObjectPathToALink = function (domainObject, objectPath) {
|
|
||||||
return objectPath !== undefined
|
|
||||||
&& objectPath.length > 1
|
|
||||||
&& domainObject.location !== this.makeKeyString(objectPath[1].identifier);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uniquely identifies a domain object.
|
* Uniquely identifies a domain object.
|
||||||
*
|
*
|
||||||
@ -528,10 +500,8 @@ ObjectAPI.prototype.isObjectPathToALink = function (domainObject, objectPath) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
function hasAlreadyBeenPersisted(domainObject) {
|
function hasAlreadyBeenPersisted(domainObject) {
|
||||||
const result = domainObject.persisted !== undefined
|
return domainObject.persisted !== undefined
|
||||||
&& domainObject.persisted >= domainObject.modified;
|
&& domainObject.persisted === domainObject.modified;
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ObjectAPI;
|
export default ObjectAPI;
|
||||||
|
@ -22,7 +22,7 @@ describe("The Status API", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
return resetApplicationState(openmct);
|
resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("set function", () => {
|
describe("set function", () => {
|
||||||
|
@ -116,11 +116,9 @@ define([
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
DefaultMetadataProvider.prototype.typeHasTelemetry = function (domainObject) {
|
DefaultMetadataProvider.prototype.typeHasTelemetry = function (domainObject) {
|
||||||
if (!this.typeService) {
|
const type = this.openmct.types.get(domainObject.type);
|
||||||
this.typeService = this.openmct.$injector.get('typeService');
|
|
||||||
}
|
|
||||||
|
|
||||||
return Boolean(this.typeService.getType(domainObject.type).typeDef.telemetry);
|
return Boolean(type.definition.telemetry);
|
||||||
};
|
};
|
||||||
|
|
||||||
return DefaultMetadataProvider;
|
return DefaultMetadataProvider;
|
||||||
|
@ -142,6 +142,8 @@ define([
|
|||||||
this.metadataCache = new WeakMap();
|
this.metadataCache = new WeakMap();
|
||||||
this.formatMapCache = new WeakMap();
|
this.formatMapCache = new WeakMap();
|
||||||
this.valueFormatterCache = new WeakMap();
|
this.valueFormatterCache = new WeakMap();
|
||||||
|
|
||||||
|
this.formatters = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -413,17 +415,6 @@ define([
|
|||||||
return _.sortBy(options, sortKeys);
|
return _.sortBy(options, sortKeys);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TelemetryAPI.prototype.getFormatService = function () {
|
|
||||||
if (!this.formatService) {
|
|
||||||
this.formatService = this.openmct.$injector.get('formatService');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.formatService;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a value formatter for a given valueMetadata.
|
* Get a value formatter for a given valueMetadata.
|
||||||
*
|
*
|
||||||
@ -433,7 +424,7 @@ define([
|
|||||||
if (!this.valueFormatterCache.has(valueMetadata)) {
|
if (!this.valueFormatterCache.has(valueMetadata)) {
|
||||||
this.valueFormatterCache.set(
|
this.valueFormatterCache.set(
|
||||||
valueMetadata,
|
valueMetadata,
|
||||||
new TelemetryValueFormatter(valueMetadata, this.getFormatService())
|
new TelemetryValueFormatter(valueMetadata, this.formatters)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,9 +438,11 @@ define([
|
|||||||
* @returns {Format}
|
* @returns {Format}
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.getFormatter = function (key) {
|
TelemetryAPI.prototype.getFormatter = function (key) {
|
||||||
const formatMap = this.getFormatService().formatMap;
|
if (this.formatters.has(key)) {
|
||||||
|
return this.formatters.get(key);
|
||||||
return formatMap[key];
|
} else {
|
||||||
|
throw new Error(`Unknown type ${key}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -476,12 +469,7 @@ define([
|
|||||||
* @param {Format} format the
|
* @param {Format} format the
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.addFormat = function (format) {
|
TelemetryAPI.prototype.addFormat = function (format) {
|
||||||
this.openmct.legacyExtension('formats', {
|
this.formatters.set(format.key, format);
|
||||||
key: format.key,
|
|
||||||
implementation: function () {
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -504,26 +492,6 @@ define([
|
|||||||
return this.getLimitEvaluator(domainObject);
|
return this.getLimitEvaluator(domainObject);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a limits for this domain object.
|
|
||||||
* Limits help you display limits and alarms of
|
|
||||||
* telemetry for display purposes without having to interact directly
|
|
||||||
* with the Limit API.
|
|
||||||
*
|
|
||||||
* This method is optional.
|
|
||||||
* If a provider does not implement this method, it is presumed
|
|
||||||
* that no limits are defined for this domain object's telemetry.
|
|
||||||
*
|
|
||||||
* @param {module:openmct.DomainObject} domainObject the domain
|
|
||||||
* object for which to get limits
|
|
||||||
* @returns {module:openmct.TelemetryAPI~LimitEvaluator}
|
|
||||||
* @method limits
|
|
||||||
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
|
||||||
*/
|
|
||||||
TelemetryAPI.prototype.limitDefinition = function (domainObject) {
|
|
||||||
return this.getLimits(domainObject);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a limit evaluator for this domain object.
|
* Get a limit evaluator for this domain object.
|
||||||
* Limit Evaluators help you evaluate limit and alarm status of individual
|
* Limit Evaluators help you evaluate limit and alarm status of individual
|
||||||
@ -551,42 +519,5 @@ define([
|
|||||||
return provider.getLimitEvaluator(domainObject);
|
return provider.getLimitEvaluator(domainObject);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a limit definitions for this domain object.
|
|
||||||
* Limit Definitions help you indicate limits and alarms of
|
|
||||||
* telemetry for display purposes without having to interact directly
|
|
||||||
* with the Limit API.
|
|
||||||
*
|
|
||||||
* This method is optional.
|
|
||||||
* If a provider does not implement this method, it is presumed
|
|
||||||
* that no limits are defined for this domain object's telemetry.
|
|
||||||
*
|
|
||||||
* @param {module:openmct.DomainObject} domainObject the domain
|
|
||||||
* object for which to display limits
|
|
||||||
* @returns {module:openmct.TelemetryAPI~LimitEvaluator}
|
|
||||||
* @method limits returns a limits object of
|
|
||||||
* type {
|
|
||||||
* level1: {
|
|
||||||
* low: { key1: value1, key2: value2 },
|
|
||||||
* high: { key1: value1, key2: value2 }
|
|
||||||
* },
|
|
||||||
* level2: {
|
|
||||||
* low: { key1: value1, key2: value2 },
|
|
||||||
* high: { key1: value1, key2: value2 }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
|
||||||
*/
|
|
||||||
TelemetryAPI.prototype.getLimits = function (domainObject) {
|
|
||||||
const provider = this.findLimitEvaluator(domainObject);
|
|
||||||
if (!provider) {
|
|
||||||
return {
|
|
||||||
limits: function () {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider.getLimits(domainObject);
|
|
||||||
};
|
|
||||||
|
|
||||||
return TelemetryAPI;
|
return TelemetryAPI;
|
||||||
});
|
});
|
||||||
|
@ -28,8 +28,7 @@ define([
|
|||||||
printj
|
printj
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// TODO: needs reference to formatService;
|
function TelemetryValueFormatter(valueMetadata, formatters) {
|
||||||
function TelemetryValueFormatter(valueMetadata, formatService) {
|
|
||||||
const numberFormatter = {
|
const numberFormatter = {
|
||||||
parse: function (x) {
|
parse: function (x) {
|
||||||
return Number(x);
|
return Number(x);
|
||||||
@ -43,13 +42,7 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.valueMetadata = valueMetadata;
|
this.valueMetadata = valueMetadata;
|
||||||
try {
|
this.formatter = formatters.get(valueMetadata.format) || numberFormatter;
|
||||||
this.formatter = formatService
|
|
||||||
.getFormat(valueMetadata.format, valueMetadata);
|
|
||||||
} catch (e) {
|
|
||||||
// TODO: Better formatting
|
|
||||||
this.formatter = numberFormatter;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valueMetadata.format === 'enum') {
|
if (valueMetadata.format === 'enum') {
|
||||||
this.formatter = {};
|
this.formatter = {};
|
||||||
|
@ -90,6 +90,7 @@ define([
|
|||||||
'../platform/framework/src/load/Bundle',
|
'../platform/framework/src/load/Bundle',
|
||||||
'../platform/identity/bundle',
|
'../platform/identity/bundle',
|
||||||
'../platform/persistence/aggregator/bundle',
|
'../platform/persistence/aggregator/bundle',
|
||||||
|
'../platform/persistence/couch/bundle',
|
||||||
'../platform/persistence/elastic/bundle',
|
'../platform/persistence/elastic/bundle',
|
||||||
'../platform/persistence/local/bundle',
|
'../platform/persistence/local/bundle',
|
||||||
'../platform/persistence/queue/bundle',
|
'../platform/persistence/queue/bundle',
|
||||||
|
@ -45,14 +45,10 @@ export default function LADTableSetViewProvider(openmct) {
|
|||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
openmct,
|
openmct,
|
||||||
|
domainObject,
|
||||||
objectPath
|
objectPath
|
||||||
},
|
},
|
||||||
data() {
|
template: '<lad-table-set></lad-table-set>'
|
||||||
return {
|
|
||||||
domainObject
|
|
||||||
};
|
|
||||||
},
|
|
||||||
template: '<lad-table-set :domain-object="domainObject"></lad-table-set>'
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
destroy: function (element) {
|
destroy: function (element) {
|
||||||
|
@ -56,7 +56,7 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
pathToTable: {
|
objectPath: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
@ -66,19 +66,20 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
let currentObjectPath = this.objectPath.slice();
|
||||||
|
currentObjectPath.unshift(this.domainObject);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
timestamp: undefined,
|
timestamp: undefined,
|
||||||
value: '---',
|
value: '---',
|
||||||
valueClass: '',
|
valueClass: '',
|
||||||
|
currentObjectPath,
|
||||||
unit: ''
|
unit: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
formattedTimestamp() {
|
formattedTimestamp() {
|
||||||
return this.timestamp !== undefined ? this.getFormattedTimestamp(this.timestamp) : '---';
|
return this.timestamp !== undefined ? this.getFormattedTimestamp(this.timestamp) : '---';
|
||||||
},
|
|
||||||
objectPath() {
|
|
||||||
return [this.domainObject, ...this.pathToTable];
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -181,7 +182,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
showContextMenu(event) {
|
showContextMenu(event) {
|
||||||
let actionCollection = this.openmct.actions.get(this.objectPath, this.getView());
|
let actionCollection = this.openmct.actions.get(this.currentObjectPath, this.getView());
|
||||||
let allActions = actionCollection.getActionsObject();
|
let allActions = actionCollection.getActionsObject();
|
||||||
let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);
|
let applicableActions = CONTEXT_MENU_ACTIONS.map(key => allActions[key]);
|
||||||
|
|
||||||
|
@ -33,10 +33,10 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<lad-row
|
<lad-row
|
||||||
v-for="ladRow in items"
|
v-for="item in items"
|
||||||
:key="ladRow.key"
|
:key="item.key"
|
||||||
:domain-object="ladRow.domainObject"
|
:domain-object="item.domainObject"
|
||||||
:path-to-table="objectPath"
|
:object-path="objectPath"
|
||||||
:has-units="hasUnits"
|
:has-units="hasUnits"
|
||||||
/>
|
/>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -43,10 +43,9 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<lad-row
|
<lad-row
|
||||||
v-for="ladRow in ladTelemetryObjects[ladTable.key]"
|
v-for="telemetryObject in ladTelemetryObjects[ladTable.key]"
|
||||||
:key="ladRow.key"
|
:key="telemetryObject.key"
|
||||||
:domain-object="ladRow.domainObject"
|
:domain-object="telemetryObject.domainObject"
|
||||||
:path-to-table="ladTable.objectPath"
|
|
||||||
:has-units="hasUnits"
|
:has-units="hasUnits"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@ -61,13 +60,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
LadRow
|
LadRow
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'objectPath'],
|
inject: ['openmct', 'domainObject'],
|
||||||
props: {
|
|
||||||
domainObject: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
ladTableObjects: [],
|
ladTableObjects: [],
|
||||||
@ -113,7 +106,6 @@ export default {
|
|||||||
let ladTable = {};
|
let ladTable = {};
|
||||||
ladTable.domainObject = domainObject;
|
ladTable.domainObject = domainObject;
|
||||||
ladTable.key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
ladTable.key = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
ladTable.objectPath = [domainObject, ...this.objectPath];
|
|
||||||
|
|
||||||
this.$set(this.ladTelemetryObjects, ladTable.key, []);
|
this.$set(this.ladTelemetryObjects, ladTable.key, []);
|
||||||
this.ladTableObjects.push(ladTable);
|
this.ladTableObjects.push(ladTable);
|
||||||
|
@ -292,11 +292,6 @@ describe("The LAD Table Set", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
openmct.time.timeSystem('utc', {
|
|
||||||
start: 0,
|
|
||||||
end: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
return resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,6 +19,10 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
import {
|
||||||
|
getAllSearchParams,
|
||||||
|
setAllSearchParams
|
||||||
|
} from 'utils/openmctLocation';
|
||||||
|
|
||||||
const TIME_EVENTS = ['timeSystem', 'clock', 'clockOffsets'];
|
const TIME_EVENTS = ['timeSystem', 'clock', 'clockOffsets'];
|
||||||
const SEARCH_MODE = 'tc.mode';
|
const SEARCH_MODE = 'tc.mode';
|
||||||
@ -45,8 +49,9 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
this.openmct.router.on('change:params', this.updateTimeSettings);
|
this.updateTimeSettings();
|
||||||
|
|
||||||
|
window.addEventListener('hashchange', this.updateTimeSettings);
|
||||||
TIME_EVENTS.forEach(event => {
|
TIME_EVENTS.forEach(event => {
|
||||||
this.openmct.time.on(event, this.setUrlFromTimeApi);
|
this.openmct.time.on(event, this.setUrlFromTimeApi);
|
||||||
});
|
});
|
||||||
@ -54,8 +59,7 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.openmct.router.off('change:params', this.updateTimeSettings);
|
window.removeEventListener('hashchange', this.updateTimeSettings);
|
||||||
|
|
||||||
this.openmct.off('start', this.initialize);
|
this.openmct.off('start', this.initialize);
|
||||||
this.openmct.off('destroy', this.destroy);
|
this.openmct.off('destroy', this.destroy);
|
||||||
|
|
||||||
@ -66,18 +70,22 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateTimeSettings() {
|
updateTimeSettings() {
|
||||||
let timeParameters = this.parseParametersFromUrl();
|
// Prevent from triggering self
|
||||||
|
if (!this.isUrlUpdateInProgress) {
|
||||||
|
let timeParameters = this.parseParametersFromUrl();
|
||||||
|
|
||||||
if (this.areTimeParametersValid(timeParameters)) {
|
if (this.areTimeParametersValid(timeParameters)) {
|
||||||
this.setTimeApiFromUrl(timeParameters);
|
this.setTimeApiFromUrl(timeParameters);
|
||||||
this.openmct.router.setLocationFromUrl();
|
} else {
|
||||||
|
this.setUrlFromTimeApi();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.setUrlFromTimeApi();
|
this.isUrlUpdateInProgress = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parseParametersFromUrl() {
|
parseParametersFromUrl() {
|
||||||
let searchParams = this.openmct.router.getAllSearchParams();
|
let searchParams = getAllSearchParams();
|
||||||
|
|
||||||
let mode = searchParams.get(SEARCH_MODE);
|
let mode = searchParams.get(SEARCH_MODE);
|
||||||
let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
|
let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
|
||||||
@ -140,7 +148,7 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setUrlFromTimeApi() {
|
setUrlFromTimeApi() {
|
||||||
let searchParams = this.openmct.router.getAllSearchParams();
|
let searchParams = getAllSearchParams();
|
||||||
let clock = this.openmct.time.clock();
|
let clock = this.openmct.time.clock();
|
||||||
let bounds = this.openmct.time.bounds();
|
let bounds = this.openmct.time.bounds();
|
||||||
let clockOffsets = this.openmct.time.clockOffsets();
|
let clockOffsets = this.openmct.time.clockOffsets();
|
||||||
@ -168,7 +176,8 @@ export default class URLTimeSettingsSynchronizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
|
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
|
||||||
this.openmct.router.setAllSearchParams(searchParams);
|
this.isUrlUpdateInProgress = true;
|
||||||
|
setAllSearchParams(searchParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
areTimeParametersValid(timeParameters) {
|
areTimeParametersValid(timeParameters) {
|
||||||
|
@ -25,118 +25,306 @@ import {
|
|||||||
} from 'utils/testing';
|
} from 'utils/testing';
|
||||||
|
|
||||||
describe("The URLTimeSettingsSynchronizer", () => {
|
describe("The URLTimeSettingsSynchronizer", () => {
|
||||||
let appHolder;
|
|
||||||
let openmct;
|
let openmct;
|
||||||
let resolveFunction;
|
let testClock;
|
||||||
let oldHash;
|
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
openmct.install(openmct.plugins.MyItems());
|
|
||||||
openmct.install(openmct.plugins.LocalTimeSystem());
|
openmct.install(openmct.plugins.LocalTimeSystem());
|
||||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
testClock = jasmine.createSpyObj("testClock", ["start", "stop", "tick", "currentValue", "on", "off"]);
|
||||||
|
testClock.key = "test-clock";
|
||||||
|
testClock.currentValue.and.returnValue(0);
|
||||||
|
|
||||||
|
openmct.time.addClock(testClock);
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
|
openmct.startHeadless();
|
||||||
appHolder = document.createElement("div");
|
|
||||||
openmct.start(appHolder);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => resetApplicationState(openmct));
|
||||||
openmct.time.stopClock();
|
|
||||||
openmct.router.removeListener('change:hash', resolveFunction);
|
|
||||||
|
|
||||||
appHolder = undefined;
|
describe("realtime mode", () => {
|
||||||
openmct = undefined;
|
it("when the clock is set via the time API, it is immediately reflected in the URL", () => {
|
||||||
resolveFunction = undefined;
|
//Test expected initial conditions
|
||||||
|
|
||||||
return resetApplicationState(openmct);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("initial clock is set to fixed is reflected in URL", (done) => {
|
|
||||||
resolveFunction = () => {
|
|
||||||
oldHash = window.location.hash;
|
|
||||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
|
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
|
||||||
|
|
||||||
openmct.router.removeListener('change:hash', resolveFunction);
|
|
||||||
done();
|
|
||||||
};
|
|
||||||
|
|
||||||
openmct.router.on('change:hash', resolveFunction);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when the clock is set via the time API, it is reflected in the URL", (done) => {
|
|
||||||
let success;
|
|
||||||
|
|
||||||
resolveFunction = () => {
|
|
||||||
openmct.time.clock('local', {
|
openmct.time.clock('local', {
|
||||||
|
start: -1000,
|
||||||
|
end: 100
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(window.location.hash.includes('tc.mode=local')).toBe(true);
|
||||||
|
|
||||||
|
//Test that expected initial conditions are no longer true
|
||||||
|
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
||||||
|
});
|
||||||
|
it("when offsets are set via the time API, they are immediately reflected in the URL", () => {
|
||||||
|
//Test expected initial conditions
|
||||||
|
expect(window.location.hash.includes('tc.startDelta')).toBe(false);
|
||||||
|
expect(window.location.hash.includes('tc.endDelta')).toBe(false);
|
||||||
|
|
||||||
|
openmct.time.clock('local', {
|
||||||
|
start: -1000,
|
||||||
|
end: 100
|
||||||
|
});
|
||||||
|
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
|
||||||
|
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
|
||||||
|
|
||||||
|
openmct.time.clockOffsets({
|
||||||
start: -2000,
|
start: -2000,
|
||||||
end: 200
|
end: 200
|
||||||
});
|
});
|
||||||
|
expect(window.location.hash.includes('tc.startDelta=2000')).toBe(true);
|
||||||
|
expect(window.location.hash.includes('tc.endDelta=200')).toBe(true);
|
||||||
|
|
||||||
const hasStartDelta = window.location.hash.includes('tc.startDelta=2000');
|
//Test that expected initial conditions are no longer true
|
||||||
const hasEndDelta = window.location.hash.includes('tc.endDelta=200');
|
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
||||||
const hasLocalClock = window.location.hash.includes('tc.mode=local');
|
});
|
||||||
success = hasStartDelta && hasEndDelta && hasLocalClock;
|
describe("when set in the url", () => {
|
||||||
if (success) {
|
it("will change from fixed to realtime mode when the mode changes", () => {
|
||||||
expect(success).toBe(true);
|
expectLocationToBeInFixedMode();
|
||||||
|
|
||||||
openmct.router.removeListener('change:hash', resolveFunction);
|
return switchToRealtimeMode().then(() => {
|
||||||
done();
|
let clock = openmct.time.clock();
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
openmct.router.on('change:hash', resolveFunction);
|
expect(clock).toBeDefined();
|
||||||
|
expect(clock.key).toBe('local');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("the clock is correctly set in the API from the URL parameters", () => {
|
||||||
|
return switchToRealtimeMode().then(() => {
|
||||||
|
let resolveFunction;
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
resolveFunction = resolve;
|
||||||
|
|
||||||
|
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
||||||
|
//detected in the API.
|
||||||
|
openmct.time.on('clock', resolveFunction);
|
||||||
|
let hash = window.location.hash;
|
||||||
|
hash = hash.replace('tc.mode=local', 'tc.mode=test-clock');
|
||||||
|
window.location.hash = hash;
|
||||||
|
}).then(() => {
|
||||||
|
let clock = openmct.time.clock();
|
||||||
|
expect(clock).toBeDefined();
|
||||||
|
expect(clock.key).toBe('test-clock');
|
||||||
|
openmct.time.off('clock', resolveFunction);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("the clock offsets are correctly set in the API from the URL parameters", () => {
|
||||||
|
return switchToRealtimeMode().then(() => {
|
||||||
|
let resolveFunction;
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
resolveFunction = resolve;
|
||||||
|
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
||||||
|
//detected in the API.
|
||||||
|
openmct.time.on('clockOffsets', resolveFunction);
|
||||||
|
let hash = window.location.hash;
|
||||||
|
hash = hash.replace('tc.startDelta=1000', 'tc.startDelta=2000');
|
||||||
|
hash = hash.replace('tc.endDelta=100', 'tc.endDelta=200');
|
||||||
|
window.location.hash = hash;
|
||||||
|
}).then(() => {
|
||||||
|
let clockOffsets = openmct.time.clockOffsets();
|
||||||
|
expect(clockOffsets).toBeDefined();
|
||||||
|
expect(clockOffsets.start).toBe(-2000);
|
||||||
|
expect(clockOffsets.end).toBe(200);
|
||||||
|
openmct.time.off('clockOffsets', resolveFunction);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("the time system is correctly set in the API from the URL parameters", () => {
|
||||||
|
return switchToRealtimeMode().then(() => {
|
||||||
|
let resolveFunction;
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
resolveFunction = resolve;
|
||||||
|
|
||||||
|
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
||||||
|
//detected in the API.
|
||||||
|
openmct.time.on('timeSystem', resolveFunction);
|
||||||
|
let hash = window.location.hash;
|
||||||
|
hash = hash.replace('tc.timeSystem=utc', 'tc.timeSystem=local');
|
||||||
|
window.location.hash = hash;
|
||||||
|
}).then(() => {
|
||||||
|
let timeSystem = openmct.time.timeSystem();
|
||||||
|
expect(timeSystem).toBeDefined();
|
||||||
|
expect(timeSystem.key).toBe('local');
|
||||||
|
openmct.time.off('timeSystem', resolveFunction);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("fixed timespan mode", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
openmct.time.stopClock();
|
||||||
|
openmct.time.timeSystem('utc', {
|
||||||
|
start: 0,
|
||||||
|
end: 1
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when bounds are set via the time API, they are immediately reflected in the URL", () => {
|
||||||
|
//Test expected initial conditions
|
||||||
|
expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
|
||||||
|
expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
|
||||||
|
|
||||||
|
openmct.time.bounds({
|
||||||
|
start: 10,
|
||||||
|
end: 20
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(window.location.hash.includes('tc.startBound=10')).toBe(true);
|
||||||
|
expect(window.location.hash.includes('tc.endBound=20')).toBe(true);
|
||||||
|
|
||||||
|
//Test that expected initial conditions are no longer true
|
||||||
|
expect(window.location.hash.includes('tc.startBound=0')).toBe(false);
|
||||||
|
expect(window.location.hash.includes('tc.endBound=1')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("when time system is set via the time API, it is immediately reflected in the URL", () => {
|
||||||
|
//Test expected initial conditions
|
||||||
|
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(true);
|
||||||
|
|
||||||
|
openmct.time.timeSystem('local', {
|
||||||
|
start: 20,
|
||||||
|
end: 30
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(window.location.hash.includes('tc.timeSystem=local')).toBe(true);
|
||||||
|
|
||||||
|
//Test that expected initial conditions are no longer true
|
||||||
|
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(false);
|
||||||
|
});
|
||||||
|
describe("when set in the url", () => {
|
||||||
|
it("time system changes are reflected in the API", () => {
|
||||||
|
let resolveFunction;
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let timeSystem = openmct.time.timeSystem();
|
||||||
|
resolveFunction = resolve;
|
||||||
|
|
||||||
|
expect(timeSystem.key).toBe('utc');
|
||||||
|
window.location.hash = window.location.hash.replace('tc.timeSystem=utc', 'tc.timeSystem=local');
|
||||||
|
|
||||||
|
openmct.time.on('timeSystem', resolveFunction);
|
||||||
|
}).then(() => {
|
||||||
|
let timeSystem = openmct.time.timeSystem();
|
||||||
|
expect(timeSystem.key).toBe('local');
|
||||||
|
|
||||||
|
openmct.time.off('timeSystem', resolveFunction);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("mode can be changed from realtime to fixed", () => {
|
||||||
|
return switchToRealtimeMode().then(() => {
|
||||||
|
expectLocationToBeInRealtimeMode();
|
||||||
|
|
||||||
|
expect(openmct.time.clock()).toBeDefined();
|
||||||
|
}).then(switchToFixedMode).then(() => {
|
||||||
|
let clock = openmct.time.clock();
|
||||||
|
expect(clock).not.toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("bounds are correctly set in the API from the URL parameters", () => {
|
||||||
|
let resolveFunction;
|
||||||
|
|
||||||
|
expectLocationToBeInFixedMode();
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
resolveFunction = resolve;
|
||||||
|
openmct.time.on('bounds', resolveFunction);
|
||||||
|
let hash = window.location.hash;
|
||||||
|
hash = hash.replace('tc.startBound=0', 'tc.startBound=222')
|
||||||
|
.replace('tc.endBound=1', 'tc.endBound=333');
|
||||||
|
window.location.hash = hash;
|
||||||
|
}).then(() => {
|
||||||
|
let bounds = openmct.time.bounds();
|
||||||
|
|
||||||
|
expect(bounds).toBeDefined();
|
||||||
|
expect(bounds.start).toBe(222);
|
||||||
|
expect(bounds.end).toBe(333);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("bounds are correctly set in the API from the URL parameters where only the end bound changes", () => {
|
||||||
|
let resolveFunction;
|
||||||
|
|
||||||
|
expectLocationToBeInFixedMode();
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
resolveFunction = resolve;
|
||||||
|
openmct.time.on('bounds', resolveFunction);
|
||||||
|
let hash = window.location.hash;
|
||||||
|
hash = hash.replace('tc.endBound=1', 'tc.endBound=333');
|
||||||
|
window.location.hash = hash;
|
||||||
|
}).then(() => {
|
||||||
|
let bounds = openmct.time.bounds();
|
||||||
|
|
||||||
|
expect(bounds).toBeDefined();
|
||||||
|
expect(bounds.start).toBe(0);
|
||||||
|
expect(bounds.end).toBe(333);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("when the clock mode is set to local, it is reflected in the URL", (done) => {
|
function setRealtimeLocationParameters() {
|
||||||
let success;
|
let hash = window.location.hash.toString()
|
||||||
|
.replace('tc.mode=fixed', 'tc.mode=local')
|
||||||
|
.replace('tc.startBound=0', 'tc.startDelta=1000')
|
||||||
|
.replace('tc.endBound=1', 'tc.endDelta=100');
|
||||||
|
|
||||||
resolveFunction = () => {
|
window.location.hash = hash;
|
||||||
let hash = window.location.hash;
|
}
|
||||||
hash = hash.replace('tc.mode=fixed', 'tc.mode=local');
|
|
||||||
window.location.hash = hash;
|
|
||||||
|
|
||||||
success = window.location.hash.includes('tc.mode=local');
|
function setFixedLocationParameters() {
|
||||||
if (success) {
|
let hash = window.location.hash.toString()
|
||||||
expect(success).toBe(true);
|
.replace('tc.mode=local', 'tc.mode=fixed')
|
||||||
done();
|
.replace('tc.timeSystem=utc', 'tc.timeSystem=local')
|
||||||
}
|
.replace('tc.startDelta=1000', 'tc.startBound=50')
|
||||||
};
|
.replace('tc.endDelta=100', 'tc.endBound=60');
|
||||||
|
|
||||||
openmct.router.on('change:hash', resolveFunction);
|
window.location.hash = hash;
|
||||||
});
|
}
|
||||||
|
|
||||||
it("when the clock mode is set to local, it is reflected in the URL", (done) => {
|
function switchToRealtimeMode() {
|
||||||
let success;
|
let resolveFunction;
|
||||||
|
|
||||||
resolveFunction = () => {
|
return new Promise((resolve) => {
|
||||||
let hash = window.location.hash;
|
resolveFunction = resolve;
|
||||||
|
openmct.time.on('clock', resolveFunction);
|
||||||
|
setRealtimeLocationParameters();
|
||||||
|
}).then(() => {
|
||||||
|
openmct.time.off('clock', resolveFunction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
hash = hash.replace('tc.mode=fixed', 'tc.mode=local');
|
function switchToFixedMode() {
|
||||||
window.location.hash = hash;
|
let resolveFunction;
|
||||||
success = window.location.hash.includes('tc.mode=local');
|
|
||||||
if (success) {
|
|
||||||
expect(success).toBe(true);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
openmct.router.on('change:hash', resolveFunction);
|
return new Promise((resolve) => {
|
||||||
});
|
resolveFunction = resolve;
|
||||||
|
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
||||||
|
//detected in the API.
|
||||||
|
openmct.time.on('clock', resolveFunction);
|
||||||
|
setFixedLocationParameters();
|
||||||
|
}).then(() => {
|
||||||
|
openmct.time.off('clock', resolveFunction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
it("reset hash", (done) => {
|
function expectLocationToBeInRealtimeMode() {
|
||||||
let success;
|
expect(window.location.hash.includes('tc.mode=local')).toBe(true);
|
||||||
|
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
|
||||||
|
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
|
||||||
|
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
||||||
|
}
|
||||||
|
|
||||||
window.location.hash = oldHash;
|
function expectLocationToBeInFixedMode() {
|
||||||
resolveFunction = () => {
|
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
|
||||||
success = window.location.hash === oldHash;
|
expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
|
||||||
if (success) {
|
expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
|
||||||
expect(success).toBe(true);
|
expect(window.location.hash.includes('tc.mode=local')).toBe(false);
|
||||||
done();
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
openmct.router.on('change:hash', resolveFunction);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -82,9 +82,7 @@ export default class Condition extends EventEmitter {
|
|||||||
if (this.isAnyOrAllTelemetry(criterion)) {
|
if (this.isAnyOrAllTelemetry(criterion)) {
|
||||||
criterion.updateResult(datum, this.conditionManager.telemetryObjects);
|
criterion.updateResult(datum, this.conditionManager.telemetryObjects);
|
||||||
} else {
|
} else {
|
||||||
if (criterion.usesTelemetry(datum.id)) {
|
criterion.updateResult(datum);
|
||||||
criterion.updateResult(datum);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -104,7 +102,7 @@ export default class Condition extends EventEmitter {
|
|||||||
|
|
||||||
isTelemetryUsed(id) {
|
isTelemetryUsed(id) {
|
||||||
return this.criteria.some(criterion => {
|
return this.criteria.some(criterion => {
|
||||||
return this.isAnyOrAllTelemetry(criterion) || criterion.usesTelemetry(id);
|
return this.isAnyOrAllTelemetry(criterion) || criterion.telemetryObjectIdAsString === id;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,11 +270,11 @@ export default class Condition extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
requestLADConditionResult(options) {
|
requestLADConditionResult() {
|
||||||
let latestTimestamp;
|
let latestTimestamp;
|
||||||
let criteriaResults = {};
|
let criteriaResults = {};
|
||||||
const criteriaRequests = this.criteria
|
const criteriaRequests = this.criteria
|
||||||
.map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects, options));
|
.map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects));
|
||||||
|
|
||||||
return Promise.all(criteriaRequests)
|
return Promise.all(criteriaRequests)
|
||||||
.then(results => {
|
.then(results => {
|
||||||
|
@ -282,7 +282,7 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
return currentCondition;
|
return currentCondition;
|
||||||
}
|
}
|
||||||
|
|
||||||
requestLADConditionSetOutput(options) {
|
requestLADConditionSetOutput() {
|
||||||
if (!this.conditions.length) {
|
if (!this.conditions.length) {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
@ -291,7 +291,7 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
let latestTimestamp;
|
let latestTimestamp;
|
||||||
let conditionResults = {};
|
let conditionResults = {};
|
||||||
const conditionRequests = this.conditions
|
const conditionRequests = this.conditions
|
||||||
.map(condition => condition.requestLADConditionResult(options));
|
.map(condition => condition.requestLADConditionResult());
|
||||||
|
|
||||||
return Promise.all(conditionRequests)
|
return Promise.all(conditionRequests)
|
||||||
.then((results) => {
|
.then((results) => {
|
||||||
|
@ -40,10 +40,10 @@ export default class ConditionSetTelemetryProvider {
|
|||||||
return domainObject.type === 'conditionSet';
|
return domainObject.type === 'conditionSet';
|
||||||
}
|
}
|
||||||
|
|
||||||
request(domainObject, options) {
|
request(domainObject) {
|
||||||
let conditionManager = this.getConditionManager(domainObject);
|
let conditionManager = this.getConditionManager(domainObject);
|
||||||
|
|
||||||
return conditionManager.requestLADConditionSetOutput(options)
|
return conditionManager.requestLADConditionSetOutput()
|
||||||
.then(latestOutput => {
|
.then(latestOutput => {
|
||||||
return latestOutput;
|
return latestOutput;
|
||||||
});
|
});
|
||||||
@ -52,9 +52,7 @@ export default class ConditionSetTelemetryProvider {
|
|||||||
subscribe(domainObject, callback) {
|
subscribe(domainObject, callback) {
|
||||||
let conditionManager = this.getConditionManager(domainObject);
|
let conditionManager = this.getConditionManager(domainObject);
|
||||||
|
|
||||||
conditionManager.on('conditionSetResultUpdated', (data) => {
|
conditionManager.on('conditionSetResultUpdated', callback);
|
||||||
callback(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.destroyConditionManager.bind(this, this.openmct.objects.makeKeyString(domainObject.identifier));
|
return this.destroyConditionManager.bind(this, this.openmct.objects.makeKeyString(domainObject.identifier));
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,6 @@ export default class StyleRuleManager extends EventEmitter {
|
|||||||
if (styleConfiguration) {
|
if (styleConfiguration) {
|
||||||
this.initialize(styleConfiguration);
|
this.initialize(styleConfiguration);
|
||||||
if (styleConfiguration.conditionSetIdentifier) {
|
if (styleConfiguration.conditionSetIdentifier) {
|
||||||
this.openmct.time.on("bounds", this.refreshData.bind(this));
|
|
||||||
this.subscribeToConditionSet();
|
this.subscribeToConditionSet();
|
||||||
} else {
|
} else {
|
||||||
this.applyStaticStyle();
|
this.applyStaticStyle();
|
||||||
@ -84,25 +83,6 @@ export default class StyleRuleManager extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshData(bounds, isTick) {
|
|
||||||
if (!isTick) {
|
|
||||||
let options = {
|
|
||||||
start: bounds.start,
|
|
||||||
end: bounds.end,
|
|
||||||
size: 1,
|
|
||||||
strategy: 'latest'
|
|
||||||
};
|
|
||||||
this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => {
|
|
||||||
this.openmct.telemetry.request(conditionSetDomainObject, options)
|
|
||||||
.then(output => {
|
|
||||||
if (output && output.length) {
|
|
||||||
this.handleConditionSetResultUpdated(output[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateObjectStyleConfig(styleConfiguration) {
|
updateObjectStyleConfig(styleConfiguration) {
|
||||||
if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) {
|
if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) {
|
||||||
this.initialize(styleConfiguration || {});
|
this.initialize(styleConfiguration || {});
|
||||||
@ -180,14 +160,10 @@ export default class StyleRuleManager extends EventEmitter {
|
|||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.stopProvidingTelemetry) {
|
if (this.stopProvidingTelemetry) {
|
||||||
|
|
||||||
this.stopProvidingTelemetry();
|
this.stopProvidingTelemetry();
|
||||||
delete this.stopProvidingTelemetry;
|
delete this.stopProvidingTelemetry;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.openmct.time.off("bounds", this.refreshData);
|
|
||||||
this.openmct.editor.off('isEditing', this.toggleSubscription);
|
|
||||||
|
|
||||||
this.conditionSetIdentifier = undefined;
|
this.conditionSetIdentifier = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +52,7 @@
|
|||||||
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
||||||
<a v-if="conditionSetDomainObject"
|
<a v-if="conditionSetDomainObject"
|
||||||
class="c-object-label icon-conditional"
|
class="c-object-label icon-conditional"
|
||||||
|
:href="navigateToPath"
|
||||||
@click="navigateOrPreview"
|
@click="navigateOrPreview"
|
||||||
>
|
>
|
||||||
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
|
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
|
||||||
@ -285,8 +286,6 @@ export default {
|
|||||||
if (this.openmct.editor.isEditing()) {
|
if (this.openmct.editor.isEditing()) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.previewAction.invoke(this.objectPath);
|
this.previewAction.invoke(this.objectPath);
|
||||||
} else {
|
|
||||||
this.openmct.router.navigate(this.navigateToPath);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeConditionSet() {
|
removeConditionSet() {
|
||||||
|
@ -66,6 +66,7 @@
|
|||||||
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
|
||||||
<a v-if="conditionSetDomainObject"
|
<a v-if="conditionSetDomainObject"
|
||||||
class="c-object-label"
|
class="c-object-label"
|
||||||
|
:href="navigateToPath"
|
||||||
@click="navigateOrPreview"
|
@click="navigateOrPreview"
|
||||||
>
|
>
|
||||||
<span class="c-object-label__type-icon icon-conditional"></span>
|
<span class="c-object-label__type-icon icon-conditional"></span>
|
||||||
@ -308,8 +309,6 @@ export default {
|
|||||||
if (this.openmct.editor.isEditing()) {
|
if (this.openmct.editor.isEditing()) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.previewAction.invoke(this.objectPath);
|
this.previewAction.invoke(this.objectPath);
|
||||||
} else {
|
|
||||||
this.openmct.router.navigate(this.navigateToPath);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isItemType(type, item) {
|
isItemType(type, item) {
|
||||||
@ -345,11 +344,6 @@ export default {
|
|||||||
const layoutItem = selectionItem[0].context.layoutItem;
|
const layoutItem = selectionItem[0].context.layoutItem;
|
||||||
const isChildItem = selectionItem.length > 1;
|
const isChildItem = selectionItem.length > 1;
|
||||||
|
|
||||||
if (!item && !layoutItem) {
|
|
||||||
// cases where selection is used for table cells
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isChildItem) {
|
if (!isChildItem) {
|
||||||
domainObject = item;
|
domainObject = item;
|
||||||
itemStyle = getApplicableStylesForItem(item);
|
itemStyle = getApplicableStylesForItem(item);
|
||||||
|
@ -147,16 +147,12 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
|
|||||||
this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry);
|
this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestLAD(telemetryObjects, requestOptions) {
|
requestLAD(telemetryObjects) {
|
||||||
let options = {
|
const options = {
|
||||||
strategy: 'latest',
|
strategy: 'latest',
|
||||||
size: 1
|
size: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
if (requestOptions !== undefined) {
|
|
||||||
options = Object.assign(options, requestOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.isValid()) {
|
if (!this.isValid()) {
|
||||||
return this.formatData({}, telemetryObjects);
|
return this.formatData({}, telemetryObjects);
|
||||||
}
|
}
|
||||||
|
@ -58,10 +58,6 @@ export default class TelemetryCriterion extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
usesTelemetry(id) {
|
|
||||||
return this.telemetryObjectIdAsString && (this.telemetryObjectIdAsString === id);
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribeForStaleData() {
|
subscribeForStaleData() {
|
||||||
if (this.stalenessSubscription) {
|
if (this.stalenessSubscription) {
|
||||||
this.stalenessSubscription.clear();
|
this.stalenessSubscription.clear();
|
||||||
@ -137,16 +133,12 @@ export default class TelemetryCriterion extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
requestLAD(telemetryObjects, requestOptions) {
|
requestLAD() {
|
||||||
let options = {
|
const options = {
|
||||||
strategy: 'latest',
|
strategy: 'latest',
|
||||||
size: 1
|
size: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
if (requestOptions !== undefined) {
|
|
||||||
options = Object.assign(options, requestOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.isValid()) {
|
if (!this.isValid()) {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
@ -104,7 +104,7 @@ export function getConsolidatedStyleValues(multipleItemStyles) {
|
|||||||
const properties = Object.keys(styleProps);
|
const properties = Object.keys(styleProps);
|
||||||
properties.forEach((property) => {
|
properties.forEach((property) => {
|
||||||
const values = aggregatedStyleValues[property];
|
const values = aggregatedStyleValues[property];
|
||||||
if (values && values.length) {
|
if (values.length) {
|
||||||
if (values.every(value => value === values[0])) {
|
if (values.every(value => value === values[0])) {
|
||||||
styleValues[property] = values[0];
|
styleValues[property] = values[0];
|
||||||
} else {
|
} else {
|
||||||
|
@ -46,7 +46,7 @@ xdescribe("the plugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
return resetApplicationState(openmct);
|
resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('installs the new folder action', () => {
|
it('installs the new folder action', () => {
|
||||||
|
31
src/plugins/devices/plugin.js
Normal file
31
src/plugins/devices/plugin.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
export default function plugin() {
|
||||||
|
return function install(openmct) {
|
||||||
|
openmct.on('start', () => {
|
||||||
|
const body = document.getElementsByTagName('body')[0];
|
||||||
|
body.classList.add('desktop');
|
||||||
|
body.classList.add('portrait');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
@ -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 if (this.domainObject.isMutable) {
|
} else {
|
||||||
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 if (this.domainObject.isMutable) {
|
} else {
|
||||||
this.openmct.objects.destroyMutable(this.domainObject);
|
this.openmct.objects.destroyMutable(this.domainObject);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -269,12 +269,7 @@ export default {
|
|||||||
},
|
},
|
||||||
subscribeToObject() {
|
subscribeToObject() {
|
||||||
this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) {
|
this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) {
|
||||||
const key = this.openmct.time.timeSystem().key;
|
if (this.openmct.time.clock() !== undefined) {
|
||||||
const datumTimeStamp = datum[key];
|
|
||||||
if (this.openmct.time.clock() !== undefined
|
|
||||||
|| (datumTimeStamp
|
|
||||||
&& (this.openmct.time.bounds().end >= datumTimeStamp))
|
|
||||||
) {
|
|
||||||
this.updateView(datum);
|
this.updateView(datum);
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
@ -112,7 +112,7 @@ describe("The Duplicate Action plugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
return resetApplicationState(openmct);
|
resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be defined", () => {
|
it("should be defined", () => {
|
||||||
|
@ -90,12 +90,14 @@ export default {
|
|||||||
this.composition.load();
|
this.composition.load();
|
||||||
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
|
this.unobserve = this.openmct.objects.observe(this.providedObject, 'configuration.filters', this.updatePersistedFilters);
|
||||||
this.unobserveGlobalFilters = this.openmct.objects.observe(this.providedObject, 'configuration.globalFilters', this.updateGlobalFilters);
|
this.unobserveGlobalFilters = this.openmct.objects.observe(this.providedObject, 'configuration.globalFilters', this.updateGlobalFilters);
|
||||||
|
this.unobserveAllMutation = this.openmct.objects.observe(this.providedObject, '*', (mutatedObject) => this.providedObject = mutatedObject);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.composition.off('add', this.addChildren);
|
this.composition.off('add', this.addChildren);
|
||||||
this.composition.off('remove', this.removeChildren);
|
this.composition.off('remove', this.removeChildren);
|
||||||
this.unobserve();
|
this.unobserve();
|
||||||
this.unobserveGlobalFilters();
|
this.unobserveGlobalFilters();
|
||||||
|
this.unobserveAllMutation();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addChildren(domainObject) {
|
addChildren(domainObject) {
|
||||||
@ -156,28 +158,25 @@ export default {
|
|||||||
},
|
},
|
||||||
getGlobalFiltersToRemove(keyString) {
|
getGlobalFiltersToRemove(keyString) {
|
||||||
let filtersToRemove = new Set();
|
let filtersToRemove = new Set();
|
||||||
const child = this.children[keyString];
|
|
||||||
if (child && child.metadataWithFilters) {
|
|
||||||
const metadataWithFilters = child.metadataWithFilters;
|
|
||||||
metadataWithFilters.forEach(metadatum => {
|
|
||||||
let keepFilter = false;
|
|
||||||
Object.keys(this.children).forEach(childKeyString => {
|
|
||||||
if (childKeyString !== keyString) {
|
|
||||||
let filterMatched = this.children[childKeyString].metadataWithFilters.some(childMetadatum => childMetadatum.key === metadatum.key);
|
|
||||||
|
|
||||||
if (filterMatched) {
|
this.children[keyString].metadataWithFilters.forEach(metadatum => {
|
||||||
keepFilter = true;
|
let keepFilter = false;
|
||||||
|
Object.keys(this.children).forEach(childKeyString => {
|
||||||
|
if (childKeyString !== keyString) {
|
||||||
|
let filterMatched = this.children[childKeyString].metadataWithFilters.some(childMetadatum => childMetadatum.key === metadatum.key);
|
||||||
|
|
||||||
return;
|
if (filterMatched) {
|
||||||
}
|
keepFilter = true;
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (!keepFilter) {
|
|
||||||
filtersToRemove.add(metadatum.key);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
if (!keepFilter) {
|
||||||
|
filtersToRemove.add(metadatum.key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return Array.from(filtersToRemove);
|
return Array.from(filtersToRemove);
|
||||||
},
|
},
|
||||||
|
@ -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]"
|
||||||
@click="navigate"
|
:href="objectLink"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c-grid-item__type-icon"
|
class="c-grid-item__type-icon"
|
||||||
@ -49,17 +49,11 @@ import statusListener from './status-listener';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [contextMenuGesture, objectLink, statusListener],
|
mixins: [contextMenuGesture, objectLink, statusListener],
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
navigate() {
|
|
||||||
this.openmct.router.navigate(this.objectLink);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
ref="objectLink"
|
ref="objectLink"
|
||||||
class="c-object-label"
|
class="c-object-label"
|
||||||
:class="[statusClass]"
|
:class="[statusClass]"
|
||||||
@click="navigate"
|
:href="objectLink"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c-object-label__type-icon c-list-item__name__type-icon"
|
class="c-object-label__type-icon c-list-item__name__type-icon"
|
||||||
@ -45,7 +45,6 @@ import statusListener from './status-listener';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [contextMenuGesture, objectLink, statusListener],
|
mixins: [contextMenuGesture, objectLink, statusListener],
|
||||||
inject: ['openmct'],
|
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -57,7 +56,7 @@ export default {
|
|||||||
return moment(timestamp).format(format);
|
return moment(timestamp).format(format);
|
||||||
},
|
},
|
||||||
navigate() {
|
navigate() {
|
||||||
this.openmct.router.navigate(this.objectLink);
|
this.$refs.objectLink.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -19,10 +19,6 @@
|
|||||||
margin: 0 $interiorMargin $interiorMargin 0;
|
margin: 0 $interiorMargin $interiorMargin 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body.mobile & {
|
|
||||||
flex: 1 0 auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************* GRID ITEMS */
|
/******************************* GRID ITEMS */
|
||||||
|
@ -29,6 +29,13 @@ define([
|
|||||||
) {
|
) {
|
||||||
return function plugin() {
|
return function plugin() {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
|
openmct.types.addType('folder', {
|
||||||
|
name: "Folder",
|
||||||
|
key: "folder",
|
||||||
|
description: "Create folders to organize other objects or links to objects without the ability to edit it's properties.",
|
||||||
|
cssClass: "icon-folder",
|
||||||
|
creatable: true
|
||||||
|
});
|
||||||
openmct.objectViews.addProvider(new FolderGridView(openmct));
|
openmct.objectViews.addProvider(new FolderGridView(openmct));
|
||||||
openmct.objectViews.addProvider(new FolderListView(openmct));
|
openmct.objectViews.addProvider(new FolderListView(openmct));
|
||||||
};
|
};
|
||||||
|
@ -41,7 +41,7 @@ export default class GoToOriginalAction {
|
|||||||
.slice(1)
|
.slice(1)
|
||||||
.join('/');
|
.join('/');
|
||||||
|
|
||||||
this._openmct.router.navigate(url);
|
window.location.href = url;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
appliesTo(objectPath) {
|
appliesTo(objectPath) {
|
||||||
|
@ -47,6 +47,7 @@ describe("the plugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('when invoked', () => {
|
describe('when invoked', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockObjectPath = [{
|
mockObjectPath = [{
|
||||||
name: 'mock folder',
|
name: 'mock folder',
|
||||||
@ -62,15 +63,11 @@ describe("the plugin", () => {
|
|||||||
key: 'test'
|
key: 'test'
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
goToFolderAction.invoke(mockObjectPath);
|
goToFolderAction.invoke(mockObjectPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('goes to the original location', (done) => {
|
it('goes to the original location', () => {
|
||||||
setTimeout(() => {
|
expect(window.location.href).toContain('context.html#/browse/?tc.mode=fixed&tc.startBound=0&tc.endBound=1&tc.timeSystem=utc');
|
||||||
expect(window.location.href).toContain('context.html#/browse/?tc.mode=fixed&tc.startBound=0&tc.endBound=1&tc.timeSystem=utc');
|
|
||||||
done();
|
|
||||||
}, 1500);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="c-compass"
|
class="c-compass"
|
||||||
:style="`width: ${ sizedImageDimensions.width }px; height: ${ sizedImageDimensions.height }px`"
|
:style="compassDimensionsStyle"
|
||||||
>
|
>
|
||||||
<CompassHUD
|
<CompassHUD
|
||||||
v-if="hasCameraFieldOfView"
|
v-if="hasCameraFieldOfView"
|
||||||
@ -34,7 +34,6 @@
|
|||||||
<CompassRose
|
<CompassRose
|
||||||
v-if="hasCameraFieldOfView"
|
v-if="hasCameraFieldOfView"
|
||||||
:heading="heading"
|
:heading="heading"
|
||||||
:sized-image-width="sizedImageDimensions.width"
|
|
||||||
:sun-heading="sunHeading"
|
:sun-heading="sunHeading"
|
||||||
:camera-angle-of-view="cameraAngleOfView"
|
:camera-angle-of-view="cameraAngleOfView"
|
||||||
:camera-pan="cameraPan"
|
:camera-pan="cameraPan"
|
||||||
@ -78,20 +77,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
sizedImageDimensions() {
|
|
||||||
let sizedImageDimensions = {};
|
|
||||||
if ((this.containerWidth / this.containerHeight) > this.naturalAspectRatio) {
|
|
||||||
// container is wider than image
|
|
||||||
sizedImageDimensions.width = this.containerHeight * this.naturalAspectRatio;
|
|
||||||
sizedImageDimensions.height = this.containerHeight;
|
|
||||||
} else {
|
|
||||||
// container is taller than image
|
|
||||||
sizedImageDimensions.width = this.containerWidth;
|
|
||||||
sizedImageDimensions.height = this.containerWidth * this.naturalAspectRatio;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sizedImageDimensions;
|
|
||||||
},
|
|
||||||
hasCameraFieldOfView() {
|
hasCameraFieldOfView() {
|
||||||
return this.cameraPan !== undefined && this.cameraAngleOfView > 0;
|
return this.cameraPan !== undefined && this.cameraAngleOfView > 0;
|
||||||
},
|
},
|
||||||
@ -109,6 +94,25 @@ export default {
|
|||||||
},
|
},
|
||||||
cameraAngleOfView() {
|
cameraAngleOfView() {
|
||||||
return CAMERA_ANGLE_OF_VIEW;
|
return CAMERA_ANGLE_OF_VIEW;
|
||||||
|
},
|
||||||
|
compassDimensionsStyle() {
|
||||||
|
const containerAspectRatio = this.containerWidth / this.containerHeight;
|
||||||
|
|
||||||
|
let width;
|
||||||
|
let height;
|
||||||
|
|
||||||
|
if (containerAspectRatio < this.naturalAspectRatio) {
|
||||||
|
width = '100%';
|
||||||
|
height = `${ this.containerWidth / this.naturalAspectRatio }px`;
|
||||||
|
} else {
|
||||||
|
width = `${ this.containerHeight * this.naturalAspectRatio }px`;
|
||||||
|
height = '100%';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -22,134 +22,129 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="w-direction-rose"
|
class="c-direction-rose"
|
||||||
:class="compassRoseSizingClasses"
|
@click="toggleLockCompass"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="c-direction-rose"
|
class="c-nsew"
|
||||||
@click="toggleLockCompass"
|
:style="compassRoseStyle"
|
||||||
>
|
>
|
||||||
<div
|
<svg
|
||||||
class="c-nsew"
|
class="c-nsew__minor-ticks"
|
||||||
:style="compassRoseStyle"
|
viewBox="0 0 100 100"
|
||||||
>
|
>
|
||||||
<svg
|
<rect
|
||||||
class="c-nsew__minor-ticks"
|
class="c-nsew__tick c-tick-ne"
|
||||||
viewBox="0 0 100 100"
|
x="49"
|
||||||
>
|
y="0"
|
||||||
<rect
|
width="2"
|
||||||
class="c-nsew__tick c-tick-ne"
|
height="5"
|
||||||
x="49"
|
/>
|
||||||
y="0"
|
<rect
|
||||||
width="2"
|
class="c-nsew__tick c-tick-se"
|
||||||
height="5"
|
x="95"
|
||||||
/>
|
y="49"
|
||||||
<rect
|
width="5"
|
||||||
class="c-nsew__tick c-tick-se"
|
height="2"
|
||||||
x="95"
|
/>
|
||||||
y="49"
|
<rect
|
||||||
width="5"
|
class="c-nsew__tick c-tick-sw"
|
||||||
height="2"
|
x="49"
|
||||||
/>
|
y="95"
|
||||||
<rect
|
width="2"
|
||||||
class="c-nsew__tick c-tick-sw"
|
height="5"
|
||||||
x="49"
|
/>
|
||||||
y="95"
|
<rect
|
||||||
width="2"
|
class="c-nsew__tick c-tick-nw"
|
||||||
height="5"
|
x="0"
|
||||||
/>
|
y="49"
|
||||||
<rect
|
width="5"
|
||||||
class="c-nsew__tick c-tick-nw"
|
height="2"
|
||||||
x="0"
|
/>
|
||||||
y="49"
|
|
||||||
width="5"
|
|
||||||
height="2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
class="c-nsew__ticks"
|
class="c-nsew__ticks"
|
||||||
viewBox="0 0 100 100"
|
viewBox="0 0 100 100"
|
||||||
>
|
>
|
||||||
<polygon
|
<polygon
|
||||||
class="c-nsew__tick c-tick-n"
|
class="c-nsew__tick c-tick-n"
|
||||||
points="50,0 60,10 40,10"
|
points="50,0 57,5 43,5"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
class="c-nsew__tick c-tick-e"
|
class="c-nsew__tick c-tick-e"
|
||||||
x="95"
|
x="95"
|
||||||
y="49"
|
y="49"
|
||||||
width="5"
|
width="5"
|
||||||
height="2"
|
height="2"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
class="c-nsew__tick c-tick-w"
|
class="c-nsew__tick c-tick-w"
|
||||||
x="0"
|
x="0"
|
||||||
y="49"
|
y="49"
|
||||||
width="5"
|
width="5"
|
||||||
height="2"
|
height="2"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
class="c-nsew__tick c-tick-s"
|
class="c-nsew__tick c-tick-s"
|
||||||
x="49"
|
x="49"
|
||||||
y="95"
|
y="95"
|
||||||
width="2"
|
width="2"
|
||||||
height="5"
|
height="5"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<text
|
<text
|
||||||
class="c-nsew__label c-label-n"
|
class="c-nsew__label c-label-n"
|
||||||
text-anchor="middle"
|
text-anchor="middle"
|
||||||
:transform="northTextTransform"
|
:transform="northTextTransform"
|
||||||
>N</text>
|
>N</text>
|
||||||
<text
|
<text
|
||||||
class="c-nsew__label c-label-e"
|
class="c-nsew__label c-label-e"
|
||||||
text-anchor="middle"
|
text-anchor="middle"
|
||||||
:transform="eastTextTransform"
|
:transform="eastTextTransform"
|
||||||
>E</text>
|
>E</text>
|
||||||
<text
|
<text
|
||||||
class="c-nsew__label c-label-w"
|
class="c-nsew__label c-label-w"
|
||||||
text-anchor="middle"
|
text-anchor="middle"
|
||||||
:transform="southTextTransform"
|
:transform="southTextTransform"
|
||||||
>W</text>
|
>W</text>
|
||||||
<text
|
<text
|
||||||
class="c-nsew__label c-label-s"
|
class="c-nsew__label c-label-s"
|
||||||
text-anchor="middle"
|
text-anchor="middle"
|
||||||
:transform="westTextTransform"
|
:transform="westTextTransform"
|
||||||
>S</text>
|
>S</text>
|
||||||
</svg>
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="hasHeading"
|
||||||
|
class="c-spacecraft-body"
|
||||||
|
:style="headingStyle"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="hasSunHeading"
|
||||||
|
class="c-sun"
|
||||||
|
:style="sunHeadingStyle"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="c-cam-field"
|
||||||
|
:style="cameraPanStyle"
|
||||||
|
>
|
||||||
|
<div class="cam-field-half cam-field-half-l">
|
||||||
|
<div
|
||||||
|
class="cam-field-area"
|
||||||
|
:style="cameraFOVStyleLeftHalf"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="cam-field-half cam-field-half-r">
|
||||||
<div
|
<div
|
||||||
v-if="hasHeading"
|
class="cam-field-area"
|
||||||
class="c-spacecraft-body"
|
:style="cameraFOVStyleRightHalf"
|
||||||
:style="headingStyle"
|
></div>
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="hasSunHeading"
|
|
||||||
class="c-sun"
|
|
||||||
:style="sunHeadingStyle"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="c-cam-field"
|
|
||||||
:style="cameraPanStyle"
|
|
||||||
>
|
|
||||||
<div class="cam-field-half cam-field-half-l">
|
|
||||||
<div
|
|
||||||
class="cam-field-area"
|
|
||||||
:style="cameraFOVStyleLeftHalf"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<div class="cam-field-half cam-field-half-r">
|
|
||||||
<div
|
|
||||||
class="cam-field-area"
|
|
||||||
:style="cameraFOVStyleRightHalf"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -160,10 +155,6 @@ import { rotate } from './utils';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
sizedImageWidth: {
|
|
||||||
type: Number,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
heading: {
|
heading: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true
|
required: true
|
||||||
@ -186,24 +177,12 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
compassRoseSizingClasses() {
|
north() {
|
||||||
let compassRoseSizingClasses = '';
|
return this.lockCompass ? rotate(-this.cameraPan) : 0;
|
||||||
if (this.sizedImageWidth < 300) {
|
|
||||||
compassRoseSizingClasses = '--rose-small --rose-min';
|
|
||||||
} else if (this.sizedImageWidth < 500) {
|
|
||||||
compassRoseSizingClasses = '--rose-small';
|
|
||||||
} else if (this.sizedImageWidth > 1000) {
|
|
||||||
compassRoseSizingClasses = '--rose-max';
|
|
||||||
}
|
|
||||||
|
|
||||||
return compassRoseSizingClasses;
|
|
||||||
},
|
},
|
||||||
compassRoseStyle() {
|
compassRoseStyle() {
|
||||||
return { transform: `rotate(${ this.north }deg)` };
|
return { transform: `rotate(${ this.north }deg)` };
|
||||||
},
|
},
|
||||||
north() {
|
|
||||||
return this.lockCompass ? rotate(-this.cameraPan) : 0;
|
|
||||||
},
|
|
||||||
northTextTransform() {
|
northTextTransform() {
|
||||||
return this.cardinalPointsTextTransform.north;
|
return this.cardinalPointsTextTransform.north;
|
||||||
},
|
},
|
||||||
@ -225,10 +204,10 @@ export default {
|
|||||||
const rotation = `rotate(${ -this.north })`;
|
const rotation = `rotate(${ -this.north })`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
north: `translate(50,23) ${ rotation }`,
|
north: `translate(50,15) ${ rotation }`,
|
||||||
east: `translate(82,50) ${ rotation }`,
|
east: `translate(87,50) ${ rotation }`,
|
||||||
south: `translate(18,50) ${ rotation }`,
|
south: `translate(13,50) ${ rotation }`,
|
||||||
west: `translate(50,82) ${ rotation }`
|
west: `translate(50,87) ${ rotation }`
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
hasHeading() {
|
hasHeading() {
|
||||||
|
@ -10,7 +10,6 @@ $elemBg: rgba(black, 0.7);
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c-compass {
|
.c-compass {
|
||||||
pointer-events: none; // This allows the image element to receive a browser-level context click
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@ -21,253 +20,195 @@ $elemBg: rgba(black, 0.7);
|
|||||||
|
|
||||||
/***************************** COMPASS HUD */
|
/***************************** COMPASS HUD */
|
||||||
.c-hud {
|
.c-hud {
|
||||||
// To be placed within a imagery view, in the bounding box of the image
|
// To be placed within a imagery view, in the bounding box of the image
|
||||||
$m: 1px;
|
$m: 1px;
|
||||||
$padTB: 2px;
|
$padTB: 2px;
|
||||||
$padLR: $padTB;
|
$padLR: $padTB;
|
||||||
color: $interfaceKeyColor;
|
color: $interfaceKeyColor;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
|
position: absolute;
|
||||||
|
top: $m; right: $m; left: $m;
|
||||||
|
height: 18px;
|
||||||
|
|
||||||
|
svg, div {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: $m;
|
}
|
||||||
right: $m;
|
|
||||||
left: $m;
|
|
||||||
height: 18px;
|
|
||||||
|
|
||||||
svg, div {
|
&__display {
|
||||||
position: absolute;
|
height: 30px;
|
||||||
}
|
pointer-events: all;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&__display {
|
&__range {
|
||||||
height: 30px;
|
border: 1px solid $interfaceKeyColor;
|
||||||
pointer-events: all;
|
border-top-color: transparent;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 50%; right: $padLR; bottom: $padTB; left: $padLR;
|
||||||
right: 0;
|
}
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__range {
|
[class*="__dir"] {
|
||||||
border: 1px solid $interfaceKeyColor;
|
// NSEW
|
||||||
border-top-color: transparent;
|
display: inline-block;
|
||||||
position: absolute;
|
font-weight: bold;
|
||||||
top: 50%;
|
text-shadow: 0 1px 2px black;
|
||||||
right: $padLR;
|
top: 50%;
|
||||||
bottom: $padTB;
|
transform: translate(-50%,-50%);
|
||||||
left: $padLR;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="__dir"] {
|
[class*="__dir--sub"] {
|
||||||
// NSEW
|
font-weight: normal;
|
||||||
display: inline-block;
|
opacity: 0.5;
|
||||||
font-weight: bold;
|
}
|
||||||
text-shadow: 0 1px 2px black;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
[class*="__dir--sub"] {
|
&__sun {
|
||||||
font-weight: normal;
|
$s: 10px;
|
||||||
opacity: 0.5;
|
@include sun('circle farthest-side at bottom');
|
||||||
}
|
bottom: $padTB + 2px;
|
||||||
|
height: $s; width: $s*2;
|
||||||
&__sun {
|
opacity: 0.8;
|
||||||
$s: 10px;
|
transform: translateX(-50%);
|
||||||
@include sun('circle farthest-side at bottom');
|
z-index: 1;
|
||||||
bottom: $padTB + 2px;
|
}
|
||||||
height: $s;
|
|
||||||
width: $s*2;
|
|
||||||
opacity: 0.8;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/***************************** COMPASS DIRECTIONS */
|
/***************************** COMPASS DIRECTIONS */
|
||||||
.c-nsew {
|
.c-nsew {
|
||||||
$color: $interfaceKeyColor;
|
$color: $interfaceKeyColor;
|
||||||
$inset: 5%;
|
$inset: 7%;
|
||||||
$tickHeightPerc: 15%;
|
$tickHeightPerc: 15%;
|
||||||
text-shadow: black 0 0 10px;
|
text-shadow: black 0 0 10px;
|
||||||
top: $inset;
|
top: $inset; right: $inset; bottom: $inset; left: $inset;
|
||||||
right: $inset;
|
z-index: 3;
|
||||||
bottom: $inset;
|
|
||||||
left: $inset;
|
|
||||||
z-index: 3;
|
|
||||||
|
|
||||||
&__tick,
|
&__tick,
|
||||||
&__label {
|
&__label {
|
||||||
fill: $color;
|
fill: $color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__minor-ticks {
|
&__minor-ticks {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
dominant-baseline: central;
|
dominant-baseline: central;
|
||||||
font-size: 1.25em;
|
font-size: 0.8em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-label-n {
|
.c-label-n {
|
||||||
font-size: 2em;
|
font-size: 1.1em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/***************************** CAMERA FIELD ANGLE */
|
/***************************** CAMERA FIELD ANGLE */
|
||||||
.c-cam-field {
|
.c-cam-field {
|
||||||
$color: white;
|
$color: white;
|
||||||
opacity: 0.3;
|
opacity: 0.2;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
.cam-field-half {
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 2;
|
|
||||||
|
|
||||||
.cam-field-half {
|
.cam-field-area {
|
||||||
top: 0;
|
background: $color;
|
||||||
right: 0;
|
top: -30%;
|
||||||
bottom: 0;
|
right: 0;
|
||||||
left: 0;
|
bottom: -30%;
|
||||||
|
left: 0;
|
||||||
.cam-field-area {
|
|
||||||
background: $color;
|
|
||||||
top: -30%;
|
|
||||||
right: 0;
|
|
||||||
bottom: -30%;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// clip-paths overlap a bit to avoid a gap between halves
|
|
||||||
&-l {
|
|
||||||
clip-path: polygon(0 0, 50.5% 0, 50.5% 100%, 0 100%);
|
|
||||||
|
|
||||||
.cam-field-area {
|
|
||||||
transform-origin: left center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-r {
|
|
||||||
clip-path: polygon(49.5% 0, 100% 0, 100% 100%, 49.5% 100%);
|
|
||||||
|
|
||||||
.cam-field-area {
|
|
||||||
transform-origin: right center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clip-paths overlap a bit to avoid a gap between halves
|
||||||
|
&-l {
|
||||||
|
clip-path: polygon(0 0, 50.5% 0, 50.5% 100%, 0 100%);
|
||||||
|
.cam-field-area {
|
||||||
|
transform-origin: left center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-r {
|
||||||
|
clip-path: polygon(49.5% 0, 100% 0, 100% 100%, 49.5% 100%);
|
||||||
|
.cam-field-area {
|
||||||
|
transform-origin: right center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/***************************** SPACECRAFT BODY */
|
/***************************** SPACECRAFT BODY */
|
||||||
.c-spacecraft-body {
|
.c-spacecraft-body {
|
||||||
$color: $interfaceKeyColor;
|
$color: $interfaceKeyColor;
|
||||||
$s: 30%;
|
$s: 30%;
|
||||||
background: $color;
|
background: $color;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
height: $s;
|
height: $s; width: $s;
|
||||||
width: $s;
|
left: 50%; top: 50%;
|
||||||
left: 50%;
|
opacity: 0.4;
|
||||||
top: 50%;
|
transform-origin: center top;
|
||||||
opacity: 0.4;
|
|
||||||
transform-origin: center top;
|
|
||||||
transform: translateX(-50%); // center by default, overridden by CompassRose.vue / headingStyle()
|
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
// Direction arrow
|
// Direction arrow
|
||||||
$color: rgba(black, 0.5);
|
$color: rgba(black, 0.5);
|
||||||
$arwPointerY: 60%;
|
$arwPointerY: 60%;
|
||||||
$arwBodyOffset: 25%;
|
$arwBodyOffset: 25%;
|
||||||
background: $color;
|
background: $color;
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10%;
|
top: 10%; right: 20%; bottom: 50%; left: 20%;
|
||||||
right: 20%;
|
clip-path: polygon(50% 0, 100% $arwPointerY, 100%-$arwBodyOffset $arwPointerY, 100%-$arwBodyOffset 100%, $arwBodyOffset 100%, $arwBodyOffset $arwPointerY, 0 $arwPointerY);
|
||||||
bottom: 50%;
|
}
|
||||||
left: 20%;
|
|
||||||
clip-path: polygon(50% 0, 100% $arwPointerY, 100%-$arwBodyOffset $arwPointerY, 100%-$arwBodyOffset 100%, $arwBodyOffset 100%, $arwBodyOffset $arwPointerY, 0 $arwPointerY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/***************************** DIRECTION ROSE */
|
/***************************** DIRECTION ROSE */
|
||||||
.w-direction-rose {
|
|
||||||
$s: 10%;
|
|
||||||
$m: 2%;
|
|
||||||
position: absolute;
|
|
||||||
bottom: $m;
|
|
||||||
left: $m;
|
|
||||||
width: $s;
|
|
||||||
padding-top: $s;
|
|
||||||
|
|
||||||
&.--rose-min {
|
|
||||||
$s: 30px;
|
|
||||||
width: $s;
|
|
||||||
padding-top: $s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.--rose-small {
|
|
||||||
.c-nsew__minor-ticks,
|
|
||||||
.c-tick-w,
|
|
||||||
.c-tick-s,
|
|
||||||
.c-tick-e,
|
|
||||||
.c-label-w,
|
|
||||||
.c-label-s,
|
|
||||||
.c-label-e {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c-label-n {
|
|
||||||
font-size: 2.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.--rose-max {
|
|
||||||
$s: 100px;
|
|
||||||
width: $s;
|
|
||||||
padding-top: $s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.c-direction-rose {
|
.c-direction-rose {
|
||||||
$c2: rgba(white, 0.1);
|
$d: 100px;
|
||||||
background: $elemBg;
|
$c2: rgba(white, 0.1);
|
||||||
background-image: radial-gradient(circle closest-side, transparent, transparent 80%, $c2);
|
background: $elemBg;
|
||||||
transform-origin: 0 0;
|
background-image: radial-gradient(circle closest-side, transparent, transparent 80%, $c2);
|
||||||
|
width: $d;
|
||||||
|
height: $d;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px; left: 10px;
|
||||||
|
clip-path: circle(50% at 50% 50%);
|
||||||
|
border-radius: 100%;
|
||||||
|
|
||||||
|
svg, div {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sun
|
||||||
|
.c-sun {
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
clip-path: circle(50% at 50% 50%);
|
|
||||||
border-radius: 100%;
|
|
||||||
pointer-events: all;
|
|
||||||
|
|
||||||
svg, div {
|
&:before {
|
||||||
position: absolute;
|
$s: 35%;
|
||||||
}
|
@include sun();
|
||||||
|
content: '';
|
||||||
// Sun
|
display: block;
|
||||||
.c-sun {
|
position: absolute;
|
||||||
top: 0;
|
opacity: 0.7;
|
||||||
right: 0;
|
top: 0; left: 50%;
|
||||||
bottom: 0;
|
height:$s; width: $s;
|
||||||
left: 0;
|
transform: translate(-50%, -60%);
|
||||||
|
|
||||||
&:before {
|
|
||||||
$s: 35%;
|
|
||||||
@include sun();
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
opacity: 0.7;
|
|
||||||
top: 0;
|
|
||||||
left: 50%;
|
|
||||||
height: $s;
|
|
||||||
width: $s;
|
|
||||||
transform: translate(-50%, -60%);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,14 +135,9 @@
|
|||||||
:class="{ selected: focusedImageIndex === index && isPaused }"
|
:class="{ selected: focusedImageIndex === index && isPaused }"
|
||||||
@click="setFocusedImage(index, thumbnailClick)"
|
@click="setFocusedImage(index, thumbnailClick)"
|
||||||
>
|
>
|
||||||
<a href=""
|
<img class="c-thumb__image"
|
||||||
:download="image.imageDownloadName"
|
:src="image.url"
|
||||||
@click.prevent
|
|
||||||
>
|
>
|
||||||
<img class="c-thumb__image"
|
|
||||||
:src="image.url"
|
|
||||||
>
|
|
||||||
</a>
|
|
||||||
<div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
|
<div class="c-thumb__timestamp">{{ image.formattedTime }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -223,9 +218,6 @@ export default {
|
|||||||
canTrackDuration() {
|
canTrackDuration() {
|
||||||
return this.openmct.time.clock() && this.timeSystem.isUTCBased;
|
return this.openmct.time.clock() && this.timeSystem.isUTCBased;
|
||||||
},
|
},
|
||||||
focusedImageDownloadName() {
|
|
||||||
return this.getImageDownloadName(this.focusedImage);
|
|
||||||
},
|
|
||||||
isNextDisabled() {
|
isNextDisabled() {
|
||||||
let disabled = false;
|
let disabled = false;
|
||||||
|
|
||||||
@ -353,7 +345,6 @@ export default {
|
|||||||
this.imageHints = { ...this.metadata.valuesForHints(['image'])[0] };
|
this.imageHints = { ...this.metadata.valuesForHints(['image'])[0] };
|
||||||
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||||
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageHints);
|
this.imageFormatter = this.openmct.telemetry.getValueFormatter(this.imageHints);
|
||||||
this.imageDownloadNameHints = { ...this.metadata.valuesForHints(['imageDownloadName'])[0]};
|
|
||||||
|
|
||||||
// related telemetry keys
|
// related telemetry keys
|
||||||
this.spacecraftPositionKeys = ['positionX', 'positionY', 'positionZ'];
|
this.spacecraftPositionKeys = ['positionX', 'positionY', 'positionZ'];
|
||||||
@ -390,9 +381,7 @@ export default {
|
|||||||
delete this.unsubscribe;
|
delete this.unsubscribe;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.imageContainerResizeObserver) {
|
this.imageContainerResizeObserver.disconnect();
|
||||||
this.imageContainerResizeObserver.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.relatedTelemetry.hasRelatedTelemetry) {
|
if (this.relatedTelemetry.hasRelatedTelemetry) {
|
||||||
this.relatedTelemetry.destroy();
|
this.relatedTelemetry.destroy();
|
||||||
@ -543,15 +532,6 @@ export default {
|
|||||||
// Replace ISO "T" with a space to allow wrapping
|
// Replace ISO "T" with a space to allow wrapping
|
||||||
return dateTimeStr.replace("T", " ");
|
return dateTimeStr.replace("T", " ");
|
||||||
},
|
},
|
||||||
getImageDownloadName(datum) {
|
|
||||||
let imageDownloadName = '';
|
|
||||||
if (datum) {
|
|
||||||
const key = this.imageDownloadNameHints.key;
|
|
||||||
imageDownloadName = datum[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
return imageDownloadName;
|
|
||||||
},
|
|
||||||
parseTime(datum) {
|
parseTime(datum) {
|
||||||
if (!datum) {
|
if (!datum) {
|
||||||
return;
|
return;
|
||||||
@ -675,7 +655,6 @@ export default {
|
|||||||
image.formattedTime = this.formatTime(datum);
|
image.formattedTime = this.formatTime(datum);
|
||||||
image.url = this.formatImageUrl(datum);
|
image.url = this.formatImageUrl(datum);
|
||||||
image.time = datum[this.timeKey];
|
image.time = datum[this.timeKey];
|
||||||
image.imageDownloadName = this.getImageDownloadName(datum);
|
|
||||||
|
|
||||||
this.imageHistory.push(image);
|
this.imageHistory.push(image);
|
||||||
|
|
||||||
@ -704,7 +683,7 @@ export default {
|
|||||||
window.clearInterval(this.durationTracker);
|
window.clearInterval(this.durationTracker);
|
||||||
},
|
},
|
||||||
updateDuration() {
|
updateDuration() {
|
||||||
let currentTime = this.openmct.time.clock() && this.openmct.time.clock().currentValue();
|
let currentTime = this.openmct.time.clock().currentValue();
|
||||||
this.numericDuration = currentTime - this.parsedSelectedTime;
|
this.numericDuration = currentTime - this.parsedSelectedTime;
|
||||||
},
|
},
|
||||||
resetAgeCSS() {
|
resetAgeCSS() {
|
||||||
@ -798,9 +777,6 @@ export default {
|
|||||||
this.focusedImageNaturalAspectRatio = undefined;
|
this.focusedImageNaturalAspectRatio = undefined;
|
||||||
|
|
||||||
const img = this.$refs.focusedImage;
|
const img = this.$refs.focusedImage;
|
||||||
if (!img) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO - should probably cache this
|
// TODO - should probably cache this
|
||||||
img.addEventListener('load', () => {
|
img.addEventListener('load', () => {
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
import ImageryPlugin from './plugin.js';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import {
|
import {
|
||||||
createOpenMct,
|
createOpenMct,
|
||||||
@ -89,11 +89,15 @@ describe("The Imagery View Layout", () => {
|
|||||||
const START = Date.now();
|
const START = Date.now();
|
||||||
const COUNT = 10;
|
const COUNT = 10;
|
||||||
|
|
||||||
let resolveFunction;
|
|
||||||
|
|
||||||
let openmct;
|
let openmct;
|
||||||
|
let imageryPlugin;
|
||||||
let parent;
|
let parent;
|
||||||
let child;
|
let child;
|
||||||
|
let timeFormat = 'utc';
|
||||||
|
let bounds = {
|
||||||
|
start: START - TEN_MINUTES,
|
||||||
|
end: START
|
||||||
|
};
|
||||||
let imageTelemetry = generateTelemetry(START - TEN_MINUTES, COUNT);
|
let imageTelemetry = generateTelemetry(START - TEN_MINUTES, COUNT);
|
||||||
let imageryObject = {
|
let imageryObject = {
|
||||||
identifier: {
|
identifier: {
|
||||||
@ -201,10 +205,6 @@ describe("The Imagery View Layout", () => {
|
|||||||
|
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
|
|
||||||
openmct.install(openmct.plugins.MyItems());
|
|
||||||
openmct.install(openmct.plugins.LocalTimeSystem());
|
|
||||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
|
||||||
|
|
||||||
parent = document.createElement('div');
|
parent = document.createElement('div');
|
||||||
child = document.createElement('div');
|
child = document.createElement('div');
|
||||||
parent.appendChild(child);
|
parent.appendChild(child);
|
||||||
@ -215,18 +215,22 @@ describe("The Imagery View Layout", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
||||||
|
|
||||||
|
imageryPlugin = new ImageryPlugin();
|
||||||
|
openmct.install(imageryPlugin);
|
||||||
|
|
||||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
||||||
|
|
||||||
|
openmct.time.timeSystem(timeFormat, {
|
||||||
|
start: 0,
|
||||||
|
end: 4
|
||||||
|
});
|
||||||
|
|
||||||
openmct.on('start', done);
|
openmct.on('start', done);
|
||||||
openmct.start(appHolder);
|
openmct.startHeadless(appHolder);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
openmct.time.timeSystem('utc', {
|
|
||||||
start: 0,
|
|
||||||
end: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
return resetApplicationState(openmct);
|
return resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -244,7 +248,7 @@ describe("The Imagery View Layout", () => {
|
|||||||
let imageryViewProvider;
|
let imageryViewProvider;
|
||||||
let imageryView;
|
let imageryView;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async (done) => {
|
||||||
let telemetryRequestResolve;
|
let telemetryRequestResolve;
|
||||||
let telemetryRequestPromise = new Promise((resolve) => {
|
let telemetryRequestPromise = new Promise((resolve) => {
|
||||||
telemetryRequestResolve = resolve;
|
telemetryRequestResolve = resolve;
|
||||||
@ -256,18 +260,23 @@ describe("The Imagery View Layout", () => {
|
|||||||
return telemetryRequestPromise;
|
return telemetryRequestPromise;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
openmct.time.clock('local', {
|
||||||
|
start: bounds.start,
|
||||||
|
end: bounds.end + 100
|
||||||
|
});
|
||||||
|
|
||||||
applicableViews = openmct.objectViews.get(imageryObject, []);
|
applicableViews = openmct.objectViews.get(imageryObject, []);
|
||||||
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
|
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
|
||||||
imageryView = imageryViewProvider.view(imageryObject);
|
imageryView = imageryViewProvider.view(imageryObject);
|
||||||
imageryView.show(child);
|
imageryView.show(child);
|
||||||
|
|
||||||
await telemetryRequestPromise;
|
await telemetryRequestPromise;
|
||||||
|
await Vue.nextTick();
|
||||||
|
|
||||||
|
return done();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
openmct.time.stopClock();
|
|
||||||
openmct.router.removeListener('change:hash', resolveFunction);
|
|
||||||
|
|
||||||
imageryView.destroy();
|
imageryView.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -277,44 +286,43 @@ describe("The Imagery View Layout", () => {
|
|||||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
|
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show the clicked thumbnail as the main image", (done) => {
|
it("should show the clicked thumbnail as the main image", async () => {
|
||||||
const target = imageTelemetry[5].url;
|
const target = imageTelemetry[5].url;
|
||||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||||
Vue.nextTick(() => {
|
await Vue.nextTick();
|
||||||
const imageInfo = getImageInfo(parent);
|
const imageInfo = getImageInfo(parent);
|
||||||
|
|
||||||
expect(imageInfo.url.indexOf(imageTelemetry[5].timeId)).not.toEqual(-1);
|
expect(imageInfo.url.indexOf(imageTelemetry[5].timeId)).not.toEqual(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show that an image is new", async (done) => {
|
||||||
|
await Vue.nextTick();
|
||||||
|
|
||||||
|
// used in code, need to wait to the 500ms here too
|
||||||
|
setTimeout(() => {
|
||||||
|
const imageIsNew = isNew(parent);
|
||||||
|
|
||||||
|
expect(imageIsNew).toBeTrue();
|
||||||
done();
|
done();
|
||||||
});
|
}, REFRESH_CSS_MS);
|
||||||
});
|
});
|
||||||
|
|
||||||
xit("should show that an image is new", (done) => {
|
it("should show that an image is not new", async (done) => {
|
||||||
Vue.nextTick(() => {
|
|
||||||
// used in code, need to wait to the 500ms here too
|
|
||||||
setTimeout(() => {
|
|
||||||
const imageIsNew = isNew(parent);
|
|
||||||
expect(imageIsNew).toBeTrue();
|
|
||||||
done();
|
|
||||||
}, REFRESH_CSS_MS);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
xit("should show that an image is not new", (done) => {
|
|
||||||
const target = imageTelemetry[2].url;
|
const target = imageTelemetry[2].url;
|
||||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
await Vue.nextTick();
|
||||||
// used in code, need to wait to the 500ms here too
|
|
||||||
setTimeout(() => {
|
|
||||||
const imageIsNew = isNew(parent);
|
|
||||||
|
|
||||||
expect(imageIsNew).toBeFalse();
|
// used in code, need to wait to the 500ms here too
|
||||||
done();
|
setTimeout(() => {
|
||||||
}, REFRESH_CSS_MS);
|
const imageIsNew = isNew(parent);
|
||||||
});
|
|
||||||
|
expect(imageIsNew).toBeFalse();
|
||||||
|
done();
|
||||||
|
}, REFRESH_CSS_MS);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should navigate via arrow keys", (done) => {
|
it("should navigate via arrow keys", async () => {
|
||||||
let keyOpts = {
|
let keyOpts = {
|
||||||
element: parent.querySelector('.c-imagery'),
|
element: parent.querySelector('.c-imagery'),
|
||||||
key: 'ArrowLeft',
|
key: 'ArrowLeft',
|
||||||
@ -324,15 +332,14 @@ describe("The Imagery View Layout", () => {
|
|||||||
|
|
||||||
simulateKeyEvent(keyOpts);
|
simulateKeyEvent(keyOpts);
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
await Vue.nextTick();
|
||||||
const imageInfo = getImageInfo(parent);
|
|
||||||
|
|
||||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 2].timeId)).not.toEqual(-1);
|
const imageInfo = getImageInfo(parent);
|
||||||
done();
|
|
||||||
});
|
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 2].timeId)).not.toEqual(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should navigate via numerous arrow keys", (done) => {
|
it("should navigate via numerous arrow keys", async () => {
|
||||||
let element = parent.querySelector('.c-imagery');
|
let element = parent.querySelector('.c-imagery');
|
||||||
let type = 'keyup';
|
let type = 'keyup';
|
||||||
let leftKeyOpts = {
|
let leftKeyOpts = {
|
||||||
@ -355,12 +362,12 @@ describe("The Imagery View Layout", () => {
|
|||||||
// right once
|
// right once
|
||||||
simulateKeyEvent(rightKeyOpts);
|
simulateKeyEvent(rightKeyOpts);
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
await Vue.nextTick();
|
||||||
const imageInfo = getImageInfo(parent);
|
|
||||||
|
|
||||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 3].timeId)).not.toEqual(-1);
|
const imageInfo = getImageInfo(parent);
|
||||||
done();
|
|
||||||
});
|
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 3].timeId)).not.toEqual(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,10 @@ import myItemsInterceptor from "./myItemsInterceptor";
|
|||||||
|
|
||||||
export default function plugin() {
|
export default function plugin() {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
|
openmct.objects.addRoot({
|
||||||
|
namespace: '',
|
||||||
|
key: 'mine'
|
||||||
|
});
|
||||||
myItemsInterceptor(openmct);
|
myItemsInterceptor(openmct);
|
||||||
missingObjectInterceptor(openmct);
|
missingObjectInterceptor(openmct);
|
||||||
};
|
};
|
||||||
|
41
src/plugins/localStorage/LocalStorageObjectProvider.js
Normal file
41
src/plugins/localStorage/LocalStorageObjectProvider.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
export default class LocalStorageObjectProvider {
|
||||||
|
constructor({spaceKey = 'mct'}) {
|
||||||
|
this.localStorage = window.localStorage;
|
||||||
|
this.space = this.initializeSpace(spaceKey);
|
||||||
|
}
|
||||||
|
get(identifier) {
|
||||||
|
if (this.getSpaceAsObject()[identifier.key] !== undefined) {
|
||||||
|
const persistedModel = this.getSpaceAsObject()[identifier.key];
|
||||||
|
const domainObject = {
|
||||||
|
identifier,
|
||||||
|
...persistedModel
|
||||||
|
};
|
||||||
|
|
||||||
|
return Promise.resolve(domainObject);
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getSpaceAsObject() {
|
||||||
|
return JSON.parse(this.space);
|
||||||
|
}
|
||||||
|
create(model) {
|
||||||
|
return this.setModel(model);
|
||||||
|
}
|
||||||
|
update(model) {
|
||||||
|
return this.setModel(model);
|
||||||
|
}
|
||||||
|
setModel(model) {
|
||||||
|
this.space[model.identifier.key] = JSON.stringify(model);
|
||||||
|
this.persist();
|
||||||
|
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
initializeSpace(spaceKey) {
|
||||||
|
if (this.localStorage[spaceKey] === undefined) {
|
||||||
|
this.localStorage[spaceKey] = JSON.stringify({});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.localStorage[spaceKey];
|
||||||
|
}
|
||||||
|
}
|
29
src/plugins/localStorage/plugin.js
Normal file
29
src/plugins/localStorage/plugin.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import LocalStorageObjectProvider from './LocalStorageObjectProvider';
|
||||||
|
|
||||||
|
export default function (namespace = '', storageSpace = 'mct') {
|
||||||
|
return function (openmct) {
|
||||||
|
openmct.objects.addProvider(namespace, new LocalStorageObjectProvider(storageSpace));
|
||||||
|
};
|
||||||
|
}
|
@ -55,7 +55,7 @@ describe("The local time", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
localTimeSystem = openmct.time.timeSystem(LOCAL_SYSTEM_KEY, {
|
localTimeSystem = openmct.time.timeSystem(LOCAL_SYSTEM_KEY, {
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 1
|
end: 4
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ describe("The Move Action plugin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
return resetApplicationState(openmct);
|
resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be defined", () => {
|
it("should be defined", () => {
|
||||||
|
@ -135,7 +135,6 @@ import SearchResults from './SearchResults.vue';
|
|||||||
import Sidebar from './Sidebar.vue';
|
import Sidebar from './Sidebar.vue';
|
||||||
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
|
import { clearDefaultNotebook, getDefaultNotebook, setDefaultNotebook, setDefaultNotebookSection, setDefaultNotebookPage } from '../utils/notebook-storage';
|
||||||
import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
|
import { addNotebookEntry, createNewEmbed, getEntryPosById, getNotebookEntries, mutateObject } from '../utils/notebook-entries';
|
||||||
import { NOTEBOOK_VIEW_TYPE } from '../notebook-constants';
|
|
||||||
import objectUtils from 'objectUtils';
|
import objectUtils from 'objectUtils';
|
||||||
|
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
@ -190,14 +189,14 @@ export default {
|
|||||||
selectedPage() {
|
selectedPage() {
|
||||||
const pages = this.getPages();
|
const pages = this.getPages();
|
||||||
if (!pages) {
|
if (!pages) {
|
||||||
return {};
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return pages.find(page => page.isSelected);
|
return pages.find(page => page.isSelected);
|
||||||
},
|
},
|
||||||
selectedSection() {
|
selectedSection() {
|
||||||
if (!this.sections.length) {
|
if (!this.sections.length) {
|
||||||
return {};
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.sections.find(section => section.isSelected);
|
return this.sections.find(section => section.isSelected);
|
||||||
@ -217,7 +216,6 @@ export default {
|
|||||||
|
|
||||||
window.addEventListener('orientationchange', this.formatSidebar);
|
window.addEventListener('orientationchange', this.formatSidebar);
|
||||||
window.addEventListener("hashchange", this.navigateToSectionPage, false);
|
window.addEventListener("hashchange", this.navigateToSectionPage, false);
|
||||||
this.openmct.router.on('change:params', this.changeSectionPage);
|
|
||||||
|
|
||||||
this.navigateToSectionPage();
|
this.navigateToSectionPage();
|
||||||
},
|
},
|
||||||
@ -228,7 +226,6 @@ export default {
|
|||||||
|
|
||||||
window.removeEventListener('orientationchange', this.formatSidebar);
|
window.removeEventListener('orientationchange', this.formatSidebar);
|
||||||
window.removeEventListener("hashchange", this.navigateToSectionPage);
|
window.removeEventListener("hashchange", this.navigateToSectionPage);
|
||||||
this.openmct.router.off('change:params', this.changeSectionPage);
|
|
||||||
},
|
},
|
||||||
updated: function () {
|
updated: function () {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@ -236,28 +233,6 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
changeSectionPage(newParams, oldParams, changedParams) {
|
|
||||||
if (newParams.view !== NOTEBOOK_VIEW_TYPE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pageId = newParams.pageId;
|
|
||||||
let sectionId = newParams.sectionId;
|
|
||||||
|
|
||||||
if (!pageId && !sectionId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sections.forEach(section => {
|
|
||||||
section.isSelected = Boolean(section.id === sectionId);
|
|
||||||
|
|
||||||
if (section.isSelected) {
|
|
||||||
section.pages.forEach(page => {
|
|
||||||
page.isSelected = Boolean(page.id === pageId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
changeSelectedSection({ sectionId, pageId }) {
|
changeSelectedSection({ sectionId, pageId }) {
|
||||||
const sections = this.sections.map(s => {
|
const sections = this.sections.map(s => {
|
||||||
s.isSelected = false;
|
s.isSelected = false;
|
||||||
@ -455,7 +430,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check for no entries first
|
// check for no entries first
|
||||||
if (entries[section.id] && entries[section.id][page.id]) {
|
if (entries[section.id]) {
|
||||||
const pageEntries = entries[section.id][page.id];
|
const pageEntries = entries[section.id][page.id];
|
||||||
|
|
||||||
pageEntries.forEach(entry => {
|
pageEntries.forEach(entry => {
|
||||||
@ -543,11 +518,9 @@ export default {
|
|||||||
return this.sections.find(section => section.isSelected);
|
return this.sections.find(section => section.isSelected);
|
||||||
},
|
},
|
||||||
navigateToSectionPage() {
|
navigateToSectionPage() {
|
||||||
let { pageId, sectionId } = this.openmct.router.getParams();
|
const { pageId, sectionId } = this.openmct.router.getParams();
|
||||||
|
|
||||||
if (!pageId || !sectionId) {
|
if (!pageId || !sectionId) {
|
||||||
sectionId = this.selectedSection.id;
|
return;
|
||||||
pageId = this.selectedPage.id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sections = this.sections.map(s => {
|
const sections = this.sections.map(s => {
|
||||||
|
@ -145,7 +145,7 @@ export default {
|
|||||||
|
|
||||||
const relativeHash = hash.slice(hash.indexOf('#'));
|
const relativeHash = hash.slice(hash.indexOf('#'));
|
||||||
const url = new URL(relativeHash, `${location.protocol}//${location.host}${location.pathname}`);
|
const url = new URL(relativeHash, `${location.protocol}//${location.host}${location.pathname}`);
|
||||||
this.openmct.router.navigate(url.hash);
|
window.location.hash = url.hash;
|
||||||
},
|
},
|
||||||
formatTime(unixTime, timeFormat) {
|
formatTime(unixTime, timeFormat) {
|
||||||
return Moment.utc(unixTime).format(timeFormat);
|
return Moment.utc(unixTime).format(timeFormat);
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="c-notebook__search-results">
|
<div class="c-notebook__search-results">
|
||||||
<div class="c-notebook__search-results__header">Search Results ({{ results.length }})</div>
|
<div class="c-notebook__search-results__header">Search Results</div>
|
||||||
<div class="c-notebook__entries">
|
<div class="c-notebook__entries">
|
||||||
<NotebookEntry v-for="(result, index) in results"
|
<NotebookEntry v-for="(result, index) in results"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
@ -111,6 +111,10 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
pages() {
|
pages() {
|
||||||
const selectedSection = this.sections.find(section => section.isSelected);
|
const selectedSection = this.sections.find(section => section.isSelected);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
export const EVENT_SNAPSHOTS_UPDATED = 'SNAPSHOTS_UPDATED';
|
export const EVENT_SNAPSHOTS_UPDATED = 'SNAPSHOTS_UPDATED';
|
||||||
export const NOTEBOOK_DEFAULT = 'DEFAULT';
|
export const NOTEBOOK_DEFAULT = 'DEFAULT';
|
||||||
export const NOTEBOOK_SNAPSHOT = 'SNAPSHOT';
|
export const NOTEBOOK_SNAPSHOT = 'SNAPSHOT';
|
||||||
export const NOTEBOOK_VIEW_TYPE = 'notebook-vue';
|
|
||||||
|
@ -65,8 +65,7 @@ describe("Notebook plugin:", () => {
|
|||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
appHolder.remove();
|
appHolder.remove();
|
||||||
|
resetApplicationState(openmct);
|
||||||
return resetApplicationState(openmct);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("has type as Notebook", () => {
|
it("has type as Notebook", () => {
|
||||||
|
@ -140,8 +140,7 @@ describe('Notebook Entries:', () => {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
notebookDomainObject.configuration.entries[selectedSection.id][selectedPage.id] = [];
|
notebookDomainObject.configuration.entries[selectedSection.id][selectedPage.id] = [];
|
||||||
|
resetApplicationState(openmct);
|
||||||
return resetApplicationState(openmct);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getNotebookEntries has no entries', () => {
|
it('getNotebookEntries has no entries', () => {
|
||||||
|
@ -83,7 +83,7 @@ describe('Notebook Storage:', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
return resetApplicationState(openmct);
|
resetApplicationState(openmct);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has empty local Storage', () => {
|
it('has empty local Storage', () => {
|
||||||
|
@ -26,7 +26,6 @@ import CouchObjectQueue from "./CouchObjectQueue";
|
|||||||
const REV = "_rev";
|
const REV = "_rev";
|
||||||
const ID = "_id";
|
const ID = "_id";
|
||||||
const HEARTBEAT = 50000;
|
const HEARTBEAT = 50000;
|
||||||
const ALL_DOCS = "_all_docs?include_docs=true";
|
|
||||||
|
|
||||||
export default class CouchObjectProvider {
|
export default class CouchObjectProvider {
|
||||||
// options {
|
// options {
|
||||||
@ -42,8 +41,6 @@ export default class CouchObjectProvider {
|
|||||||
this.objectQueue = {};
|
this.objectQueue = {};
|
||||||
this.observeEnabled = options.disableObserve !== true;
|
this.observeEnabled = options.disableObserve !== true;
|
||||||
this.observers = {};
|
this.observers = {};
|
||||||
this.batchIds = [];
|
|
||||||
|
|
||||||
if (this.observeEnabled) {
|
if (this.observeEnabled) {
|
||||||
this.observeObjectChanges(options.filter);
|
this.observeObjectChanges(options.filter);
|
||||||
}
|
}
|
||||||
@ -70,9 +67,6 @@ export default class CouchObjectProvider {
|
|||||||
// stringify body if needed
|
// stringify body if needed
|
||||||
if (fetchOptions.body) {
|
if (fetchOptions.body) {
|
||||||
fetchOptions.body = JSON.stringify(fetchOptions.body);
|
fetchOptions.body = JSON.stringify(fetchOptions.body);
|
||||||
fetchOptions.headers = {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetch(this.url + '/' + subPath, fetchOptions)
|
return fetch(this.url + '/' + subPath, fetchOptions)
|
||||||
@ -84,18 +78,14 @@ export default class CouchObjectProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Check the response to a create/update/delete request;
|
||||||
* Check the response to a create/update/delete request;
|
// track the rev if it's valid, otherwise return false to
|
||||||
* track the rev if it's valid, otherwise return false to
|
// indicate that the request failed.
|
||||||
* indicate that the request failed.
|
// persist any queued objects
|
||||||
* persist any queued objects
|
checkResponse(response, intermediateResponse) {
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
checkResponse(response, intermediateResponse, key) {
|
|
||||||
let requestSuccess = false;
|
let requestSuccess = false;
|
||||||
const id = response ? response.id : undefined;
|
const id = response ? response.id : undefined;
|
||||||
let rev;
|
let rev;
|
||||||
|
|
||||||
if (response && response.ok) {
|
if (response && response.ok) {
|
||||||
rev = response.rev;
|
rev = response.rev;
|
||||||
requestSuccess = true;
|
requestSuccess = true;
|
||||||
@ -113,14 +103,9 @@ export default class CouchObjectProvider {
|
|||||||
if (this.objectQueue[id].hasNext()) {
|
if (this.objectQueue[id].hasNext()) {
|
||||||
this.updateQueued(id);
|
this.updateQueued(id);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.objectQueue[key].pending = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
getModel(response) {
|
getModel(response) {
|
||||||
if (response && response.model) {
|
if (response && response.model) {
|
||||||
let key = response[ID];
|
let key = response[ID];
|
||||||
@ -134,7 +119,8 @@ export default class CouchObjectProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Sometimes CouchDB returns the old rev which fetching the object if there is a document update in progress
|
//Sometimes CouchDB returns the old rev which fetching the object if there is a document update in progress
|
||||||
if (!this.objectQueue[key].pending) {
|
//Only update the rev if it's the first time we're getting the object from CouchDB. Subsequent revs should only be updated by updates.
|
||||||
|
if (!this.objectQueue[key].pending && !this.objectQueue[key].rev) {
|
||||||
this.objectQueue[key].updateRevision(response[REV]);
|
this.objectQueue[key].updateRevision(response[REV]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,118 +131,10 @@ export default class CouchObjectProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get(identifier, abortSignal) {
|
get(identifier, abortSignal) {
|
||||||
this.batchIds.push(identifier.key);
|
return this.request(identifier.key, "GET", undefined, abortSignal).then(this.getModel.bind(this));
|
||||||
|
|
||||||
if (this.bulkPromise === undefined) {
|
|
||||||
this.bulkPromise = this.deferBatchedGet(abortSignal);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.bulkPromise
|
|
||||||
.then((domainObjectMap) => {
|
|
||||||
return domainObjectMap[identifier.key];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async getObjectsByFilter(filter) {
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
deferBatchedGet(abortSignal) {
|
|
||||||
// We until the next event loop cycle to "collect" all of the get
|
|
||||||
// requests triggered in this iteration of the event loop
|
|
||||||
|
|
||||||
return this.waitOneEventCycle().then(() => {
|
|
||||||
let batchIds = this.batchIds;
|
|
||||||
|
|
||||||
this.clearBatch();
|
|
||||||
|
|
||||||
if (batchIds.length === 1) {
|
|
||||||
let objectKey = batchIds[0];
|
|
||||||
|
|
||||||
//If there's only one request, just do a regular get
|
|
||||||
return this.request(objectKey, "GET", undefined, abortSignal)
|
|
||||||
.then(this.returnAsMap(objectKey));
|
|
||||||
} else {
|
|
||||||
return this.bulkGet(batchIds, abortSignal);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
returnAsMap(objectKey) {
|
|
||||||
return (result) => {
|
|
||||||
let objectMap = {};
|
|
||||||
objectMap[objectKey] = this.getModel(result);
|
|
||||||
|
|
||||||
return objectMap;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
clearBatch() {
|
|
||||||
this.batchIds = [];
|
|
||||||
delete this.bulkPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
waitOneEventCycle() {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(resolve);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
bulkGet(ids, signal) {
|
|
||||||
ids = this.removeDuplicates(ids);
|
|
||||||
|
|
||||||
const query = {
|
|
||||||
'keys': ids
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.request(ALL_DOCS, 'POST', query, signal).then((response) => {
|
|
||||||
if (response && response.rows !== undefined) {
|
|
||||||
return response.rows.reduce((map, row) => {
|
|
||||||
if (row.doc !== undefined) {
|
|
||||||
map[row.key] = this.getModel(row.doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}, {});
|
|
||||||
} else {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
removeDuplicates(array) {
|
|
||||||
return Array.from(new Set(array));
|
|
||||||
}
|
|
||||||
|
|
||||||
search(query, abortSignal) {
|
|
||||||
const filter = {
|
|
||||||
"selector": {
|
|
||||||
"model": {
|
|
||||||
"name": {
|
|
||||||
"$regex": `(?i)${query}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.getObjectsByFilter(filter, abortSignal);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getObjectsByFilter(filter, abortSignal) {
|
|
||||||
let objects = [];
|
let objects = [];
|
||||||
|
|
||||||
let url = `${this.url}/_find`;
|
let url = `${this.url}/_find`;
|
||||||
@ -271,7 +149,6 @@ export default class CouchObjectProvider {
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
signal: abortSignal,
|
|
||||||
body
|
body
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -326,9 +203,6 @@ export default class CouchObjectProvider {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
abortGetChanges() {
|
abortGetChanges() {
|
||||||
if (this.controller) {
|
if (this.controller) {
|
||||||
this.controller.abort();
|
this.controller.abort();
|
||||||
@ -338,9 +212,6 @@ export default class CouchObjectProvider {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async observeObjectChanges(filter) {
|
async observeObjectChanges(filter) {
|
||||||
let intermediateResponse = this.getIntermediateResponse();
|
let intermediateResponse = this.getIntermediateResponse();
|
||||||
|
|
||||||
@ -421,9 +292,6 @@ export default class CouchObjectProvider {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
getIntermediateResponse() {
|
getIntermediateResponse() {
|
||||||
let intermediateResponse = {};
|
let intermediateResponse = {};
|
||||||
intermediateResponse.promise = new Promise(function (resolve, reject) {
|
intermediateResponse.promise = new Promise(function (resolve, reject) {
|
||||||
@ -434,9 +302,6 @@ export default class CouchObjectProvider {
|
|||||||
return intermediateResponse;
|
return intermediateResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
enqueueObject(key, model, intermediateResponse) {
|
enqueueObject(key, model, intermediateResponse) {
|
||||||
if (this.objectQueue[key]) {
|
if (this.objectQueue[key]) {
|
||||||
this.objectQueue[key].enqueue({
|
this.objectQueue[key].enqueue({
|
||||||
@ -459,22 +324,19 @@ export default class CouchObjectProvider {
|
|||||||
const queued = this.objectQueue[key].dequeue();
|
const queued = this.objectQueue[key].dequeue();
|
||||||
let document = new CouchDocument(key, queued.model);
|
let document = new CouchDocument(key, queued.model);
|
||||||
this.request(key, "PUT", document).then((response) => {
|
this.request(key, "PUT", document).then((response) => {
|
||||||
this.checkResponse(response, queued.intermediateResponse, key);
|
this.checkResponse(response, queued.intermediateResponse);
|
||||||
});
|
});
|
||||||
|
|
||||||
return intermediateResponse.promise;
|
return intermediateResponse.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
updateQueued(key) {
|
updateQueued(key) {
|
||||||
if (!this.objectQueue[key].pending) {
|
if (!this.objectQueue[key].pending) {
|
||||||
this.objectQueue[key].pending = true;
|
this.objectQueue[key].pending = true;
|
||||||
const queued = this.objectQueue[key].dequeue();
|
const queued = this.objectQueue[key].dequeue();
|
||||||
let document = new CouchDocument(key, queued.model, this.objectQueue[key].rev);
|
let document = new CouchDocument(key, queued.model, this.objectQueue[key].rev);
|
||||||
this.request(key, "PUT", document).then((response) => {
|
this.request(key, "PUT", document).then((response) => {
|
||||||
this.checkResponse(response, queued.intermediateResponse, key);
|
this.checkResponse(response, queued.intermediateResponse);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import {
|
|||||||
createOpenMct,
|
createOpenMct,
|
||||||
resetApplicationState, spyOnBuiltins
|
resetApplicationState, spyOnBuiltins
|
||||||
} from 'utils/testing';
|
} from 'utils/testing';
|
||||||
|
import CouchObjectProvider from './CouchObjectProvider';
|
||||||
|
|
||||||
describe('the plugin', () => {
|
describe('the plugin', () => {
|
||||||
let openmct;
|
let openmct;
|
||||||
@ -41,8 +42,7 @@ describe('the plugin', () => {
|
|||||||
namespace: '',
|
namespace: '',
|
||||||
key: 'some-value'
|
key: 'some-value'
|
||||||
},
|
},
|
||||||
type: 'mock-type',
|
type: 'mock-type'
|
||||||
modified: 0
|
|
||||||
};
|
};
|
||||||
options = {
|
options = {
|
||||||
url: testPath,
|
url: testPath,
|
||||||
@ -95,7 +95,6 @@ describe('the plugin', () => {
|
|||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
_id: 'some-value',
|
_id: 'some-value',
|
||||||
id: 'some-value',
|
|
||||||
_rev: 1,
|
_rev: 1,
|
||||||
model: {}
|
model: {}
|
||||||
};
|
};
|
||||||
@ -105,131 +104,44 @@ describe('the plugin', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('gets an object', () => {
|
it('gets an object', () => {
|
||||||
return openmct.objects.get(mockDomainObject.identifier).then((result) => {
|
openmct.objects.get(mockDomainObject.identifier).then((result) => {
|
||||||
expect(result.identifier.key).toEqual(mockDomainObject.identifier.key);
|
expect(result.identifier.key).toEqual(mockDomainObject.identifier.key);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates an object', () => {
|
it('creates an object', () => {
|
||||||
return openmct.objects.save(mockDomainObject).then((result) => {
|
openmct.objects.save(mockDomainObject).then((result) => {
|
||||||
expect(provider.create).toHaveBeenCalled();
|
expect(provider.create).toHaveBeenCalled();
|
||||||
expect(result).toBeTrue();
|
expect(result).toBeTrue();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates an object', (done) => {
|
it('updates an object', () => {
|
||||||
return openmct.objects.save(mockDomainObject).then((result) => {
|
openmct.objects.save(mockDomainObject).then((result) => {
|
||||||
expect(result).toBeTrue();
|
expect(result).toBeTrue();
|
||||||
expect(provider.create).toHaveBeenCalled();
|
expect(provider.create).toHaveBeenCalled();
|
||||||
|
openmct.objects.save(mockDomainObject).then((updatedResult) => {
|
||||||
//Set modified timestamp it detects a change and persists the updated model.
|
|
||||||
mockDomainObject.modified = Date.now();
|
|
||||||
|
|
||||||
return openmct.objects.save(mockDomainObject).then((updatedResult) => {
|
|
||||||
expect(updatedResult).toBeTrue();
|
expect(updatedResult).toBeTrue();
|
||||||
expect(provider.update).toHaveBeenCalled();
|
expect(provider.update).toHaveBeenCalled();
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
describe('batches requests', () => {
|
it('updates queued objects', () => {
|
||||||
let mockPromise;
|
let couchProvider = new CouchObjectProvider(openmct, options, '');
|
||||||
beforeEach(() => {
|
let intermediateResponse = couchProvider.getIntermediateResponse();
|
||||||
mockPromise = Promise.resolve({
|
spyOn(couchProvider, 'updateQueued');
|
||||||
json: () => {
|
couchProvider.enqueueObject(mockDomainObject.identifier.key, mockDomainObject, intermediateResponse);
|
||||||
return {
|
couchProvider.objectQueue[mockDomainObject.identifier.key].updateRevision(1);
|
||||||
total_rows: 0,
|
couchProvider.update(mockDomainObject);
|
||||||
rows: []
|
expect(couchProvider.objectQueue[mockDomainObject.identifier.key].hasNext()).toBe(2);
|
||||||
};
|
couchProvider.checkResponse({
|
||||||
}
|
ok: true,
|
||||||
});
|
rev: 2,
|
||||||
fetch.and.returnValue(mockPromise);
|
id: mockDomainObject.identifier.key
|
||||||
|
}, intermediateResponse);
|
||||||
|
|
||||||
|
expect(couchProvider.updateQueued).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
it('for multiple simultaneous gets', () => {
|
|
||||||
const objectIds = [
|
|
||||||
{
|
|
||||||
namespace: '',
|
|
||||||
key: 'object-1'
|
|
||||||
}, {
|
|
||||||
namespace: '',
|
|
||||||
key: 'object-2'
|
|
||||||
}, {
|
|
||||||
namespace: '',
|
|
||||||
key: 'object-3'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const getAllObjects = Promise.all(
|
|
||||||
objectIds.map((identifier) =>
|
|
||||||
openmct.objects.get(identifier)
|
|
||||||
));
|
|
||||||
|
|
||||||
return getAllObjects.then(() => {
|
|
||||||
const requestUrl = fetch.calls.mostRecent().args[0];
|
|
||||||
const requestMethod = fetch.calls.mostRecent().args[1].method;
|
|
||||||
|
|
||||||
expect(fetch).toHaveBeenCalledTimes(1);
|
|
||||||
expect(requestUrl.includes('_all_docs')).toBeTrue();
|
|
||||||
expect(requestMethod).toEqual('POST');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('but not for single gets', () => {
|
|
||||||
const objectId = {
|
|
||||||
namespace: '',
|
|
||||||
key: 'object-1'
|
|
||||||
};
|
|
||||||
|
|
||||||
const getObject = openmct.objects.get(objectId);
|
|
||||||
|
|
||||||
return getObject.then(() => {
|
|
||||||
const requestUrl = fetch.calls.mostRecent().args[0];
|
|
||||||
const requestMethod = fetch.calls.mostRecent().args[1].method;
|
|
||||||
|
|
||||||
expect(fetch).toHaveBeenCalledTimes(1);
|
|
||||||
expect(requestUrl.endsWith(`${objectId.key}`)).toBeTrue();
|
|
||||||
expect(requestMethod).toEqual('GET');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('implements server-side search', () => {
|
|
||||||
let mockPromise;
|
|
||||||
beforeEach(() => {
|
|
||||||
mockPromise = Promise.resolve({
|
|
||||||
body: {
|
|
||||||
getReader() {
|
|
||||||
return {
|
|
||||||
read() {
|
|
||||||
return Promise.resolve({
|
|
||||||
done: true,
|
|
||||||
value: undefined
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
fetch.and.returnValue(mockPromise);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("using Couch's 'find' endpoint", () => {
|
|
||||||
return Promise.all(openmct.objects.search('test')).then(() => {
|
|
||||||
const requestUrl = fetch.calls.mostRecent().args[0];
|
|
||||||
|
|
||||||
expect(fetch).toHaveBeenCalled();
|
|
||||||
expect(requestUrl.endsWith('_find')).toBeTrue();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("and supports search by object name", () => {
|
|
||||||
return Promise.all(openmct.objects.search('test')).then(() => {
|
|
||||||
const requestPayload = JSON.parse(fetch.calls.mostRecent().args[1].body);
|
|
||||||
|
|
||||||
expect(requestPayload).toBeDefined();
|
|
||||||
expect(requestPayload.selector.model.name.$regex).toEqual('(?i)test');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="plot-series-limit-label"
|
|
||||||
:style="styleObj"
|
|
||||||
:class="limit.cssClass"
|
|
||||||
>
|
|
||||||
<span class="plot-series-limit-value">{{ limit.value }}</span>
|
|
||||||
<span class="plot-series-color-swatch"
|
|
||||||
:style="{ 'background-color': limit.color }"
|
|
||||||
></span>
|
|
||||||
<span class="plot-series-name">{{ limit.name }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
limit: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
default() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
point: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
default() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
styleObj() {
|
|
||||||
const top = `${this.point.top - 10}px`;
|
|
||||||
const left = `${this.point.left + 5}px`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
'position': 'absolute',
|
|
||||||
'top': top,
|
|
||||||
'left': left,
|
|
||||||
'color': '#fff'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -1,44 +0,0 @@
|
|||||||
<template>
|
|
||||||
<hr :style="styleObj"
|
|
||||||
:class="cssWithoutUprLwr"
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
point: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
default() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cssClass: {
|
|
||||||
type: String,
|
|
||||||
default() {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
styleObj() {
|
|
||||||
const top = `${this.point.top}px`;
|
|
||||||
const left = `${this.point.left}px`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
'position': 'absolute',
|
|
||||||
'width': '100%',
|
|
||||||
'top': top,
|
|
||||||
'left': left
|
|
||||||
};
|
|
||||||
},
|
|
||||||
cssWithoutUprLwr() {
|
|
||||||
let cssClass = this.cssClass.replace(/is-limit--upr/gi, 'is-limit--line');
|
|
||||||
cssClass = cssClass.replace(/is-limit--lwr/gi, 'is-limit--line');
|
|
||||||
|
|
||||||
return cssClass;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -1,105 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
import eventHelpers from '../lib/eventHelpers';
|
|
||||||
|
|
||||||
export default class MCTChartAlarmLineSet {
|
|
||||||
constructor(series, chart, offset, bounds) {
|
|
||||||
this.series = series;
|
|
||||||
this.chart = chart;
|
|
||||||
this.offset = offset;
|
|
||||||
this.bounds = bounds;
|
|
||||||
this.limits = [];
|
|
||||||
|
|
||||||
eventHelpers.extend(this);
|
|
||||||
this.listenTo(series, 'limitBounds', this.updateBounds, this);
|
|
||||||
this.listenTo(series, 'change:xKey', this.getLimitPoints, this);
|
|
||||||
|
|
||||||
if (series.limits) {
|
|
||||||
this.getLimitPoints(series);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateBounds(bounds) {
|
|
||||||
this.bounds = bounds;
|
|
||||||
this.getLimitPoints(this.series);
|
|
||||||
}
|
|
||||||
|
|
||||||
color() {
|
|
||||||
return this.series.get('color');
|
|
||||||
}
|
|
||||||
|
|
||||||
name() {
|
|
||||||
return this.series.get('name');
|
|
||||||
}
|
|
||||||
|
|
||||||
makePoint(point, series) {
|
|
||||||
if (!this.offset.xVal) {
|
|
||||||
this.chart.setOffset(point, undefined, series);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: this.offset.xVal(point, series),
|
|
||||||
y: this.offset.yVal(point, series)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getLimitPoints(series) {
|
|
||||||
this.limits = [];
|
|
||||||
let xKey = series.get('xKey');
|
|
||||||
Object.keys(series.limits).forEach((key) => {
|
|
||||||
const limitForLevel = series.limits[key];
|
|
||||||
if (limitForLevel.high) {
|
|
||||||
const point = this.makePoint(Object.assign({ [xKey]: this.bounds.start }, limitForLevel.high), series);
|
|
||||||
this.limits.push({
|
|
||||||
seriesKey: series.keyString,
|
|
||||||
value: series.getYVal(limitForLevel.high),
|
|
||||||
color: this.color().asHexString(),
|
|
||||||
name: this.name(),
|
|
||||||
point,
|
|
||||||
cssClass: limitForLevel.high.cssClass
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (limitForLevel.low) {
|
|
||||||
const point = this.makePoint(Object.assign({ [xKey]: this.bounds.start }, limitForLevel.low), series);
|
|
||||||
this.limits.push({
|
|
||||||
seriesKey: series.keyString,
|
|
||||||
value: series.getYVal(limitForLevel.low),
|
|
||||||
color: this.color().asHexString(),
|
|
||||||
name: this.name(),
|
|
||||||
point,
|
|
||||||
cssClass: limitForLevel.low.cssClass
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.limits = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.stopListening();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
<!--
|
|
||||||
Open MCT, Copyright (c) 2014-2020, United States Government
|
|
||||||
as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
Administration. All rights reserved.
|
|
||||||
|
|
||||||
Open MCT is licensed under the Apache License, Version 2.0 (the
|
|
||||||
"License"); you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
License for the specific language governing permissions and limitations
|
|
||||||
under the License.
|
|
||||||
|
|
||||||
Open MCT includes source code licensed under additional open source
|
|
||||||
licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
this source code distribution or the Licensing information page available
|
|
||||||
at runtime from the About dialog for additional information.
|
|
||||||
-->
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div v-if="canEdit">
|
|
||||||
<plot-options-edit />
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<plot-options-browse />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import PlotOptionsBrowse from "./PlotOptionsBrowse.vue";
|
|
||||||
import PlotOptionsEdit from "./PlotOptionsEdit.vue";
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
PlotOptionsBrowse,
|
|
||||||
PlotOptionsEdit
|
|
||||||
},
|
|
||||||
inject: ['openmct', 'domainObject'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isEditing: this.openmct.editor.isEditing()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
canEdit() {
|
|
||||||
return this.isEditing && !this.domainObject.locked;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.openmct.editor.on('isEditing', this.setEditState);
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.openmct.editor.off('isEditing', this.setEditState);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
setEditState(isEditing) {
|
|
||||||
this.isEditing = isEditing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user