Compare commits
	
		
			6 Commits
		
	
	
		
			v1.8.1
			...
			progress-i
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					b071abbf6b | ||
| 
						 | 
					92f4ed6690 | ||
| 
						 | 
					85755f6124 | ||
| 
						 | 
					803531acb0 | ||
| 
						 | 
					cdbfb7b33f | ||
| 
						 | 
					21a3afd60c | 
@@ -7,42 +7,30 @@ orbs:
 | 
			
		||||
  node: circleci/node@4.5.1
 | 
			
		||||
  browser-tools: circleci/browser-tools@1.1.3
 | 
			
		||||
jobs:
 | 
			
		||||
  npm-audit:
 | 
			
		||||
    executor: linux
 | 
			
		||||
    steps:
 | 
			
		||||
      - checkout
 | 
			
		||||
      - node/install:
 | 
			
		||||
          install-npm: true
 | 
			
		||||
          node-version: lts/fermium
 | 
			
		||||
      - run: npm install
 | 
			
		||||
      - run: npm audit --audit-level=low
 | 
			
		||||
  test:
 | 
			
		||||
    parameters:
 | 
			
		||||
      node-version:
 | 
			
		||||
        type: string
 | 
			
		||||
      browser:
 | 
			
		||||
        type: string
 | 
			
		||||
      always-pass:
 | 
			
		||||
        type: boolean  
 | 
			
		||||
    executor: linux
 | 
			
		||||
    steps:
 | 
			
		||||
      - checkout
 | 
			
		||||
      - restore_cache:
 | 
			
		||||
          key: deps-{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}
 | 
			
		||||
      - node/install:
 | 
			
		||||
          install-npm: false
 | 
			
		||||
          node-version: << parameters.node-version >>
 | 
			
		||||
      - run: npm install
 | 
			
		||||
      - when:
 | 
			
		||||
      - node/install-packages:
 | 
			
		||||
          override-ci-command: npm install
 | 
			
		||||
      - when: # Just to save time until caching saves the browser bin
 | 
			
		||||
          condition:
 | 
			
		||||
            equal: [ "FirefoxESR", <<parameters.browser>> ]
 | 
			
		||||
          steps:
 | 
			
		||||
            - browser-tools/install-firefox:
 | 
			
		||||
                version: "91.2.0esr" #https://archive.mozilla.org/pub/firefox/releases/          
 | 
			
		||||
      - when:
 | 
			
		||||
          condition:
 | 
			
		||||
            equal: [ "FirefoxHeadless", <<parameters.browser>> ]
 | 
			
		||||
          steps:
 | 
			
		||||
            - browser-tools/install-firefox
 | 
			
		||||
      - when:
 | 
			
		||||
                version: "78.11.0esr" #https://archive.mozilla.org/pub/firefox/releases/          
 | 
			
		||||
      - when: # Just to save time until caching saves the browser bin
 | 
			
		||||
          condition:
 | 
			
		||||
            equal: [ "ChromeHeadless", <<parameters.browser>> ]
 | 
			
		||||
          steps:
 | 
			
		||||
@@ -54,54 +42,46 @@ jobs:
 | 
			
		||||
            - ~/.npm
 | 
			
		||||
            - ~/.cache
 | 
			
		||||
            - node_modules
 | 
			
		||||
      - when:
 | 
			
		||||
          condition:
 | 
			
		||||
            equal: [ "", <<parameters.browser>> ] #Only run linting when browsers are not running to save time
 | 
			
		||||
          steps:
 | 
			
		||||
            - run: npm run lint
 | 
			
		||||
      - when:
 | 
			
		||||
          condition: << parameters.browser >> #Truthy evaluation to only run when browser is specified
 | 
			
		||||
          steps:
 | 
			
		||||
            - run: npm run test:coverage -- --browsers=<<parameters.browser>>
 | 
			
		||||
            - store_test_results:
 | 
			
		||||
                path: dist/reports/tests/
 | 
			
		||||
            - store_artifacts:
 | 
			
		||||
                path: dist/reports/
 | 
			
		||||
      - run: npm run test:coverage -- --browsers=<<parameters.browser>> || <<parameters.always-pass>>
 | 
			
		||||
      - store_test_results:
 | 
			
		||||
          path: dist/reports/tests/
 | 
			
		||||
      - store_artifacts:
 | 
			
		||||
          path: dist/reports/
 | 
			
		||||
workflows:
 | 
			
		||||
  matrix-tests:
 | 
			
		||||
    jobs:
 | 
			
		||||
      - test:
 | 
			
		||||
          post-steps:
 | 
			
		||||
            - run:
 | 
			
		||||
                command:
 | 
			
		||||
                  curl -Os https://uploader.codecov.io/latest/linux/codecov;chmod +x codecov;./codecov              
 | 
			
		||||
          name: node10-chrome
 | 
			
		||||
          node-version: lts/dubnium
 | 
			
		||||
          browser: ChromeHeadless
 | 
			
		||||
          always-pass: false
 | 
			
		||||
      - test:
 | 
			
		||||
          name: node12-build-lint
 | 
			
		||||
          name: node12-firefoxESR-build-only
 | 
			
		||||
          node-version: lts/erbium
 | 
			
		||||
          browser: "" #Skip unit tests
 | 
			
		||||
      - test: 
 | 
			
		||||
          name: node14-build-lint
 | 
			
		||||
          browser: FirefoxESR
 | 
			
		||||
          always-pass: true
 | 
			
		||||
      - test:
 | 
			
		||||
          name: node14-chrome-build-only
 | 
			
		||||
          node-version: lts/fermium
 | 
			
		||||
          browser: "" #Skip unit tests
 | 
			
		||||
 | 
			
		||||
          browser: ChromeHeadless
 | 
			
		||||
          always-pass: true
 | 
			
		||||
  nightly:
 | 
			
		||||
    jobs:
 | 
			
		||||
      - test:
 | 
			
		||||
          name: node10-chrome-nightly
 | 
			
		||||
          node-version: lts/dubnium
 | 
			
		||||
          browser: ChromeHeadless
 | 
			
		||||
          always-pass: false
 | 
			
		||||
      - test:
 | 
			
		||||
          name: node12-firefoxESR-nightly
 | 
			
		||||
          node-version: lts/erbium
 | 
			
		||||
          browser: FirefoxESR
 | 
			
		||||
          always-pass: false
 | 
			
		||||
      - test:
 | 
			
		||||
          name: node14-firefox-nightly
 | 
			
		||||
          name: node14-chrome-nightly
 | 
			
		||||
          node-version: lts/fermium
 | 
			
		||||
          browser: FirefoxHeadless
 | 
			
		||||
      - npm-audit
 | 
			
		||||
          browser: ChromeHeadless
 | 
			
		||||
          always-pass: false
 | 
			
		||||
    triggers:
 | 
			
		||||
      - schedule:
 | 
			
		||||
          cron: "0 0 * * *"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							@@ -2,4 +2,4 @@ blank_issues_enabled: true
 | 
			
		||||
contact_links:
 | 
			
		||||
  - name: Discussions
 | 
			
		||||
    url: https://github.com/nasa/openmct/discussions
 | 
			
		||||
    about: Have a question about the project?
 | 
			
		||||
    about: Got a question?
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
<!--- This is for filing enhancements or features. If you have a general -->
 | 
			
		||||
<!--- question, please visit https://github.com/nasa/openmct/discussions -->
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
name: Feature Request
 | 
			
		||||
about: Suggest an idea for this project
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<!--
 | 
			
		||||
Thank you for suggesting an idea to make Open MCT better.
 | 
			
		||||
 | 
			
		||||
Please fill in as much of the template below as you're able.
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
**Is your feature request related to a problem? Please describe.**
 | 
			
		||||
<!-- Please describe the problem you are trying to solve. -->
 | 
			
		||||
 | 
			
		||||
**Describe the solution you'd like**
 | 
			
		||||
<!--- Please describe the desired behavior. -->
 | 
			
		||||
 | 
			
		||||
**Describe alternatives you've considered**
 | 
			
		||||
<!--- Please describe alternative solutions or features you have considered. -->
 | 
			
		||||
							
								
								
									
										11
									
								
								.github/ISSUE_TEMPLATE/maintenance-type.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/ISSUE_TEMPLATE/maintenance-type.md
									
									
									
									
										vendored
									
									
								
							@@ -1,11 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
name: Maintenance
 | 
			
		||||
about: Add, update or remove documentation, tests, or dependencies.
 | 
			
		||||
title: ''
 | 
			
		||||
labels: type:maintenance
 | 
			
		||||
assignees: ''
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
#### Summary
 | 
			
		||||
<!--- Generally describe the purpose of the change. -->
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@@ -2,7 +2,6 @@
 | 
			
		||||
 | 
			
		||||
* [ ] 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?
 | 
			
		||||
* [ ] Is this change backwards compatible? For example, developers won't need to change how they are calling the API or how they've extended core plugins such as Tables or Plots.
 | 
			
		||||
 | 
			
		||||
### Author Checklist
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,23 +0,0 @@
 | 
			
		||||
 | 
			
		||||
version: 2
 | 
			
		||||
updates:
 | 
			
		||||
  - package-ecosystem: "npm"
 | 
			
		||||
    directory: "/"
 | 
			
		||||
    schedule:
 | 
			
		||||
      interval: "daily"  
 | 
			
		||||
    open-pull-requests-limit: 2
 | 
			
		||||
    labels:
 | 
			
		||||
      - "type:maintenance"
 | 
			
		||||
      - "dependencies"
 | 
			
		||||
    allow:
 | 
			
		||||
      - dependency-name: "eslint*"
 | 
			
		||||
      - dependency-name: "karma*"
 | 
			
		||||
      - dependency-name: "jasmine*"
 | 
			
		||||
 | 
			
		||||
  - package-ecosystem: "github-actions"
 | 
			
		||||
    directory: "/"
 | 
			
		||||
    schedule:
 | 
			
		||||
      interval: "daily"    
 | 
			
		||||
    labels:
 | 
			
		||||
      - "type:maintenance"
 | 
			
		||||
      - "dependencies"
 | 
			
		||||
							
								
								
									
										8
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							@@ -4,14 +4,6 @@ name: "CodeQL"
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches: [ master ]
 | 
			
		||||
  pull_request:
 | 
			
		||||
    branches: [ master ]
 | 
			
		||||
    paths-ignore:
 | 
			
		||||
      - '**/*Spec.js'
 | 
			
		||||
      - '**/*.md'
 | 
			
		||||
      - '**/*.txt'
 | 
			
		||||
      - '**/*.yml'
 | 
			
		||||
      - '**/*.yaml'
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: '28 21 * * 3'
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -44,6 +44,3 @@ report.*.json
 | 
			
		||||
.lighthouseci
 | 
			
		||||
 | 
			
		||||
package-lock.json
 | 
			
		||||
 | 
			
		||||
#codecov artifacts
 | 
			
		||||
codecov
 | 
			
		||||
 
 | 
			
		||||
@@ -317,7 +317,6 @@ checklist).
 | 
			
		||||
### Reviewer Checklist
 | 
			
		||||
 | 
			
		||||
* [ ] Changes appear to address issue?
 | 
			
		||||
* [ ] Changes appear not to be breaking changes?
 | 
			
		||||
* [ ] Appropriate unit tests included?
 | 
			
		||||
* [ ] Code style and in-line documentation are appropriate?
 | 
			
		||||
* [ ] Commit messages meet standards?
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# Open MCT [](http://www.apache.org/licenses/LICENSE-2.0) [](https://lgtm.com/projects/g/nasa/openmct/context:javascript) [](https://codecov.io/gh/nasa/openmct)
 | 
			
		||||
# Open MCT [](http://www.apache.org/licenses/LICENSE-2.0) [](https://lgtm.com/projects/g/nasa/openmct/context:javascript)
 | 
			
		||||
 | 
			
		||||
Open MCT (Open Mission Control Technologies) is a next-generation mission control framework for visualization of data on desktop and mobile devices. It is developed at NASA's Ames Research Center, and is being used by NASA for data analysis of spacecraft missions, as well as planning and operation of experimental rover systems. As a generalizable and open source framework, Open MCT could be used as the basis for building applications for planning, operation, and analysis of any systems producing telemetry data.
 | 
			
		||||
 | 
			
		||||
@@ -85,8 +85,6 @@ naming convention is otherwise the same.)
 | 
			
		||||
When `npm test` is run, test results will be written as HTML to
 | 
			
		||||
`dist/reports/tests/`. Code coverage information is written to `dist/reports/coverage`.
 | 
			
		||||
 | 
			
		||||
Code Coverage Reports are available from [codecov.io](https://app.codecov.io/gh/nasa/openmct/)
 | 
			
		||||
 | 
			
		||||
# Glossary
 | 
			
		||||
 | 
			
		||||
Certain terms are used throughout Open MCT with consistent meanings
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								SECURITY.md
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								SECURITY.md
									
									
									
									
									
								
							@@ -1,31 +0,0 @@
 | 
			
		||||
# Security Policy
 | 
			
		||||
 | 
			
		||||
The Open MCT team secures our code base using a combination of code review, dependency review, and periodic security reviews. Static analysis performed during automated verification additionally safeguards against common coding errors which may result in vulnerabilities.
 | 
			
		||||
 | 
			
		||||
### Reporting a Vulnerability
 | 
			
		||||
 | 
			
		||||
For general defects, please for a [Bug Report](https://github.com/nasa/openmct/issues/new/choose)
 | 
			
		||||
 | 
			
		||||
To report a vulnerability for Open MCT please send a detailed report to [arc-dl-openmct](mailto:arc-dl-openmct@mail.nasa.gov). 
 | 
			
		||||
 | 
			
		||||
See our [top-level security policy](https://github.com/nasa/openmct/security/policy) for additional information.
 | 
			
		||||
 | 
			
		||||
### CodeQL and LGTM
 | 
			
		||||
 | 
			
		||||
The [CodeQL GitHub Actions workflow](https://github.com/nasa/openmct/blob/master/.github/workflows/codeql-analysis.yml) is available to the public. To review the results, fork the repository and run the CodeQL workflow. 
 | 
			
		||||
 | 
			
		||||
CodeQL is run for every pull-request in GitHub Actions.
 | 
			
		||||
 | 
			
		||||
The project is also monitored by [LGTM](https://lgtm.com/projects/g/nasa/openmct/) and is available to public.
 | 
			
		||||
 | 
			
		||||
### ESLint
 | 
			
		||||
 | 
			
		||||
Static analysis is run for every push on the master branch and every pull request on all branches in Github Actions. 
 | 
			
		||||
 | 
			
		||||
For more information about ESLint, visit https://eslint.org/.
 | 
			
		||||
 | 
			
		||||
### General Support
 | 
			
		||||
 | 
			
		||||
For additional support, please open a [Github Discussion](https://github.com/nasa/openmct/discussions). 
 | 
			
		||||
 | 
			
		||||
If you wish to report a cybersecurity incident or concern, please contact the NASA Security Operations Center either by phone at 1-877-627-2732 or via email address soc@nasa.gov.
 | 
			
		||||
							
								
								
									
										27
									
								
								codecov.yml
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								codecov.yml
									
									
									
									
									
								
							@@ -1,27 +0,0 @@
 | 
			
		||||
codecov:
 | 
			
		||||
  require_ci_to_pass: false #This setting will update the bot regardless of whether or not tests pass
 | 
			
		||||
 | 
			
		||||
coverage:
 | 
			
		||||
  status:
 | 
			
		||||
    project:
 | 
			
		||||
      default:
 | 
			
		||||
        informational: true
 | 
			
		||||
    patch:
 | 
			
		||||
      default:
 | 
			
		||||
        informational: true
 | 
			
		||||
  precision: 2
 | 
			
		||||
  round: down
 | 
			
		||||
  range: "66...100"
 | 
			
		||||
 | 
			
		||||
parsers:
 | 
			
		||||
  gcov:
 | 
			
		||||
    branch_detection:
 | 
			
		||||
      conditional: true
 | 
			
		||||
      loop: true
 | 
			
		||||
      method: false
 | 
			
		||||
      macro: false
 | 
			
		||||
 | 
			
		||||
comment:
 | 
			
		||||
  layout: "reach,diff,flags,files,footer"
 | 
			
		||||
  behavior: default
 | 
			
		||||
  require_changes: false
 | 
			
		||||
@@ -28,15 +28,6 @@ define([
 | 
			
		||||
                        domain: 2
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: "cos",
 | 
			
		||||
                    name: "Cosine",
 | 
			
		||||
                    unit: "deg",
 | 
			
		||||
                    formatString: '%0.2f',
 | 
			
		||||
                    hints: {
 | 
			
		||||
                        domain: 3
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                // Need to enable "LocalTimeSystem" plugin to make use of this
 | 
			
		||||
                // {
 | 
			
		||||
                //     key: "local",
 | 
			
		||||
 
 | 
			
		||||
@@ -54,38 +54,23 @@
 | 
			
		||||
        var start = Date.now();
 | 
			
		||||
        var step = 1000 / data.dataRateInHz;
 | 
			
		||||
        var nextStep = start - (start % step) + step;
 | 
			
		||||
        let work;
 | 
			
		||||
        if (data.spectra) {
 | 
			
		||||
            work = function (now) {
 | 
			
		||||
                while (nextStep < now) {
 | 
			
		||||
                    const messageCopy = Object.create(message);
 | 
			
		||||
                    message.data.start = nextStep - (60 * 1000);
 | 
			
		||||
                    message.data.end = nextStep;
 | 
			
		||||
                    onRequest(messageCopy);
 | 
			
		||||
                    nextStep += step;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return nextStep;
 | 
			
		||||
            };
 | 
			
		||||
        } else {
 | 
			
		||||
            work = function (now) {
 | 
			
		||||
                while (nextStep < now) {
 | 
			
		||||
                    self.postMessage({
 | 
			
		||||
                        id: message.id,
 | 
			
		||||
                        data: {
 | 
			
		||||
                            name: data.name,
 | 
			
		||||
                            utc: nextStep,
 | 
			
		||||
                            yesterday: nextStep - 60 * 60 * 24 * 1000,
 | 
			
		||||
                            sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
 | 
			
		||||
                            wavelength: wavelength(start, nextStep),
 | 
			
		||||
                            cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    nextStep += step;
 | 
			
		||||
                }
 | 
			
		||||
        function work(now) {
 | 
			
		||||
            while (nextStep < now) {
 | 
			
		||||
                self.postMessage({
 | 
			
		||||
                    id: message.id,
 | 
			
		||||
                    data: {
 | 
			
		||||
                        name: data.name,
 | 
			
		||||
                        utc: nextStep,
 | 
			
		||||
                        yesterday: nextStep - 60 * 60 * 24 * 1000,
 | 
			
		||||
                        sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
 | 
			
		||||
                        cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                nextStep += step;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
                return nextStep;
 | 
			
		||||
            };
 | 
			
		||||
            return nextStep;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        subscriptions[message.id] = work;
 | 
			
		||||
@@ -126,21 +111,13 @@
 | 
			
		||||
                utc: nextStep,
 | 
			
		||||
                yesterday: nextStep - 60 * 60 * 24 * 1000,
 | 
			
		||||
                sin: sin(nextStep, period, amplitude, offset, phase, randomness),
 | 
			
		||||
                wavelength: wavelength(start, nextStep),
 | 
			
		||||
                cos: cos(nextStep, period, amplitude, offset, phase, randomness)
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.postMessage({
 | 
			
		||||
            id: message.id,
 | 
			
		||||
            data: request.spectra ? {
 | 
			
		||||
                wavelength: data.map((item) => {
 | 
			
		||||
                    return item.wavelength;
 | 
			
		||||
                }),
 | 
			
		||||
                cos: data.map((item) => {
 | 
			
		||||
                    return item.cos;
 | 
			
		||||
                })
 | 
			
		||||
            } : data
 | 
			
		||||
            data: data
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -154,10 +131,6 @@
 | 
			
		||||
            * Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function wavelength(start, nextStep) {
 | 
			
		||||
        return (nextStep - start) / 10;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function sendError(error, message) {
 | 
			
		||||
        self.postMessage({
 | 
			
		||||
            error: error.name + ': ' + error.message,
 | 
			
		||||
 
 | 
			
		||||
@@ -20,219 +20,156 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
const DEFAULT_IMAGE_SAMPLES = [
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18731.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18732.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18733.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18734.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18735.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18736.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18737.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18738.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18739.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18740.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18741.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18742.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18743.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18744.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18745.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18746.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18747.jpg",
 | 
			
		||||
    "https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
 | 
			
		||||
];
 | 
			
		||||
const DEFAULT_IMAGE_LOAD_DELAY_IN_MILISECONDS = 20000;
 | 
			
		||||
const MIN_IMAGE_LOAD_DELAY_IN_MILISECONDS = 5000;
 | 
			
		||||
define([
 | 
			
		||||
 | 
			
		||||
let openmctInstance;
 | 
			
		||||
], function (
 | 
			
		||||
 | 
			
		||||
export default function () {
 | 
			
		||||
    return function install(openmct) {
 | 
			
		||||
        openmctInstance = openmct;
 | 
			
		||||
        openmct.types.addType('example.imagery', {
 | 
			
		||||
            key: 'example.imagery',
 | 
			
		||||
            name: 'Example Imagery',
 | 
			
		||||
            cssClass: 'icon-image',
 | 
			
		||||
            description: 'For development use. Creates example imagery '
 | 
			
		||||
                + 'data that mimics a live imagery stream.',
 | 
			
		||||
            creatable: true,
 | 
			
		||||
            initialize: (object) => {
 | 
			
		||||
                object.configuration = {
 | 
			
		||||
                    imageLocation: '',
 | 
			
		||||
                    imageLoadDelayInMilliSeconds: DEFAULT_IMAGE_LOAD_DELAY_IN_MILISECONDS,
 | 
			
		||||
                    imageSamples: []
 | 
			
		||||
                };
 | 
			
		||||
) {
 | 
			
		||||
    function ImageryPlugin() {
 | 
			
		||||
 | 
			
		||||
                object.telemetry = {
 | 
			
		||||
                    values: [
 | 
			
		||||
                        {
 | 
			
		||||
                            name: 'Name',
 | 
			
		||||
                            key: 'name'
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            name: 'Time',
 | 
			
		||||
                            key: 'utc',
 | 
			
		||||
                            format: 'utc',
 | 
			
		||||
                            hints: {
 | 
			
		||||
                                domain: 2
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            name: 'Local Time',
 | 
			
		||||
                            key: 'local',
 | 
			
		||||
                            format: 'local-format',
 | 
			
		||||
                            hints: {
 | 
			
		||||
                                domain: 1
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            name: 'Image',
 | 
			
		||||
                            key: 'url',
 | 
			
		||||
                            format: 'image',
 | 
			
		||||
                            hints: {
 | 
			
		||||
                                image: 1
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            name: 'Image Download Name',
 | 
			
		||||
                            key: 'imageDownloadName',
 | 
			
		||||
                            format: 'imageDownloadName',
 | 
			
		||||
                            hints: {
 | 
			
		||||
                                imageDownloadName: 1
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    ]
 | 
			
		||||
                };
 | 
			
		||||
            },
 | 
			
		||||
            form: [
 | 
			
		||||
                {
 | 
			
		||||
                    key: 'imageLocation',
 | 
			
		||||
                    name: 'Images url list (comma separated)',
 | 
			
		||||
                    control: 'textarea',
 | 
			
		||||
                    cssClass: 'l-inline',
 | 
			
		||||
                    property: [
 | 
			
		||||
                        "configuration",
 | 
			
		||||
                        "imageLocation"
 | 
			
		||||
                    ]
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: 'imageLoadDelayInMilliSeconds',
 | 
			
		||||
                    name: 'Image load delay (milliseconds)',
 | 
			
		||||
                    control: 'numberfield',
 | 
			
		||||
                    required: true,
 | 
			
		||||
                    cssClass: 'l-inline',
 | 
			
		||||
                    property: [
 | 
			
		||||
                        "configuration",
 | 
			
		||||
                        "imageLoadDelayInMilliSeconds"
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        });
 | 
			
		||||
        const IMAGE_SAMPLES = [
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18731.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18732.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18733.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18734.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18735.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18736.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18737.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18738.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18739.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18740.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18741.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18742.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18743.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18744.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18745.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18746.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18747.jpg",
 | 
			
		||||
            "https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
 | 
			
		||||
        ];
 | 
			
		||||
        const IMAGE_DELAY = 20000;
 | 
			
		||||
 | 
			
		||||
        openmct.telemetry.addProvider(getRealtimeProvider());
 | 
			
		||||
        openmct.telemetry.addProvider(getHistoricalProvider());
 | 
			
		||||
        openmct.telemetry.addProvider(getLadProvider());
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
        function getCompassValues(min, max) {
 | 
			
		||||
            return min + Math.random() * (max - min);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
function getCompassValues(min, max) {
 | 
			
		||||
    return min + Math.random() * (max - min);
 | 
			
		||||
}
 | 
			
		||||
        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]}`;
 | 
			
		||||
 | 
			
		||||
function getImageSamples(configuration) {
 | 
			
		||||
    let imageSamples = DEFAULT_IMAGE_SAMPLES;
 | 
			
		||||
 | 
			
		||||
    if (configuration.imageLocation && configuration.imageLocation.length) {
 | 
			
		||||
        imageSamples = getImageUrlListFromConfig(configuration);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return imageSamples;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getImageUrlListFromConfig(configuration) {
 | 
			
		||||
    return configuration.imageLocation.split(',');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getImageLoadDelay(domainObject) {
 | 
			
		||||
    const imageLoadDelay = domainObject.configuration.imageLoadDelayInMilliSeconds;
 | 
			
		||||
    if (!imageLoadDelay) {
 | 
			
		||||
        openmctInstance.objects.mutate(domainObject, 'configuration.imageLoadDelayInMilliSeconds', DEFAULT_IMAGE_LOAD_DELAY_IN_MILISECONDS);
 | 
			
		||||
 | 
			
		||||
        return DEFAULT_IMAGE_LOAD_DELAY_IN_MILISECONDS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (imageLoadDelay < MIN_IMAGE_LOAD_DELAY_IN_MILISECONDS) {
 | 
			
		||||
        openmctInstance.objects.mutate(domainObject, 'configuration.imageLoadDelayInMilliSeconds', MIN_IMAGE_LOAD_DELAY_IN_MILISECONDS);
 | 
			
		||||
 | 
			
		||||
        return MIN_IMAGE_LOAD_DELAY_IN_MILISECONDS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return imageLoadDelay;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getRealtimeProvider() {
 | 
			
		||||
    return {
 | 
			
		||||
        supportsSubscribe: domainObject => domainObject.type === 'example.imagery',
 | 
			
		||||
        subscribe: (domainObject, callback) => {
 | 
			
		||||
            const delay = getImageLoadDelay(domainObject);
 | 
			
		||||
            const interval = setInterval(() => {
 | 
			
		||||
                callback(pointForTimestamp(Date.now(), domainObject.name, getImageSamples(domainObject.configuration), delay));
 | 
			
		||||
            }, delay);
 | 
			
		||||
 | 
			
		||||
            return () => {
 | 
			
		||||
                clearInterval(interval);
 | 
			
		||||
            return {
 | 
			
		||||
                name,
 | 
			
		||||
                utc: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
 | 
			
		||||
                local: Math.floor(timestamp / IMAGE_DELAY) * IMAGE_DELAY,
 | 
			
		||||
                url,
 | 
			
		||||
                sunOrientation: getCompassValues(0, 360),
 | 
			
		||||
                cameraPan: getCompassValues(0, 360),
 | 
			
		||||
                heading: getCompassValues(0, 360),
 | 
			
		||||
                imageDownloadName
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getHistoricalProvider() {
 | 
			
		||||
    return {
 | 
			
		||||
        supportsRequest: (domainObject, options) => {
 | 
			
		||||
            return domainObject.type === 'example.imagery'
 | 
			
		||||
                && options.strategy !== 'latest';
 | 
			
		||||
        },
 | 
			
		||||
        request: (domainObject, options) => {
 | 
			
		||||
            const delay = getImageLoadDelay(domainObject);
 | 
			
		||||
            let start = options.start;
 | 
			
		||||
            const end = Math.min(options.end, Date.now());
 | 
			
		||||
            const data = [];
 | 
			
		||||
            while (start <= end && data.length < delay) {
 | 
			
		||||
                data.push(pointForTimestamp(start, domainObject.name, getImageSamples(domainObject.configuration), delay));
 | 
			
		||||
                start += delay;
 | 
			
		||||
        var realtimeProvider = {
 | 
			
		||||
            supportsSubscribe: function (domainObject) {
 | 
			
		||||
                return domainObject.type === 'example.imagery';
 | 
			
		||||
            },
 | 
			
		||||
            subscribe: function (domainObject, callback) {
 | 
			
		||||
                var interval = setInterval(function () {
 | 
			
		||||
                    callback(pointForTimestamp(Date.now(), domainObject.name));
 | 
			
		||||
                }, IMAGE_DELAY);
 | 
			
		||||
 | 
			
		||||
                return function () {
 | 
			
		||||
                    clearInterval(interval);
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
            return Promise.resolve(data);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
        var historicalProvider = {
 | 
			
		||||
            supportsRequest: function (domainObject, options) {
 | 
			
		||||
                return domainObject.type === 'example.imagery'
 | 
			
		||||
                    && options.strategy !== 'latest';
 | 
			
		||||
            },
 | 
			
		||||
            request: function (domainObject, options) {
 | 
			
		||||
                var start = options.start;
 | 
			
		||||
                var end = Math.min(options.end, Date.now());
 | 
			
		||||
                var data = [];
 | 
			
		||||
                while (start <= end && data.length < IMAGE_DELAY) {
 | 
			
		||||
                    data.push(pointForTimestamp(start, domainObject.name));
 | 
			
		||||
                    start += IMAGE_DELAY;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
function getLadProvider() {
 | 
			
		||||
    return {
 | 
			
		||||
        supportsRequest: (domainObject, options) => {
 | 
			
		||||
            return domainObject.type === 'example.imagery'
 | 
			
		||||
                && options.strategy === 'latest';
 | 
			
		||||
        },
 | 
			
		||||
        request: (domainObject, options) => {
 | 
			
		||||
            const delay = getImageLoadDelay(domainObject);
 | 
			
		||||
                return Promise.resolve(data);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
            return Promise.resolve([pointForTimestamp(Date.now(), domainObject.name, delay)]);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
        var ladProvider = {
 | 
			
		||||
            supportsRequest: function (domainObject, options) {
 | 
			
		||||
                return domainObject.type === 'example.imagery'
 | 
			
		||||
                    && options.strategy === 'latest';
 | 
			
		||||
            },
 | 
			
		||||
            request: function (domainObject, options) {
 | 
			
		||||
                return Promise.resolve([pointForTimestamp(Date.now(), domainObject.name)]);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
function pointForTimestamp(timestamp, name, imageSamples, delay) {
 | 
			
		||||
    const url = imageSamples[Math.floor(timestamp / delay) % imageSamples.length];
 | 
			
		||||
    const urlItems = url.split('/');
 | 
			
		||||
    const imageDownloadName = `example.imagery.${urlItems[urlItems.length - 1]}`;
 | 
			
		||||
        return function install(openmct) {
 | 
			
		||||
            openmct.types.addType('example.imagery', {
 | 
			
		||||
                key: 'example.imagery',
 | 
			
		||||
                name: 'Example Imagery',
 | 
			
		||||
                cssClass: 'icon-image',
 | 
			
		||||
                description: 'For development use. Creates example imagery '
 | 
			
		||||
                    + 'data that mimics a live imagery stream.',
 | 
			
		||||
                creatable: true,
 | 
			
		||||
                initialize: function (object) {
 | 
			
		||||
                    object.telemetry = {
 | 
			
		||||
                        values: [
 | 
			
		||||
                            {
 | 
			
		||||
                                name: 'Name',
 | 
			
		||||
                                key: 'name'
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                name: 'Time',
 | 
			
		||||
                                key: 'utc',
 | 
			
		||||
                                format: 'utc',
 | 
			
		||||
                                hints: {
 | 
			
		||||
                                    domain: 2
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                name: 'Local Time',
 | 
			
		||||
                                key: 'local',
 | 
			
		||||
                                format: 'local-format',
 | 
			
		||||
                                hints: {
 | 
			
		||||
                                    domain: 1
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                name: 'Image',
 | 
			
		||||
                                key: 'url',
 | 
			
		||||
                                format: 'image',
 | 
			
		||||
                                hints: {
 | 
			
		||||
                                    image: 1
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                name: 'Image Download Name',
 | 
			
		||||
                                key: 'imageDownloadName',
 | 
			
		||||
                                format: 'imageDownloadName',
 | 
			
		||||
                                hints: {
 | 
			
		||||
                                    imageDownloadName: 1
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        ]
 | 
			
		||||
                    };
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        name,
 | 
			
		||||
        utc: Math.floor(timestamp / delay) * delay,
 | 
			
		||||
        local: Math.floor(timestamp / delay) * delay,
 | 
			
		||||
        url,
 | 
			
		||||
        sunOrientation: getCompassValues(0, 360),
 | 
			
		||||
        cameraPan: getCompassValues(0, 360),
 | 
			
		||||
        heading: getCompassValues(0, 360),
 | 
			
		||||
        imageDownloadName
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
            openmct.telemetry.addProvider(realtimeProvider);
 | 
			
		||||
            openmct.telemetry.addProvider(historicalProvider);
 | 
			
		||||
            openmct.telemetry.addProvider(ladProvider);
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return ImageryPlugin;
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -82,7 +82,6 @@
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        openmct.install(openmct.plugins.LocalStorage());
 | 
			
		||||
 | 
			
		||||
        openmct.install(openmct.plugins.Espresso());
 | 
			
		||||
        openmct.install(openmct.plugins.MyItems());
 | 
			
		||||
        openmct.install(openmct.plugins.Generator());
 | 
			
		||||
@@ -197,7 +196,6 @@
 | 
			
		||||
            {indicator: true}
 | 
			
		||||
        ));
 | 
			
		||||
        openmct.install(openmct.plugins.Clock({ enableClockIndicator: true }));
 | 
			
		||||
        openmct.install(openmct.plugins.Timer());
 | 
			
		||||
        openmct.start();
 | 
			
		||||
    </script>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
const devMode = process.env.NODE_ENV !== 'production';
 | 
			
		||||
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
 | 
			
		||||
const coverageEnabled = process.env.COVERAGE === 'true';
 | 
			
		||||
const reporters = ['spec', 'junit'];
 | 
			
		||||
const reporters = ['progress', 'html', 'junit'];
 | 
			
		||||
 | 
			
		||||
if (coverageEnabled) {
 | 
			
		||||
    reporters.push('coverage-istanbul');
 | 
			
		||||
@@ -60,7 +60,7 @@ module.exports = (config) => {
 | 
			
		||||
        client: {
 | 
			
		||||
            jasmine: {
 | 
			
		||||
                random: false,
 | 
			
		||||
                timeoutInterval: 5000
 | 
			
		||||
                timeoutInterval: 30000
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        customLaunchers: {
 | 
			
		||||
@@ -78,37 +78,33 @@ module.exports = (config) => {
 | 
			
		||||
        logLevel: config.LOG_INFO,
 | 
			
		||||
        autoWatch: true,
 | 
			
		||||
        // HTML test reporting.
 | 
			
		||||
        // htmlReporter: {
 | 
			
		||||
        //    outputDir: "dist/reports/tests",
 | 
			
		||||
        //    preserveDescribeNesting: true,
 | 
			
		||||
        //    foldAll: false
 | 
			
		||||
        // },
 | 
			
		||||
        htmlReporter: {
 | 
			
		||||
            outputDir: "dist/reports/tests",
 | 
			
		||||
            preserveDescribeNesting: true,
 | 
			
		||||
            foldAll: false
 | 
			
		||||
        },
 | 
			
		||||
        junitReporter: {
 | 
			
		||||
            outputDir: "dist/reports/tests",
 | 
			
		||||
            outputFile: "test-results.xml",
 | 
			
		||||
            useBrowserName: false
 | 
			
		||||
        },
 | 
			
		||||
        browserConsoleLogOptions: {
 | 
			
		||||
            level: "error",
 | 
			
		||||
            format: "%b %T: %m",
 | 
			
		||||
            terminal: true
 | 
			
		||||
        },
 | 
			
		||||
        coverageIstanbulReporter: {
 | 
			
		||||
            fixWebpackSourcePaths: true,
 | 
			
		||||
            dir: process.env.CIRCLE_ARTIFACTS
 | 
			
		||||
                ? process.env.CIRCLE_ARTIFACTS + '/coverage'
 | 
			
		||||
                : "dist/reports/coverage",
 | 
			
		||||
            reports: ['lcovonly', 'text-summary'],
 | 
			
		||||
            reports: ['html', 'lcovonly', 'text-summary'],
 | 
			
		||||
            thresholds: {
 | 
			
		||||
                global: {
 | 
			
		||||
                    lines: 66
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        specReporter: {
 | 
			
		||||
            maxLogLines: 5,
 | 
			
		||||
            suppressErrorSummary: false,
 | 
			
		||||
            suppressFailed: false,
 | 
			
		||||
            suppressPassed: false,
 | 
			
		||||
            suppressSkipped: true,
 | 
			
		||||
            showSpecTiming: true,
 | 
			
		||||
            failFast: false
 | 
			
		||||
        },
 | 
			
		||||
        preprocessors: {
 | 
			
		||||
            'indexTest.js': ['webpack', 'sourcemap']
 | 
			
		||||
        },
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								package.json
									
									
									
									
									
								
							@@ -1,9 +1,9 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "openmct",
 | 
			
		||||
  "version": "1.8.1",
 | 
			
		||||
  "version": "1.7.8-SNAPSHOT",
 | 
			
		||||
  "description": "The Open MCT core platform",
 | 
			
		||||
  "dependencies": {},
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@braintree/sanitize-url": "^5.0.2",
 | 
			
		||||
    "angular": ">=1.8.0",
 | 
			
		||||
    "angular-route": "1.4.14",
 | 
			
		||||
    "babel-eslint": "10.0.3",
 | 
			
		||||
@@ -12,9 +12,16 @@
 | 
			
		||||
    "copy-webpack-plugin": "^4.5.2",
 | 
			
		||||
    "cross-env": "^6.0.3",
 | 
			
		||||
    "css-loader": "^1.0.0",
 | 
			
		||||
    "d3-array": "1.2.x",
 | 
			
		||||
    "d3-axis": "1.0.x",
 | 
			
		||||
    "d3-collection": "1.0.x",
 | 
			
		||||
    "d3-color": "1.0.x",
 | 
			
		||||
    "d3-format": "1.2.x",
 | 
			
		||||
    "d3-interpolate": "1.1.x",
 | 
			
		||||
    "d3-scale": "1.0.x",
 | 
			
		||||
    "d3-selection": "1.3.x",
 | 
			
		||||
    "d3-time": "1.0.x",
 | 
			
		||||
    "d3-time-format": "2.1.x",
 | 
			
		||||
    "eslint": "7.0.0",
 | 
			
		||||
    "eslint-plugin-vue": "^7.5.0",
 | 
			
		||||
    "eslint-plugin-you-dont-need-lodash-underscore": "^6.10.0",
 | 
			
		||||
@@ -34,14 +41,14 @@
 | 
			
		||||
    "jsdoc": "^3.3.2",
 | 
			
		||||
    "karma": "6.3.4",
 | 
			
		||||
    "karma-chrome-launcher": "3.1.0",
 | 
			
		||||
    "karma-firefox-launcher": "2.1.1",
 | 
			
		||||
    "karma-cli": "2.0.0",
 | 
			
		||||
    "karma-coverage": "2.0.3",
 | 
			
		||||
    "karma-coverage-istanbul-reporter": "3.0.3",
 | 
			
		||||
    "karma-firefox-launcher": "2.1.1",
 | 
			
		||||
    "karma-jasmine": "4.0.1",
 | 
			
		||||
    "karma-junit-reporter": "2.0.1",
 | 
			
		||||
    "karma-html-reporter": "0.2.7",
 | 
			
		||||
    "karma-jasmine": "4.0.1",
 | 
			
		||||
    "karma-sourcemap-loader": "0.3.8",
 | 
			
		||||
    "karma-spec-reporter": "0.0.32",
 | 
			
		||||
    "karma-webpack": "4.0.2",
 | 
			
		||||
    "location-bar": "^3.0.1",
 | 
			
		||||
    "lodash": "^4.17.12",
 | 
			
		||||
@@ -55,8 +62,6 @@
 | 
			
		||||
    "node-bourbon": "^4.2.3",
 | 
			
		||||
    "node-sass": "^4.14.1",
 | 
			
		||||
    "painterro": "^1.2.56",
 | 
			
		||||
    "plotly.js-basic-dist": "^2.5.0",
 | 
			
		||||
    "plotly.js-gl2d-dist": "^2.5.0",
 | 
			
		||||
    "printj": "^1.2.1",
 | 
			
		||||
    "raw-loader": "^0.5.1",
 | 
			
		||||
    "request": "^2.69.0",
 | 
			
		||||
@@ -65,7 +70,6 @@
 | 
			
		||||
    "uuid": "^3.3.3",
 | 
			
		||||
    "v8-compile-cache": "^1.1.0",
 | 
			
		||||
    "vue": "2.5.6",
 | 
			
		||||
    "vue-eslint-parser": "7.11.0",
 | 
			
		||||
    "vue-loader": "^15.2.6",
 | 
			
		||||
    "vue-template-compiler": "2.5.6",
 | 
			
		||||
    "webpack": "^4.16.2",
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,9 @@ define([
 | 
			
		||||
    "./src/policies/EditPersistableObjectsPolicy",
 | 
			
		||||
    "./src/representers/EditRepresenter",
 | 
			
		||||
    "./src/capabilities/EditorCapability",
 | 
			
		||||
    "./src/capabilities/TransactionCapabilityDecorator",
 | 
			
		||||
    "./src/services/TransactionManager",
 | 
			
		||||
    "./src/services/TransactionService",
 | 
			
		||||
    "./src/creation/CreateMenuController",
 | 
			
		||||
    "./src/creation/LocatorController",
 | 
			
		||||
    "./src/creation/CreationPolicy",
 | 
			
		||||
@@ -60,6 +63,9 @@ define([
 | 
			
		||||
    EditPersistableObjectsPolicy,
 | 
			
		||||
    EditRepresenter,
 | 
			
		||||
    EditorCapability,
 | 
			
		||||
    TransactionCapabilityDecorator,
 | 
			
		||||
    TransactionManager,
 | 
			
		||||
    TransactionService,
 | 
			
		||||
    CreateMenuController,
 | 
			
		||||
    LocatorController,
 | 
			
		||||
    CreationPolicy,
 | 
			
		||||
@@ -257,6 +263,26 @@ define([
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "components": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "type": "decorator",
 | 
			
		||||
                        "provides": "capabilityService",
 | 
			
		||||
                        "implementation": TransactionCapabilityDecorator,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "$q",
 | 
			
		||||
                            "transactionManager"
 | 
			
		||||
                        ],
 | 
			
		||||
                        "priority": "fallback"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "type": "provider",
 | 
			
		||||
                        "provides": "transactionService",
 | 
			
		||||
                        "implementation": TransactionService,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "$q",
 | 
			
		||||
                            "$log",
 | 
			
		||||
                            "cacheService"
 | 
			
		||||
                        ]
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "CreateActionProvider",
 | 
			
		||||
                        "provides": "actionService",
 | 
			
		||||
@@ -294,6 +320,7 @@ define([
 | 
			
		||||
                        "description": "Provides transactional editing capabilities",
 | 
			
		||||
                        "implementation": EditorCapability,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "transactionService",
 | 
			
		||||
                            "openmct"
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
@@ -304,6 +331,15 @@ define([
 | 
			
		||||
                        "template": locatorTemplate
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "services": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "transactionManager",
 | 
			
		||||
                        "implementation": TransactionManager,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "transactionService"
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "runs": [
 | 
			
		||||
                    {
 | 
			
		||||
                        depends: [
 | 
			
		||||
 
 | 
			
		||||
@@ -96,7 +96,8 @@ function (
 | 
			
		||||
    SaveAsAction.prototype.save = function () {
 | 
			
		||||
        var self = this,
 | 
			
		||||
            domainObject = this.domainObject,
 | 
			
		||||
            dialog = new SaveInProgressDialog(this.dialogService);
 | 
			
		||||
            dialog = new SaveInProgressDialog(this.dialogService),
 | 
			
		||||
            toUndirty = [];
 | 
			
		||||
 | 
			
		||||
        function doWizardSave(parent) {
 | 
			
		||||
            var wizard = self.createWizard(parent);
 | 
			
		||||
@@ -131,14 +132,11 @@ function (
 | 
			
		||||
            return fetchObject(object.getModel().location);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function saveObject(object) {
 | 
			
		||||
            //persist the object, which adds it to the transaction and then call editor.save
 | 
			
		||||
            return object.getCapability("persistence").persist()
 | 
			
		||||
                .then(() => {
 | 
			
		||||
                    return self.openmct.editor.save().then(() => {
 | 
			
		||||
                        return object;
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
        function saveObject(parent) {
 | 
			
		||||
            return self.openmct.editor.save().then(() => {
 | 
			
		||||
                // Force mutation for search indexing
 | 
			
		||||
                return parent;
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function addSavedObjectToParent(parent) {
 | 
			
		||||
@@ -152,6 +150,17 @@ function (
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function undirty(object) {
 | 
			
		||||
            return object.getCapability('persistence').refresh();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function undirtyOriginals(object) {
 | 
			
		||||
            return Promise.all(toUndirty.map(undirty))
 | 
			
		||||
                .then(() => {
 | 
			
		||||
                    return object;
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function indexForSearch(addedObject) {
 | 
			
		||||
            addedObject.useCapability('mutation', (model) => {
 | 
			
		||||
                return model;
 | 
			
		||||
@@ -178,9 +187,10 @@ function (
 | 
			
		||||
        return getParent(domainObject)
 | 
			
		||||
            .then(doWizardSave)
 | 
			
		||||
            .then(showBlockingDialog)
 | 
			
		||||
            .then(saveObject)
 | 
			
		||||
            .then(getParent)
 | 
			
		||||
            .then(saveObject)
 | 
			
		||||
            .then(addSavedObjectToParent)
 | 
			
		||||
            .then(undirtyOriginals)
 | 
			
		||||
            .then((addedObject) => {
 | 
			
		||||
                return fetchObject(addedObject.getId());
 | 
			
		||||
            })
 | 
			
		||||
 
 | 
			
		||||
@@ -30,17 +30,34 @@ define(
 | 
			
		||||
         * Once initiated, any persist operations will be queued pending a
 | 
			
		||||
         * subsequent call to [.save()](@link #save) or [.finish()](@link
 | 
			
		||||
         * #finish).
 | 
			
		||||
         * @param transactionService
 | 
			
		||||
         * @param domainObject
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function EditorCapability(
 | 
			
		||||
            transactionService,
 | 
			
		||||
            openmct,
 | 
			
		||||
            domainObject
 | 
			
		||||
        ) {
 | 
			
		||||
            this.transactionService = transactionService;
 | 
			
		||||
            this.openmct = openmct;
 | 
			
		||||
            this.domainObject = domainObject;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Initiate an editing session. This will start a transaction during
 | 
			
		||||
         * which any persist operations will be deferred until either save()
 | 
			
		||||
         * or finish() are called.
 | 
			
		||||
         */
 | 
			
		||||
        EditorCapability.prototype.edit = function () {
 | 
			
		||||
            console.warn('DEPRECATED: cannot edit via edit capability, use openmct.editor instead.');
 | 
			
		||||
 | 
			
		||||
            if (!this.openmct.editor.isEditing()) {
 | 
			
		||||
                this.openmct.editor.edit();
 | 
			
		||||
                this.domainObject.getCapability('status').set('editing', true);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Determines whether this object, or any of its ancestors are
 | 
			
		||||
         * currently being edited.
 | 
			
		||||
@@ -59,6 +76,38 @@ define(
 | 
			
		||||
            return this.openmct.editor.isEditing();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Save any unsaved changes from this editing session. This will
 | 
			
		||||
         * end the current transaction and continue with a new one.
 | 
			
		||||
         * @returns {*}
 | 
			
		||||
         */
 | 
			
		||||
        EditorCapability.prototype.save = function () {
 | 
			
		||||
            console.warn('DEPRECATED: cannot save via edit capability, use openmct.editor instead.');
 | 
			
		||||
 | 
			
		||||
            return Promise.resolve();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        EditorCapability.prototype.invoke = EditorCapability.prototype.edit;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Finish the current editing session. This will discard any pending
 | 
			
		||||
         * persist operations
 | 
			
		||||
         * @returns {*}
 | 
			
		||||
         */
 | 
			
		||||
        EditorCapability.prototype.finish = function () {
 | 
			
		||||
            console.warn('DEPRECATED: cannot finish via edit capability, use openmct.editor instead.');
 | 
			
		||||
 | 
			
		||||
            return Promise.resolve();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * @returns {boolean} true if there have been any domain model
 | 
			
		||||
         * modifications since the last persist, false otherwise.
 | 
			
		||||
         */
 | 
			
		||||
        EditorCapability.prototype.dirty = function () {
 | 
			
		||||
            return this.transactionService.size() > 0;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return EditorCapability;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,75 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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(
 | 
			
		||||
    ['./TransactionalPersistenceCapability'],
 | 
			
		||||
    function (TransactionalPersistenceCapability) {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Wraps the [PersistenceCapability]{@link PersistenceCapability} with
 | 
			
		||||
         * transactional capabilities.
 | 
			
		||||
         * @param $q
 | 
			
		||||
         * @param transactionService
 | 
			
		||||
         * @param capabilityService
 | 
			
		||||
         * @see TransactionalPersistenceCapability
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function TransactionCapabilityDecorator(
 | 
			
		||||
            $q,
 | 
			
		||||
            transactionService,
 | 
			
		||||
            capabilityService
 | 
			
		||||
        ) {
 | 
			
		||||
            this.capabilityService = capabilityService;
 | 
			
		||||
            this.transactionService = transactionService;
 | 
			
		||||
            this.$q = $q;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Decorate PersistenceCapability to queue persistence calls when a
 | 
			
		||||
         * transaction is in progress.
 | 
			
		||||
         */
 | 
			
		||||
        TransactionCapabilityDecorator.prototype.getCapabilities = function () {
 | 
			
		||||
            var self = this,
 | 
			
		||||
                capabilities = this.capabilityService.getCapabilities
 | 
			
		||||
                    .apply(this.capabilityService, arguments),
 | 
			
		||||
                persistenceCapability = capabilities.persistence;
 | 
			
		||||
 | 
			
		||||
            capabilities.persistence = function (domainObject) {
 | 
			
		||||
                var original =
 | 
			
		||||
                    (typeof persistenceCapability === 'function')
 | 
			
		||||
                        ? persistenceCapability(domainObject)
 | 
			
		||||
                        : persistenceCapability;
 | 
			
		||||
 | 
			
		||||
                return new TransactionalPersistenceCapability(
 | 
			
		||||
                    self.$q,
 | 
			
		||||
                    self.transactionService,
 | 
			
		||||
                    original,
 | 
			
		||||
                    domainObject
 | 
			
		||||
                );
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            return capabilities;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return TransactionCapabilityDecorator;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -0,0 +1,91 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 () {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Wraps persistence capability to enable transactions. Transactions
 | 
			
		||||
         * will cause persist calls not to be invoked immediately, but
 | 
			
		||||
         * rather queued until [EditorCapability.save()]{@link EditorCapability#save}
 | 
			
		||||
         * or [EditorCapability.cancel()]{@link EditorCapability#cancel} are
 | 
			
		||||
         * called.
 | 
			
		||||
         * @memberof platform/commonUI/edit/capabilities
 | 
			
		||||
         * @param $q
 | 
			
		||||
         * @param transactionManager
 | 
			
		||||
         * @param persistenceCapability
 | 
			
		||||
         * @param domainObject
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function TransactionalPersistenceCapability(
 | 
			
		||||
            $q,
 | 
			
		||||
            transactionManager,
 | 
			
		||||
            persistenceCapability,
 | 
			
		||||
            domainObject
 | 
			
		||||
        ) {
 | 
			
		||||
            this.transactionManager = transactionManager;
 | 
			
		||||
            this.persistenceCapability = persistenceCapability;
 | 
			
		||||
            this.domainObject = domainObject;
 | 
			
		||||
            this.$q = $q;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The wrapped persist function. If a transaction is active, persist
 | 
			
		||||
         * will be queued until the transaction is committed or cancelled.
 | 
			
		||||
         * @returns {*}
 | 
			
		||||
         */
 | 
			
		||||
        TransactionalPersistenceCapability.prototype.persist = function () {
 | 
			
		||||
            var wrappedPersistence = this.persistenceCapability;
 | 
			
		||||
 | 
			
		||||
            if (this.transactionManager.isActive()) {
 | 
			
		||||
                this.transactionManager.addToTransaction(
 | 
			
		||||
                    this.domainObject.getId(),
 | 
			
		||||
                    wrappedPersistence.persist.bind(wrappedPersistence),
 | 
			
		||||
                    wrappedPersistence.refresh.bind(wrappedPersistence)
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                //Need to return a promise from this function
 | 
			
		||||
                return this.$q.when(true);
 | 
			
		||||
            } else {
 | 
			
		||||
                return this.persistenceCapability.persist();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        TransactionalPersistenceCapability.prototype.refresh = function () {
 | 
			
		||||
            this.transactionManager
 | 
			
		||||
                .clearTransactionsFor(this.domainObject.getId());
 | 
			
		||||
 | 
			
		||||
            return this.persistenceCapability.refresh();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        TransactionalPersistenceCapability.prototype.getSpace = function () {
 | 
			
		||||
            return this.persistenceCapability.getSpace();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        TransactionalPersistenceCapability.prototype.persisted = function () {
 | 
			
		||||
            return this.persistenceCapability.persisted();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return TransactionalPersistenceCapability;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -86,20 +86,11 @@ define(
 | 
			
		||||
                        })
 | 
			
		||||
                        .join('/');
 | 
			
		||||
 | 
			
		||||
                function editObject() {
 | 
			
		||||
                    const path = objectPath.slice(-1).map(obj => {
 | 
			
		||||
                        const objNew = obj.getCapability('adapter').invoke();
 | 
			
		||||
 | 
			
		||||
                        return objNew;
 | 
			
		||||
                    });
 | 
			
		||||
                    if (isFirstViewEditable(object.useCapability('adapter'), path)) {
 | 
			
		||||
                        openmct.editor.edit();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                openmct.router.once('afterNavigation', editObject);
 | 
			
		||||
 | 
			
		||||
                openmct.router.navigate(url);
 | 
			
		||||
 | 
			
		||||
                if (isFirstViewEditable(object.useCapability('adapter'), objectPath)) {
 | 
			
		||||
                    openmct.editor.edit();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            newModel.type = this.type.getKey();
 | 
			
		||||
 
 | 
			
		||||
@@ -19,35 +19,31 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import { BAR_GRAPH_KEY } from './BarGraphConstants';
 | 
			
		||||
 | 
			
		||||
export default function BarGraphCompositionPolicy(openmct) {
 | 
			
		||||
    function hasRange(metadata) {
 | 
			
		||||
        const rangeValues = metadata.valuesForHints(['range']);
 | 
			
		||||
 | 
			
		||||
        return rangeValues.length > 0;
 | 
			
		||||
define(['./Transaction'], function (Transaction) {
 | 
			
		||||
    /**
 | 
			
		||||
     * A nested transaction is a transaction which takes place in the context
 | 
			
		||||
     * of a larger parent transaction. It becomes part of the parent
 | 
			
		||||
     * transaction when (and only when) committed.
 | 
			
		||||
     * @param parent
 | 
			
		||||
     * @constructor
 | 
			
		||||
     * @extends {platform/commonUI/edit/services.Transaction}
 | 
			
		||||
     * @memberof platform/commonUI/edit/services
 | 
			
		||||
     */
 | 
			
		||||
    function NestedTransaction(parent) {
 | 
			
		||||
        this.parent = parent;
 | 
			
		||||
        Transaction.call(this, parent.$log);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function hasBarGraphTelemetry(domainObject) {
 | 
			
		||||
        if (!openmct.telemetry.isTelemetryObject(domainObject)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    NestedTransaction.prototype = Object.create(Transaction.prototype);
 | 
			
		||||
 | 
			
		||||
        let metadata = openmct.telemetry.getMetadata(domainObject);
 | 
			
		||||
    NestedTransaction.prototype.commit = function () {
 | 
			
		||||
        this.parent.add(
 | 
			
		||||
            Transaction.prototype.commit.bind(this),
 | 
			
		||||
            Transaction.prototype.cancel.bind(this)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return metadata.values().length > 0 && hasRange(metadata);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        allow: function (parent, child) {
 | 
			
		||||
            if ((parent.type === BAR_GRAPH_KEY)
 | 
			
		||||
                && (hasBarGraphTelemetry(child) === false)
 | 
			
		||||
            ) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve(true);
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    return NestedTransaction;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										99
									
								
								platform/commonUI/edit/src/services/Transaction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								platform/commonUI/edit/src/services/Transaction.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 Transaction represents a set of changes that are intended to
 | 
			
		||||
     * be kept or discarded as a unit.
 | 
			
		||||
     * @param $log Angular's `$log` service, for logging messages
 | 
			
		||||
     * @constructor
 | 
			
		||||
     * @memberof platform/commonUI/edit/services
 | 
			
		||||
     */
 | 
			
		||||
    function Transaction($log) {
 | 
			
		||||
        this.$log = $log;
 | 
			
		||||
        this.callbacks = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a change to the current transaction, as expressed by functions
 | 
			
		||||
     * to either keep or discard the change.
 | 
			
		||||
     * @param {Function} commit called when the transaction is committed
 | 
			
		||||
     * @param {Function} cancel called when the transaction is cancelled
 | 
			
		||||
     * @returns {Function) a function which may be called to remove this
 | 
			
		||||
     *          pair of callbacks from the transaction
 | 
			
		||||
     */
 | 
			
		||||
    Transaction.prototype.add = function (commit, cancel) {
 | 
			
		||||
        var callback = {
 | 
			
		||||
            commit: commit,
 | 
			
		||||
            cancel: cancel
 | 
			
		||||
        };
 | 
			
		||||
        this.callbacks.push(callback);
 | 
			
		||||
 | 
			
		||||
        return function () {
 | 
			
		||||
            this.callbacks = this.callbacks.filter(function (c) {
 | 
			
		||||
                return c !== callback;
 | 
			
		||||
            });
 | 
			
		||||
        }.bind(this);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the number of changes in the current transaction.
 | 
			
		||||
     * @returns {number} the size of the current transaction
 | 
			
		||||
     */
 | 
			
		||||
    Transaction.prototype.size = function () {
 | 
			
		||||
        return this.callbacks.length;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Keep all changes associated with this transaction.
 | 
			
		||||
     * @method {platform/commonUI/edit/services.Transaction#commit}
 | 
			
		||||
     * @returns {Promise} a promise which will resolve when all callbacks
 | 
			
		||||
     *          have been handled.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Discard all changes associated with this transaction.
 | 
			
		||||
     * @method {platform/commonUI/edit/services.Transaction#cancel}
 | 
			
		||||
     * @returns {Promise} a promise which will resolve when all callbacks
 | 
			
		||||
     *          have been handled.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    ['commit', 'cancel'].forEach(function (method) {
 | 
			
		||||
        Transaction.prototype[method] = function () {
 | 
			
		||||
            var promises = [];
 | 
			
		||||
            var callback;
 | 
			
		||||
 | 
			
		||||
            while (this.callbacks.length > 0) {
 | 
			
		||||
                callback = this.callbacks.shift();
 | 
			
		||||
                try {
 | 
			
		||||
                    promises.push(callback[method]());
 | 
			
		||||
                } catch (e) {
 | 
			
		||||
                    this.$log
 | 
			
		||||
                        .error("Error trying to " + method + " transaction.");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Promise.all(promises);
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return Transaction;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										119
									
								
								platform/commonUI/edit/src/services/TransactionManager.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								platform/commonUI/edit/src/services/TransactionManager.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 () {
 | 
			
		||||
    /**
 | 
			
		||||
     * Manages transactions to support the TransactionalPersistenceCapability.
 | 
			
		||||
     * This assumes that all commit/cancel callbacks for a given domain
 | 
			
		||||
     * object are equivalent, and only need to be added once to any active
 | 
			
		||||
     * transaction. Violating this assumption may cause unexpected behavior.
 | 
			
		||||
     * @constructor
 | 
			
		||||
     * @memberof platform/commonUI/edit
 | 
			
		||||
     */
 | 
			
		||||
    function TransactionManager(transactionService) {
 | 
			
		||||
        this.transactionService = transactionService;
 | 
			
		||||
        this.clearTransactionFns = {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if a transaction is currently active.
 | 
			
		||||
     * @returns {boolean} true if there is a transaction active
 | 
			
		||||
     */
 | 
			
		||||
    TransactionManager.prototype.isActive = function () {
 | 
			
		||||
        return this.transactionService.isActive();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if callbacks associated with this domain object have already
 | 
			
		||||
     * been added to the active transaction.
 | 
			
		||||
     * @private
 | 
			
		||||
     * @param {string} id the identifier of the domain object to check
 | 
			
		||||
     * @returns {boolean} true if callbacks have been added
 | 
			
		||||
     */
 | 
			
		||||
    TransactionManager.prototype.isScheduled = function (id) {
 | 
			
		||||
        return Boolean(this.clearTransactionFns[id]);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add callbacks associated with this domain object to the active
 | 
			
		||||
     * transaction. Both callbacks are expected to return promises that
 | 
			
		||||
     * resolve when their associated behavior is complete.
 | 
			
		||||
     *
 | 
			
		||||
     * If callbacks associated with this domain object have already been
 | 
			
		||||
     * added to the active transaction, this call will be ignored.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {string} id the identifier of the associated domain object
 | 
			
		||||
     * @param {Function} onCommit behavior to invoke when committing transaction
 | 
			
		||||
     * @param {Function} onCancel behavior to invoke when cancelling transaction
 | 
			
		||||
     */
 | 
			
		||||
    TransactionManager.prototype.addToTransaction = function (
 | 
			
		||||
        id,
 | 
			
		||||
        onCommit,
 | 
			
		||||
        onCancel
 | 
			
		||||
    ) {
 | 
			
		||||
        var release = this.releaseClearFn.bind(this, id);
 | 
			
		||||
 | 
			
		||||
        function chain(promiseFn, nextFn) {
 | 
			
		||||
            return function () {
 | 
			
		||||
                return promiseFn().then(nextFn);
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Clear any existing persistence calls for object with given ID. This ensures only the most recent persistence
 | 
			
		||||
         * call is executed. This should prevent stale objects being persisted and overwriting fresh ones.
 | 
			
		||||
         */
 | 
			
		||||
        if (this.isScheduled(id)) {
 | 
			
		||||
            this.clearTransactionsFor(id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.clearTransactionFns[id] =
 | 
			
		||||
            this.transactionService.addToTransaction(
 | 
			
		||||
                chain(onCommit, release),
 | 
			
		||||
                chain(onCancel, release)
 | 
			
		||||
            );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove any callbacks associated with this domain object from the
 | 
			
		||||
     * active transaction.
 | 
			
		||||
     * @param {string} id the identifier for the domain object
 | 
			
		||||
     */
 | 
			
		||||
    TransactionManager.prototype.clearTransactionsFor = function (id) {
 | 
			
		||||
        if (this.isScheduled(id)) {
 | 
			
		||||
            this.clearTransactionFns[id]();
 | 
			
		||||
            this.releaseClearFn(id);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Release the cached "remove from transaction" function that has been
 | 
			
		||||
     * stored in association with this domain object.
 | 
			
		||||
     * @param {string} id the identifier for the domain object
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    TransactionManager.prototype.releaseClearFn = function (id) {
 | 
			
		||||
        delete this.clearTransactionFns[id];
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return TransactionManager;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										138
									
								
								platform/commonUI/edit/src/services/TransactionService.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								platform/commonUI/edit/src/services/TransactionService.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,138 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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(
 | 
			
		||||
    ['./Transaction', './NestedTransaction'],
 | 
			
		||||
    function (Transaction, NestedTransaction) {
 | 
			
		||||
        /**
 | 
			
		||||
         * Implements an application-wide transaction state. Once a
 | 
			
		||||
         * transaction is started, calls to
 | 
			
		||||
         * [PersistenceCapability.persist()]{@link PersistenceCapability#persist}
 | 
			
		||||
         * will be deferred until a subsequent call to
 | 
			
		||||
         * [TransactionService.commit]{@link TransactionService#commit} is made.
 | 
			
		||||
         *
 | 
			
		||||
         * @memberof platform/commonUI/edit/services
 | 
			
		||||
         * @param $q
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function TransactionService($q, $log, cacheService) {
 | 
			
		||||
            this.$q = $q;
 | 
			
		||||
            this.$log = $log;
 | 
			
		||||
            this.cacheService = cacheService;
 | 
			
		||||
            this.transactions = [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Starts a transaction. While a transaction is active all calls to
 | 
			
		||||
         * [PersistenceCapability.persist](@link PersistenceCapability#persist)
 | 
			
		||||
         * will be queued until [commit]{@link #commit} or [cancel]{@link
 | 
			
		||||
         * #cancel} are called
 | 
			
		||||
         */
 | 
			
		||||
        TransactionService.prototype.startTransaction = function () {
 | 
			
		||||
            var transaction = this.isActive()
 | 
			
		||||
                ? new NestedTransaction(this.transactions[0])
 | 
			
		||||
                : new Transaction(this.$log);
 | 
			
		||||
 | 
			
		||||
            this.transactions.push(transaction);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * @returns {boolean} If true, indicates that a transaction is in progress
 | 
			
		||||
         */
 | 
			
		||||
        TransactionService.prototype.isActive = function () {
 | 
			
		||||
            return this.transactions.length > 0;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Adds provided functions to a queue to be called on
 | 
			
		||||
         * [.commit()]{@link #commit} or
 | 
			
		||||
         * [.cancel()]{@link #commit}
 | 
			
		||||
         * @param onCommit A function to call on commit
 | 
			
		||||
         * @param onCancel A function to call on cancel
 | 
			
		||||
         */
 | 
			
		||||
        TransactionService.prototype.addToTransaction = function (onCommit, onCancel) {
 | 
			
		||||
            if (this.isActive()) {
 | 
			
		||||
                return this.activeTransaction().add(onCommit, onCancel);
 | 
			
		||||
            } else {
 | 
			
		||||
                //Log error because this is a programming error if it occurs.
 | 
			
		||||
                this.$log.error("No transaction in progress");
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get the transaction at the top of the stack.
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        TransactionService.prototype.activeTransaction = function () {
 | 
			
		||||
            return this.transactions[this.transactions.length - 1];
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * All persist calls deferred since the beginning of the transaction
 | 
			
		||||
         * will be committed.  If this is the last transaction, clears the
 | 
			
		||||
         * cache.
 | 
			
		||||
         *
 | 
			
		||||
         * @returns {Promise} resolved when all persist operations have
 | 
			
		||||
         * completed. Will reject if any commit operations fail
 | 
			
		||||
         */
 | 
			
		||||
        TransactionService.prototype.commit = function () {
 | 
			
		||||
            var transaction = this.transactions.pop();
 | 
			
		||||
            if (!transaction) {
 | 
			
		||||
                return Promise.reject();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!this.isActive()) {
 | 
			
		||||
                return transaction.commit()
 | 
			
		||||
                    .then(function (r) {
 | 
			
		||||
                        this.cacheService.flush();
 | 
			
		||||
 | 
			
		||||
                        return r;
 | 
			
		||||
                    }.bind(this));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return transaction.commit();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Cancel the current transaction, replacing any dirty objects from
 | 
			
		||||
         * persistence. Not a true rollback, as it cannot be used to undo any
 | 
			
		||||
         * persist calls that were successful in the event one of a batch of
 | 
			
		||||
         * persists failing.
 | 
			
		||||
         *
 | 
			
		||||
         * @returns {*}
 | 
			
		||||
         */
 | 
			
		||||
        TransactionService.prototype.cancel = function () {
 | 
			
		||||
            var transaction = this.transactions.pop();
 | 
			
		||||
 | 
			
		||||
            return transaction ? transaction.cancel() : Promise.reject();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get the size (the number of commit/cancel callbacks) of
 | 
			
		||||
         * the active transaction.
 | 
			
		||||
         * @returns {number} size of the active transaction
 | 
			
		||||
         */
 | 
			
		||||
        TransactionService.prototype.size = function () {
 | 
			
		||||
            return this.isActive() ? this.activeTransaction().size() : 0;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return TransactionService;
 | 
			
		||||
    });
 | 
			
		||||
							
								
								
									
										192
									
								
								platform/commonUI/edit/test/capabilities/EditorCapabilitySpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								platform/commonUI/edit/test/capabilities/EditorCapabilitySpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,192 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/capabilities/EditorCapability"],
 | 
			
		||||
    function (EditorCapability) {
 | 
			
		||||
 | 
			
		||||
        xdescribe("The editor capability", function () {
 | 
			
		||||
            var mockDomainObject,
 | 
			
		||||
                capabilities,
 | 
			
		||||
                mockParentObject,
 | 
			
		||||
                mockTransactionService,
 | 
			
		||||
                mockStatusCapability,
 | 
			
		||||
                mockParentStatus,
 | 
			
		||||
                mockContextCapability,
 | 
			
		||||
                capability;
 | 
			
		||||
 | 
			
		||||
            function fastPromise(val) {
 | 
			
		||||
                return {
 | 
			
		||||
                    then: function (callback) {
 | 
			
		||||
                        return callback(val);
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj(
 | 
			
		||||
                    "domainObject",
 | 
			
		||||
                    ["getId", "getModel", "hasCapability", "getCapability", "useCapability"]
 | 
			
		||||
                );
 | 
			
		||||
                mockParentObject = jasmine.createSpyObj(
 | 
			
		||||
                    "domainObject",
 | 
			
		||||
                    ["getId", "getModel", "hasCapability", "getCapability", "useCapability"]
 | 
			
		||||
                );
 | 
			
		||||
                mockTransactionService = jasmine.createSpyObj(
 | 
			
		||||
                    "transactionService",
 | 
			
		||||
                    [
 | 
			
		||||
                        "startTransaction",
 | 
			
		||||
                        "size",
 | 
			
		||||
                        "commit",
 | 
			
		||||
                        "cancel"
 | 
			
		||||
                    ]
 | 
			
		||||
                );
 | 
			
		||||
                mockTransactionService.commit.and.returnValue(fastPromise());
 | 
			
		||||
                mockTransactionService.cancel.and.returnValue(fastPromise());
 | 
			
		||||
                mockTransactionService.isActive = jasmine.createSpy('isActive');
 | 
			
		||||
 | 
			
		||||
                mockStatusCapability = jasmine.createSpyObj(
 | 
			
		||||
                    "statusCapability",
 | 
			
		||||
                    ["get", "set"]
 | 
			
		||||
                );
 | 
			
		||||
                mockParentStatus = jasmine.createSpyObj(
 | 
			
		||||
                    "statusCapability",
 | 
			
		||||
                    ["get", "set"]
 | 
			
		||||
                );
 | 
			
		||||
                mockContextCapability = jasmine.createSpyObj(
 | 
			
		||||
                    "contextCapability",
 | 
			
		||||
                    ["getParent"]
 | 
			
		||||
                );
 | 
			
		||||
                mockContextCapability.getParent.and.returnValue(mockParentObject);
 | 
			
		||||
 | 
			
		||||
                capabilities = {
 | 
			
		||||
                    context: mockContextCapability,
 | 
			
		||||
                    status: mockStatusCapability
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                mockDomainObject.hasCapability.and.callFake(function (name) {
 | 
			
		||||
                    return capabilities[name] !== undefined;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                mockDomainObject.getCapability.and.callFake(function (name) {
 | 
			
		||||
                    return capabilities[name];
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                mockParentObject.getCapability.and.returnValue(mockParentStatus);
 | 
			
		||||
                mockParentObject.hasCapability.and.returnValue(false);
 | 
			
		||||
 | 
			
		||||
                capability = new EditorCapability(
 | 
			
		||||
                    mockTransactionService,
 | 
			
		||||
                    mockDomainObject
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("starts a transaction when edit is invoked", function () {
 | 
			
		||||
                capability.edit();
 | 
			
		||||
                expect(mockTransactionService.startTransaction).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("sets editing status on object", function () {
 | 
			
		||||
                capability.edit();
 | 
			
		||||
                expect(mockStatusCapability.set).toHaveBeenCalledWith("editing", true);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("uses editing status to determine editing context root", function () {
 | 
			
		||||
                capability.edit();
 | 
			
		||||
                mockStatusCapability.get.and.returnValue(false);
 | 
			
		||||
                expect(capability.isEditContextRoot()).toBe(false);
 | 
			
		||||
                mockStatusCapability.get.and.returnValue(true);
 | 
			
		||||
                expect(capability.isEditContextRoot()).toBe(true);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("inEditingContext returns true if parent object is being"
 | 
			
		||||
                + " edited", function () {
 | 
			
		||||
                mockStatusCapability.get.and.returnValue(false);
 | 
			
		||||
                mockParentStatus.get.and.returnValue(false);
 | 
			
		||||
                expect(capability.inEditContext()).toBe(false);
 | 
			
		||||
                mockParentStatus.get.and.returnValue(true);
 | 
			
		||||
                expect(capability.inEditContext()).toBe(true);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("save", function () {
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    capability.edit();
 | 
			
		||||
                    capability.save();
 | 
			
		||||
                });
 | 
			
		||||
                it("commits the transaction", function () {
 | 
			
		||||
                    expect(mockTransactionService.commit).toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
                it("begins a new transaction", function () {
 | 
			
		||||
                    expect(mockTransactionService.startTransaction).toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("finish", function () {
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    mockTransactionService.isActive.and.returnValue(true);
 | 
			
		||||
                    capability.edit();
 | 
			
		||||
                    capability.finish();
 | 
			
		||||
                });
 | 
			
		||||
                it("cancels the transaction", function () {
 | 
			
		||||
                    expect(mockTransactionService.cancel).toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
                it("resets the edit state", function () {
 | 
			
		||||
                    expect(mockStatusCapability.set).toHaveBeenCalledWith('editing', false);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("finish", function () {
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    mockTransactionService.isActive.and.returnValue(false);
 | 
			
		||||
                    capability.edit();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("does not cancel transaction when transaction is not active", function () {
 | 
			
		||||
                    capability.finish();
 | 
			
		||||
                    expect(mockTransactionService.cancel).not.toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("returns a promise", function () {
 | 
			
		||||
                    expect(capability.finish() instanceof Promise).toBe(true);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("dirty", function () {
 | 
			
		||||
                var model = {};
 | 
			
		||||
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    mockDomainObject.getModel.and.returnValue(model);
 | 
			
		||||
                    capability.edit();
 | 
			
		||||
                    capability.finish();
 | 
			
		||||
                });
 | 
			
		||||
                it("returns true if the object has been modified since it"
 | 
			
		||||
                    + " was last persisted", function () {
 | 
			
		||||
                    mockTransactionService.size.and.returnValue(0);
 | 
			
		||||
                    expect(capability.dirty()).toBe(false);
 | 
			
		||||
                    mockTransactionService.size.and.returnValue(1);
 | 
			
		||||
                    expect(capability.dirty()).toBe(true);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -19,33 +19,36 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
import { BAR_GRAPH_KEY } from './BarGraphConstants';
 | 
			
		||||
import BarGraphViewProvider from './BarGraphViewProvider';
 | 
			
		||||
import BarGraphInspectorViewProvider from './inspector/BarGraphInspectorViewProvider';
 | 
			
		||||
import BarGraphCompositionPolicy from './BarGraphCompositionPolicy';
 | 
			
		||||
 | 
			
		||||
export default function () {
 | 
			
		||||
    return function install(openmct) {
 | 
			
		||||
        openmct.types.addType(BAR_GRAPH_KEY, {
 | 
			
		||||
            key: BAR_GRAPH_KEY,
 | 
			
		||||
            name: "Bar Graph",
 | 
			
		||||
            cssClass: "icon-bar-chart",
 | 
			
		||||
            description: "View data as a bar graph. Can be added to Display Layouts.",
 | 
			
		||||
            creatable: true,
 | 
			
		||||
            initialize: function (domainObject) {
 | 
			
		||||
                domainObject.composition = [];
 | 
			
		||||
                domainObject.configuration = {
 | 
			
		||||
                    barStyles: { series: {} }
 | 
			
		||||
                };
 | 
			
		||||
            },
 | 
			
		||||
            priority: 891
 | 
			
		||||
define(
 | 
			
		||||
    [
 | 
			
		||||
        "../../src/capabilities/TransactionalPersistenceCapability",
 | 
			
		||||
        "../../src/capabilities/TransactionCapabilityDecorator"
 | 
			
		||||
    ],
 | 
			
		||||
    function (TransactionalPersistenceCapability, TransactionCapabilityDecorator) {
 | 
			
		||||
 | 
			
		||||
        describe("The transaction capability decorator", function () {
 | 
			
		||||
            var mockQ,
 | 
			
		||||
                mockTransactionService,
 | 
			
		||||
                mockCapabilityService,
 | 
			
		||||
                provider;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockQ = {};
 | 
			
		||||
                mockTransactionService = {};
 | 
			
		||||
                mockCapabilityService = jasmine.createSpyObj("capabilityService", ["getCapabilities"]);
 | 
			
		||||
                mockCapabilityService.getCapabilities.and.returnValue({
 | 
			
		||||
                    persistence: function () {}
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                provider = new TransactionCapabilityDecorator(mockQ, mockTransactionService, mockCapabilityService);
 | 
			
		||||
 | 
			
		||||
            });
 | 
			
		||||
            it("decorates the persistence capability", function () {
 | 
			
		||||
                var capabilities = provider.getCapabilities();
 | 
			
		||||
                expect(capabilities.persistence({}) instanceof TransactionalPersistenceCapability).toBe(true);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        openmct.objectViews.addProvider(new BarGraphViewProvider(openmct));
 | 
			
		||||
 | 
			
		||||
        openmct.inspectorViews.addProvider(new BarGraphInspectorViewProvider(openmct));
 | 
			
		||||
 | 
			
		||||
        openmct.composition.addPolicy(new BarGraphCompositionPolicy(openmct).allow);
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -0,0 +1,111 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/capabilities/TransactionalPersistenceCapability"
 | 
			
		||||
    ],
 | 
			
		||||
    function (TransactionalPersistenceCapability) {
 | 
			
		||||
 | 
			
		||||
        function fastPromise(val) {
 | 
			
		||||
            return {
 | 
			
		||||
                then: function (callback) {
 | 
			
		||||
                    return callback(val);
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        describe("The transactional persistence decorator", function () {
 | 
			
		||||
            var mockQ,
 | 
			
		||||
                mockTransactionManager,
 | 
			
		||||
                mockPersistence,
 | 
			
		||||
                mockDomainObject,
 | 
			
		||||
                testId,
 | 
			
		||||
                capability;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                testId = "test-id";
 | 
			
		||||
 | 
			
		||||
                mockQ = jasmine.createSpyObj("$q", ["when"]);
 | 
			
		||||
                mockQ.when.and.callFake(function (val) {
 | 
			
		||||
                    return fastPromise(val);
 | 
			
		||||
                });
 | 
			
		||||
                mockTransactionManager = jasmine.createSpyObj(
 | 
			
		||||
                    "transactionService",
 | 
			
		||||
                    ["isActive", "addToTransaction", "clearTransactionsFor"]
 | 
			
		||||
                );
 | 
			
		||||
                mockPersistence = jasmine.createSpyObj(
 | 
			
		||||
                    "persistenceCapability",
 | 
			
		||||
                    ["persist", "refresh", "getSpace"]
 | 
			
		||||
                );
 | 
			
		||||
                mockPersistence.persist.and.returnValue(fastPromise());
 | 
			
		||||
                mockPersistence.refresh.and.returnValue(fastPromise());
 | 
			
		||||
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj(
 | 
			
		||||
                    "domainObject",
 | 
			
		||||
                    ["getModel", "getId"]
 | 
			
		||||
                );
 | 
			
		||||
                mockDomainObject.getModel.and.returnValue({persisted: 1});
 | 
			
		||||
                mockDomainObject.getId.and.returnValue(testId);
 | 
			
		||||
 | 
			
		||||
                capability = new TransactionalPersistenceCapability(
 | 
			
		||||
                    mockQ,
 | 
			
		||||
                    mockTransactionManager,
 | 
			
		||||
                    mockPersistence,
 | 
			
		||||
                    mockDomainObject
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("if no transaction is active, passes through to persistence"
 | 
			
		||||
                + " provider", function () {
 | 
			
		||||
                mockTransactionManager.isActive.and.returnValue(false);
 | 
			
		||||
                capability.persist();
 | 
			
		||||
                expect(mockPersistence.persist).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("if transaction is active, persist and cancel calls are"
 | 
			
		||||
                + " queued", function () {
 | 
			
		||||
                mockTransactionManager.isActive.and.returnValue(true);
 | 
			
		||||
                capability.persist();
 | 
			
		||||
                expect(mockTransactionManager.addToTransaction).toHaveBeenCalled();
 | 
			
		||||
                mockTransactionManager.addToTransaction.calls.mostRecent().args[1]();
 | 
			
		||||
                expect(mockPersistence.persist).toHaveBeenCalled();
 | 
			
		||||
                mockTransactionManager.addToTransaction.calls.mostRecent().args[2]();
 | 
			
		||||
                expect(mockPersistence.refresh).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("wraps getSpace", function () {
 | 
			
		||||
                mockPersistence.getSpace.and.returnValue('foo');
 | 
			
		||||
                expect(capability.getSpace()).toEqual('foo');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("clears transactions and delegates refresh calls", function () {
 | 
			
		||||
                capability.refresh();
 | 
			
		||||
                expect(mockTransactionManager.clearTransactionsFor)
 | 
			
		||||
                    .toHaveBeenCalledWith(testId);
 | 
			
		||||
                expect(mockPersistence.refresh)
 | 
			
		||||
                    .toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -0,0 +1,75 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/services/NestedTransaction"], function (NestedTransaction) {
 | 
			
		||||
    var TRANSACTION_METHODS = ['add', 'commit', 'cancel', 'size'];
 | 
			
		||||
 | 
			
		||||
    describe("A NestedTransaction", function () {
 | 
			
		||||
        var mockTransaction,
 | 
			
		||||
            nestedTransaction;
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            mockTransaction =
 | 
			
		||||
                jasmine.createSpyObj('transaction', TRANSACTION_METHODS);
 | 
			
		||||
            nestedTransaction = new NestedTransaction(mockTransaction);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("exposes a Transaction's interface", function () {
 | 
			
		||||
            TRANSACTION_METHODS.forEach(function (method) {
 | 
			
		||||
                expect(nestedTransaction[method])
 | 
			
		||||
                    .toEqual(jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe("when callbacks are added", function () {
 | 
			
		||||
            var mockCommit,
 | 
			
		||||
                mockCancel;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockCommit = jasmine.createSpy('commit');
 | 
			
		||||
                mockCancel = jasmine.createSpy('cancel');
 | 
			
		||||
                nestedTransaction.add(mockCommit, mockCancel);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("does not interact with its parent transaction", function () {
 | 
			
		||||
                TRANSACTION_METHODS.forEach(function (method) {
 | 
			
		||||
                    expect(mockTransaction[method])
 | 
			
		||||
                        .not.toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("and the transaction is committed", function () {
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    nestedTransaction.commit();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("adds to its parent transaction", function () {
 | 
			
		||||
                    expect(mockTransaction.add).toHaveBeenCalledWith(
 | 
			
		||||
                        jasmine.any(Function),
 | 
			
		||||
                        jasmine.any(Function)
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										141
									
								
								platform/commonUI/edit/test/services/TransactionManagerSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								platform/commonUI/edit/test/services/TransactionManagerSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,141 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/services/TransactionManager"],
 | 
			
		||||
    function (TransactionManager) {
 | 
			
		||||
        describe("TransactionManager", function () {
 | 
			
		||||
            var mockTransactionService,
 | 
			
		||||
                testId,
 | 
			
		||||
                mockOnCommit,
 | 
			
		||||
                mockOnCancel,
 | 
			
		||||
                mockRemoves,
 | 
			
		||||
                mockPromise,
 | 
			
		||||
                manager;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockRemoves = [];
 | 
			
		||||
                mockTransactionService = jasmine.createSpyObj(
 | 
			
		||||
                    "transactionService",
 | 
			
		||||
                    ["addToTransaction", "isActive"]
 | 
			
		||||
                );
 | 
			
		||||
                mockOnCommit = jasmine.createSpy('commit');
 | 
			
		||||
                mockOnCancel = jasmine.createSpy('cancel');
 | 
			
		||||
                testId = 'test-id';
 | 
			
		||||
                mockPromise = jasmine.createSpyObj('promise', ['then']);
 | 
			
		||||
 | 
			
		||||
                mockOnCommit.and.returnValue(mockPromise);
 | 
			
		||||
                mockOnCancel.and.returnValue(mockPromise);
 | 
			
		||||
 | 
			
		||||
                mockTransactionService.addToTransaction.and.callFake(function () {
 | 
			
		||||
                    var mockRemove =
 | 
			
		||||
                        jasmine.createSpy('remove-' + mockRemoves.length);
 | 
			
		||||
                    mockRemoves.push(mockRemove);
 | 
			
		||||
 | 
			
		||||
                    return mockRemove;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                manager = new TransactionManager(mockTransactionService);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("delegates isActive calls", function () {
 | 
			
		||||
                [false, true].forEach(function (state) {
 | 
			
		||||
                    mockTransactionService.isActive.and.returnValue(state);
 | 
			
		||||
                    expect(manager.isActive()).toBe(state);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("when addToTransaction is called", function () {
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    manager.addToTransaction(
 | 
			
		||||
                        testId,
 | 
			
		||||
                        mockOnCommit,
 | 
			
		||||
                        mockOnCancel
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("adds callbacks to the active transaction", function () {
 | 
			
		||||
                    expect(mockTransactionService.addToTransaction)
 | 
			
		||||
                        .toHaveBeenCalledWith(
 | 
			
		||||
                            jasmine.any(Function),
 | 
			
		||||
                            jasmine.any(Function)
 | 
			
		||||
                        );
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("invokes passed-in callbacks from its own callbacks", function () {
 | 
			
		||||
                    expect(mockOnCommit).not.toHaveBeenCalled();
 | 
			
		||||
                    mockTransactionService.addToTransaction
 | 
			
		||||
                        .calls.mostRecent().args[0]();
 | 
			
		||||
                    expect(mockOnCommit).toHaveBeenCalled();
 | 
			
		||||
 | 
			
		||||
                    expect(mockOnCancel).not.toHaveBeenCalled();
 | 
			
		||||
                    mockTransactionService.addToTransaction
 | 
			
		||||
                        .calls.mostRecent().args[1]();
 | 
			
		||||
                    expect(mockOnCancel).toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                describe("Adds callbacks to transaction", function () {
 | 
			
		||||
                    beforeEach(function () {
 | 
			
		||||
                        spyOn(manager, 'clearTransactionsFor');
 | 
			
		||||
                        manager.clearTransactionsFor.and.callThrough();
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    it("and clears pending calls if same object", function () {
 | 
			
		||||
                        manager.addToTransaction(
 | 
			
		||||
                            testId,
 | 
			
		||||
                            jasmine.createSpy(),
 | 
			
		||||
                            jasmine.createSpy()
 | 
			
		||||
                        );
 | 
			
		||||
                        expect(manager.clearTransactionsFor).toHaveBeenCalledWith(testId);
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    it("and does not clear pending calls if different object", function () {
 | 
			
		||||
                        manager.addToTransaction(
 | 
			
		||||
                            'other-id',
 | 
			
		||||
                            jasmine.createSpy(),
 | 
			
		||||
                            jasmine.createSpy()
 | 
			
		||||
                        );
 | 
			
		||||
                        expect(manager.clearTransactionsFor).not.toHaveBeenCalled();
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    afterEach(function () {
 | 
			
		||||
                        expect(mockTransactionService.addToTransaction.calls.count()).toEqual(2);
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("does not remove callbacks from the transaction", function () {
 | 
			
		||||
                    expect(mockRemoves[0]).not.toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                describe("and clearTransactionsFor is subsequently called", function () {
 | 
			
		||||
                    beforeEach(function () {
 | 
			
		||||
                        manager.clearTransactionsFor(testId);
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    it("removes callbacks from the transaction", function () {
 | 
			
		||||
                        expect(mockRemoves[0]).toHaveBeenCalled();
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										139
									
								
								platform/commonUI/edit/test/services/TransactionServiceSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								platform/commonUI/edit/test/services/TransactionServiceSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,139 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/services/TransactionService"],
 | 
			
		||||
    function (TransactionService) {
 | 
			
		||||
 | 
			
		||||
        describe("The Transaction Service", function () {
 | 
			
		||||
            var mockQ,
 | 
			
		||||
                mockLog,
 | 
			
		||||
                mockCacheService,
 | 
			
		||||
                transactionService;
 | 
			
		||||
 | 
			
		||||
            function fastPromise(val) {
 | 
			
		||||
                return {
 | 
			
		||||
                    then: function (callback) {
 | 
			
		||||
                        return fastPromise(callback(val));
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockQ = jasmine.createSpyObj("$q", ["all"]);
 | 
			
		||||
                mockCacheService = jasmine.createSpyObj("cacheService", ["flush"]);
 | 
			
		||||
                mockQ.all.and.returnValue(fastPromise());
 | 
			
		||||
                mockLog = jasmine.createSpyObj("$log", ["error"]);
 | 
			
		||||
                transactionService = new TransactionService(mockQ, mockLog, mockCacheService);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("isActive returns true if a transaction is in progress", function () {
 | 
			
		||||
                expect(transactionService.isActive()).toBe(false);
 | 
			
		||||
                transactionService.startTransaction();
 | 
			
		||||
                expect(transactionService.isActive()).toBe(true);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("addToTransaction queues onCommit and onCancel functions", function () {
 | 
			
		||||
                var onCommit = jasmine.createSpy('onCommit'),
 | 
			
		||||
                    onCancel = jasmine.createSpy('onCancel');
 | 
			
		||||
 | 
			
		||||
                transactionService.startTransaction();
 | 
			
		||||
                transactionService.addToTransaction(onCommit, onCancel);
 | 
			
		||||
                expect(transactionService.size()).toBe(1);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("size function returns size of commit and cancel queues", function () {
 | 
			
		||||
                var onCommit = jasmine.createSpy('onCommit'),
 | 
			
		||||
                    onCancel = jasmine.createSpy('onCancel');
 | 
			
		||||
 | 
			
		||||
                transactionService.startTransaction();
 | 
			
		||||
                transactionService.addToTransaction(onCommit, onCancel);
 | 
			
		||||
                transactionService.addToTransaction(onCommit, onCancel);
 | 
			
		||||
                transactionService.addToTransaction(onCommit, onCancel);
 | 
			
		||||
                expect(transactionService.size()).toBe(3);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("commit", function () {
 | 
			
		||||
                var onCommits;
 | 
			
		||||
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    onCommits = [0, 1, 2].map(function (val) {
 | 
			
		||||
                        return jasmine.createSpy("onCommit" + val);
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    transactionService.startTransaction();
 | 
			
		||||
                    onCommits.forEach(transactionService.addToTransaction.bind(transactionService));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("commit calls all queued commit functions", function () {
 | 
			
		||||
                    expect(transactionService.size()).toBe(3);
 | 
			
		||||
 | 
			
		||||
                    return transactionService.commit().then(() => {
 | 
			
		||||
                        onCommits.forEach(function (spy) {
 | 
			
		||||
                            expect(spy).toHaveBeenCalled();
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("commit resets active state and clears queues", function () {
 | 
			
		||||
                    return transactionService.commit().then(() => {
 | 
			
		||||
                        expect(transactionService.isActive()).toBe(false);
 | 
			
		||||
                        expect(transactionService.size()).toBe(0);
 | 
			
		||||
                        expect(transactionService.size()).toBe(0);
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("cancel", function () {
 | 
			
		||||
                var onCancels;
 | 
			
		||||
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    onCancels = [0, 1, 2].map(function (val) {
 | 
			
		||||
                        return jasmine.createSpy("onCancel" + val);
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    transactionService.startTransaction();
 | 
			
		||||
                    onCancels.forEach(function (onCancel) {
 | 
			
		||||
                        transactionService.addToTransaction(undefined, onCancel);
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("cancel calls all queued cancel functions", function () {
 | 
			
		||||
                    expect(transactionService.size()).toBe(3);
 | 
			
		||||
                    transactionService.cancel();
 | 
			
		||||
                    onCancels.forEach(function (spy) {
 | 
			
		||||
                        expect(spy).toHaveBeenCalled();
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("cancel resets active state and clears queues", function () {
 | 
			
		||||
                    transactionService.cancel();
 | 
			
		||||
                    expect(transactionService.isActive()).toBe(false);
 | 
			
		||||
                    expect(transactionService.size()).toBe(0);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										109
									
								
								platform/commonUI/edit/test/services/TransactionSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								platform/commonUI/edit/test/services/TransactionSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/services/Transaction"],
 | 
			
		||||
    function (Transaction) {
 | 
			
		||||
 | 
			
		||||
        describe("A Transaction", function () {
 | 
			
		||||
            var mockLog,
 | 
			
		||||
                transaction;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockLog = jasmine.createSpyObj(
 | 
			
		||||
                    '$log',
 | 
			
		||||
                    ['warn', 'info', 'error', 'debug']
 | 
			
		||||
                );
 | 
			
		||||
                transaction = new Transaction(mockLog);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("initially has a size of zero", function () {
 | 
			
		||||
                expect(transaction.size()).toEqual(0);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("when callbacks are added", function () {
 | 
			
		||||
                var mockCommit,
 | 
			
		||||
                    mockCancel,
 | 
			
		||||
                    remove;
 | 
			
		||||
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    mockCommit = jasmine.createSpy('commit');
 | 
			
		||||
                    mockCancel = jasmine.createSpy('cancel');
 | 
			
		||||
                    remove = transaction.add(mockCommit, mockCancel);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("reports a new size", function () {
 | 
			
		||||
                    expect(transaction.size()).toEqual(1);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("returns a function to remove those callbacks", function () {
 | 
			
		||||
                    expect(remove).toEqual(jasmine.any(Function));
 | 
			
		||||
                    remove();
 | 
			
		||||
                    expect(transaction.size()).toEqual(0);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                describe("and the transaction is committed", function () {
 | 
			
		||||
                    beforeEach(function () {
 | 
			
		||||
                        transaction.commit();
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    it("triggers the commit callback", function () {
 | 
			
		||||
                        expect(mockCommit).toHaveBeenCalled();
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    it("does not trigger the cancel callback", function () {
 | 
			
		||||
                        expect(mockCancel).not.toHaveBeenCalled();
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                describe("and the transaction is cancelled", function () {
 | 
			
		||||
                    beforeEach(function () {
 | 
			
		||||
                        transaction.cancel();
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    it("triggers the cancel callback", function () {
 | 
			
		||||
                        expect(mockCancel).toHaveBeenCalled();
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    it("does not trigger the commit callback", function () {
 | 
			
		||||
                        expect(mockCommit).not.toHaveBeenCalled();
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                describe("and an exception is encountered during commit", function () {
 | 
			
		||||
                    beforeEach(function () {
 | 
			
		||||
                        mockCommit.and.callFake(function () {
 | 
			
		||||
                            throw new Error("test error");
 | 
			
		||||
                        });
 | 
			
		||||
                        transaction.commit();
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    it("logs an error", function () {
 | 
			
		||||
                        expect(mockLog.error).toHaveBeenCalled();
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
@@ -25,14 +25,15 @@ define([
 | 
			
		||||
], function (
 | 
			
		||||
    moment
 | 
			
		||||
) {
 | 
			
		||||
    const DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS";
 | 
			
		||||
    const DATE_FORMATS = [
 | 
			
		||||
        DATE_FORMAT,
 | 
			
		||||
        DATE_FORMAT + "Z",
 | 
			
		||||
        "YYYY-MM-DD HH:mm:ss",
 | 
			
		||||
        "YYYY-MM-DD HH:mm",
 | 
			
		||||
        "YYYY-MM-DD"
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
 | 
			
		||||
        DATE_FORMATS = [
 | 
			
		||||
            DATE_FORMAT,
 | 
			
		||||
            DATE_FORMAT + "Z",
 | 
			
		||||
            "YYYY-MM-DD HH:mm:ss",
 | 
			
		||||
            "YYYY-MM-DD HH:mm",
 | 
			
		||||
            "YYYY-MM-DD"
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @typedef Scale
 | 
			
		||||
@@ -52,27 +53,15 @@ define([
 | 
			
		||||
        this.key = "utc";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {string} formatString
 | 
			
		||||
     * @returns the value of formatString if the value is a string type and exists in the DATE_FORMATS array; otherwise the DATE_FORMAT value.
 | 
			
		||||
     */
 | 
			
		||||
    function validateFormatString(formatString) {
 | 
			
		||||
        return typeof formatString === 'string' && DATE_FORMATS.includes(formatString) ? formatString : DATE_FORMAT;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {number} value The value to format.
 | 
			
		||||
     * @param {string} formatString The string format to format. Default "YYYY-MM-DD HH:mm:ss.SSS" + "Z"
 | 
			
		||||
     * @returns {string} the formatted date(s) according to the proper parameter of formatString or the default value of "YYYY-MM-DD HH:mm:ss.SSS" + "Z".
 | 
			
		||||
     * If multiple values were requested, then an array of
 | 
			
		||||
     * @returns {string} the formatted date(s). If multiple values were requested, then an array of
 | 
			
		||||
     * formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
 | 
			
		||||
     * in the array.
 | 
			
		||||
     */
 | 
			
		||||
    UTCTimeFormat.prototype.format = function (value, formatString) {
 | 
			
		||||
    UTCTimeFormat.prototype.format = function (value) {
 | 
			
		||||
        if (value !== undefined) {
 | 
			
		||||
            const format = validateFormatString(formatString);
 | 
			
		||||
 | 
			
		||||
            return moment.utc(value).format(format) + (formatString ? '' : 'Z');
 | 
			
		||||
            return moment.utc(value).format(DATE_FORMAT) + "Z";
 | 
			
		||||
        } else {
 | 
			
		||||
            return value;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ define([
 | 
			
		||||
    "./src/models/ModelCacheService",
 | 
			
		||||
    "./src/models/PersistedModelProvider",
 | 
			
		||||
    "./src/models/CachingModelDecorator",
 | 
			
		||||
    "./src/models/MissingModelDecorator",
 | 
			
		||||
    "./src/types/TypeProvider",
 | 
			
		||||
    "./src/actions/ActionProvider",
 | 
			
		||||
    "./src/actions/ActionAggregator",
 | 
			
		||||
@@ -44,6 +45,7 @@ define([
 | 
			
		||||
    "./src/capabilities/MutationCapability",
 | 
			
		||||
    "./src/capabilities/DelegationCapability",
 | 
			
		||||
    "./src/capabilities/InstantiationCapability",
 | 
			
		||||
    "./src/runs/TransactingMutationListener",
 | 
			
		||||
    "./src/services/Now",
 | 
			
		||||
    "./src/services/Throttle",
 | 
			
		||||
    "./src/services/Topic",
 | 
			
		||||
@@ -56,6 +58,7 @@ define([
 | 
			
		||||
    ModelCacheService,
 | 
			
		||||
    PersistedModelProvider,
 | 
			
		||||
    CachingModelDecorator,
 | 
			
		||||
    MissingModelDecorator,
 | 
			
		||||
    TypeProvider,
 | 
			
		||||
    ActionProvider,
 | 
			
		||||
    ActionAggregator,
 | 
			
		||||
@@ -72,6 +75,7 @@ define([
 | 
			
		||||
    MutationCapability,
 | 
			
		||||
    DelegationCapability,
 | 
			
		||||
    InstantiationCapability,
 | 
			
		||||
    TransactingMutationListener,
 | 
			
		||||
    Now,
 | 
			
		||||
    Throttle,
 | 
			
		||||
    Topic,
 | 
			
		||||
@@ -146,6 +150,12 @@ define([
 | 
			
		||||
                            "cacheService"
 | 
			
		||||
                        ]
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "provides": "modelService",
 | 
			
		||||
                        "type": "decorator",
 | 
			
		||||
                        "priority": "fallback",
 | 
			
		||||
                        "implementation": MissingModelDecorator
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "provides": "typeService",
 | 
			
		||||
                        "type": "provider",
 | 
			
		||||
@@ -353,6 +363,12 @@ define([
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "runs": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "implementation": TransactingMutationListener,
 | 
			
		||||
                        "depends": ["topic", "transactionService", "cacheService"]
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "constants": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "PERSISTENCE_SPACE",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										62
									
								
								platform/core/src/models/MissingModelDecorator.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								platform/core/src/models/MissingModelDecorator.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 () {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Adds placeholder domain object models for any models which
 | 
			
		||||
         * fail to load from the underlying model service.
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @memberof platform/core
 | 
			
		||||
         * @param {ModelService} modelService this service to decorate
 | 
			
		||||
         * @implements {ModelService}
 | 
			
		||||
         */
 | 
			
		||||
        function MissingModelDecorator(modelService) {
 | 
			
		||||
            this.modelService = modelService;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function missingModel(id) {
 | 
			
		||||
            return {
 | 
			
		||||
                type: "unknown",
 | 
			
		||||
                name: "Missing: " + id
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        MissingModelDecorator.prototype.getModels = function (ids) {
 | 
			
		||||
            function addMissingModels(models) {
 | 
			
		||||
                var result = {};
 | 
			
		||||
                ids.forEach(function (id) {
 | 
			
		||||
                    result[id] = models[id] || missingModel(id);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return this.modelService.getModels(ids).then(addMissingModels);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return MissingModelDecorator;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										55
									
								
								platform/core/src/runs/TransactingMutationListener.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								platform/core/src/runs/TransactingMutationListener.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define([], function () {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Listens for mutation on domain objects and triggers persistence when
 | 
			
		||||
     * it occurs.
 | 
			
		||||
     * @param {Topic} topic the `topic` service; used to listen for mutation
 | 
			
		||||
     * @memberof platform/core
 | 
			
		||||
     */
 | 
			
		||||
    function TransactingMutationListener(
 | 
			
		||||
        topic,
 | 
			
		||||
        transactionService,
 | 
			
		||||
        cacheService
 | 
			
		||||
    ) {
 | 
			
		||||
 | 
			
		||||
        function hasChanged(domainObject) {
 | 
			
		||||
            var model = domainObject.getModel();
 | 
			
		||||
 | 
			
		||||
            return model.persisted === undefined || model.modified > model.persisted;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var mutationTopic = topic('mutation');
 | 
			
		||||
        mutationTopic.listen(function (domainObject) {
 | 
			
		||||
            var persistence = domainObject.getCapability('persistence');
 | 
			
		||||
            cacheService.put(domainObject.getId(), domainObject.getModel());
 | 
			
		||||
 | 
			
		||||
            if (hasChanged(domainObject)) {
 | 
			
		||||
                persistence.persist();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return TransactingMutationListener;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										86
									
								
								platform/core/test/models/MissingModelDecoratorSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								platform/core/test/models/MissingModelDecoratorSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/models/MissingModelDecorator"],
 | 
			
		||||
    function (MissingModelDecorator) {
 | 
			
		||||
 | 
			
		||||
        describe("The missing model decorator", function () {
 | 
			
		||||
            var mockModelService,
 | 
			
		||||
                testModels,
 | 
			
		||||
                decorator;
 | 
			
		||||
 | 
			
		||||
            function asPromise(value) {
 | 
			
		||||
                return (value || {}).then ? value : {
 | 
			
		||||
                    then: function (callback) {
 | 
			
		||||
                        return asPromise(callback(value));
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockModelService = jasmine.createSpyObj(
 | 
			
		||||
                    "modelService",
 | 
			
		||||
                    ["getModels"]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                testModels = {
 | 
			
		||||
                    testId: { someKey: "some value" }
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                mockModelService.getModels.and.returnValue(asPromise(testModels));
 | 
			
		||||
 | 
			
		||||
                decorator = new MissingModelDecorator(mockModelService);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("delegates to the wrapped model service", function () {
 | 
			
		||||
                decorator.getModels(['a', 'b', 'c']);
 | 
			
		||||
                expect(mockModelService.getModels)
 | 
			
		||||
                    .toHaveBeenCalledWith(['a', 'b', 'c']);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("provides models for any IDs which are missing", function () {
 | 
			
		||||
                var models;
 | 
			
		||||
                decorator.getModels(['testId', 'otherId'])
 | 
			
		||||
                    .then(function (m) {
 | 
			
		||||
                        models = m;
 | 
			
		||||
                    });
 | 
			
		||||
                expect(models.otherId).toBeDefined();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("does not overwrite existing models", function () {
 | 
			
		||||
                var models;
 | 
			
		||||
                decorator.getModels(['testId', 'otherId'])
 | 
			
		||||
                    .then(function (m) {
 | 
			
		||||
                        models = m;
 | 
			
		||||
                    });
 | 
			
		||||
                expect(models.testId).toEqual({ someKey: "some value" });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("does not modify the wrapped service's response", function () {
 | 
			
		||||
                decorator.getModels(['testId', 'otherId']);
 | 
			
		||||
                expect(testModels.otherId).toBeUndefined();
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										112
									
								
								platform/core/test/runs/TransactingMutationListenerSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								platform/core/test/runs/TransactingMutationListenerSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,112 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/runs/TransactingMutationListener"],
 | 
			
		||||
    function (TransactingMutationListener) {
 | 
			
		||||
 | 
			
		||||
        describe("TransactingMutationListener", function () {
 | 
			
		||||
            var mockTopic,
 | 
			
		||||
                mockMutationTopic,
 | 
			
		||||
                mockCacheService,
 | 
			
		||||
                mockTransactionService,
 | 
			
		||||
                mockDomainObject,
 | 
			
		||||
                mockModel,
 | 
			
		||||
                mockPersistence;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockTopic = jasmine.createSpy('topic');
 | 
			
		||||
                mockMutationTopic =
 | 
			
		||||
                    jasmine.createSpyObj('mutation', ['listen']);
 | 
			
		||||
                mockCacheService =
 | 
			
		||||
                    jasmine.createSpyObj('cacheService', [
 | 
			
		||||
                        'put'
 | 
			
		||||
                    ]);
 | 
			
		||||
                mockTransactionService =
 | 
			
		||||
                    jasmine.createSpyObj('transactionService', [
 | 
			
		||||
                        'isActive',
 | 
			
		||||
                        'startTransaction',
 | 
			
		||||
                        'commit'
 | 
			
		||||
                    ]);
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj(
 | 
			
		||||
                    'domainObject',
 | 
			
		||||
                    ['getId', 'getCapability', 'getModel']
 | 
			
		||||
                );
 | 
			
		||||
                mockPersistence = jasmine.createSpyObj(
 | 
			
		||||
                    'persistence',
 | 
			
		||||
                    ['persist', 'refresh', 'persisted']
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockTopic.and.callFake(function (t) {
 | 
			
		||||
                    expect(t).toBe('mutation');
 | 
			
		||||
 | 
			
		||||
                    return mockMutationTopic;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                mockDomainObject.getId.and.returnValue('mockId');
 | 
			
		||||
                mockDomainObject.getCapability.and.callFake(function (c) {
 | 
			
		||||
                    expect(c).toBe('persistence');
 | 
			
		||||
 | 
			
		||||
                    return mockPersistence;
 | 
			
		||||
                });
 | 
			
		||||
                mockModel = {};
 | 
			
		||||
                mockDomainObject.getModel.and.returnValue(mockModel);
 | 
			
		||||
 | 
			
		||||
                mockPersistence.persisted.and.returnValue(true);
 | 
			
		||||
 | 
			
		||||
                return new TransactingMutationListener(
 | 
			
		||||
                    mockTopic,
 | 
			
		||||
                    mockTransactionService,
 | 
			
		||||
                    mockCacheService
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("listens for mutation", function () {
 | 
			
		||||
                expect(mockMutationTopic.listen)
 | 
			
		||||
                    .toHaveBeenCalledWith(jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("calls persist if the model has changed", function () {
 | 
			
		||||
                mockModel.persisted = Date.now();
 | 
			
		||||
 | 
			
		||||
                //Mark the model dirty by setting the mutated date later than the last persisted date.
 | 
			
		||||
                mockModel.modified = mockModel.persisted + 1;
 | 
			
		||||
 | 
			
		||||
                mockMutationTopic.listen.calls.mostRecent()
 | 
			
		||||
                    .args[0](mockDomainObject);
 | 
			
		||||
 | 
			
		||||
                expect(mockPersistence.persist).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("does not call persist if the model has not changed", function () {
 | 
			
		||||
                mockModel.persisted = Date.now();
 | 
			
		||||
 | 
			
		||||
                mockModel.modified = mockModel.persisted;
 | 
			
		||||
 | 
			
		||||
                mockMutationTopic.listen.calls.mostRecent()
 | 
			
		||||
                    .args[0](mockDomainObject);
 | 
			
		||||
 | 
			
		||||
                expect(mockPersistence.persist).not.toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										209
									
								
								platform/features/clock/bundle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								platform/features/clock/bundle.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,209 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/services/TickerService",
 | 
			
		||||
    "./src/services/TimerService",
 | 
			
		||||
    "./src/controllers/TimerController",
 | 
			
		||||
    "./src/controllers/RefreshingController",
 | 
			
		||||
    "./src/actions/StartTimerAction",
 | 
			
		||||
    "./src/actions/RestartTimerAction",
 | 
			
		||||
    "./src/actions/StopTimerAction",
 | 
			
		||||
    "./src/actions/PauseTimerAction",
 | 
			
		||||
    "./res/templates/timer.html"
 | 
			
		||||
], function (
 | 
			
		||||
    TickerService,
 | 
			
		||||
    TimerService,
 | 
			
		||||
    TimerController,
 | 
			
		||||
    RefreshingController,
 | 
			
		||||
    StartTimerAction,
 | 
			
		||||
    RestartTimerAction,
 | 
			
		||||
    StopTimerAction,
 | 
			
		||||
    PauseTimerAction,
 | 
			
		||||
    timerTemplate
 | 
			
		||||
) {
 | 
			
		||||
    return {
 | 
			
		||||
        name: "platform/features/clock",
 | 
			
		||||
        definition: {
 | 
			
		||||
            "name": "Clocks/Timers",
 | 
			
		||||
            "descriptions": "Domain objects for displaying current & relative times.",
 | 
			
		||||
            "configuration": {
 | 
			
		||||
                "paths": {
 | 
			
		||||
                    "moment-duration-format": "moment-duration-format"
 | 
			
		||||
                },
 | 
			
		||||
                "shim": {
 | 
			
		||||
                    "moment-duration-format": {
 | 
			
		||||
                        "deps": [
 | 
			
		||||
                            "moment"
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "extensions": {
 | 
			
		||||
                "constants": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "CLOCK_INDICATOR_FORMAT",
 | 
			
		||||
                        "value": "YYYY/MM/DD HH:mm:ss"
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "services": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "tickerService",
 | 
			
		||||
                        "implementation": TickerService,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "$timeout",
 | 
			
		||||
                            "now"
 | 
			
		||||
                        ]
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "timerService",
 | 
			
		||||
                        "implementation": TimerService,
 | 
			
		||||
                        "depends": ["openmct"]
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "controllers": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "TimerController",
 | 
			
		||||
                        "implementation": TimerController,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "$scope",
 | 
			
		||||
                            "$window",
 | 
			
		||||
                            "now"
 | 
			
		||||
                        ]
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "RefreshingController",
 | 
			
		||||
                        "implementation": RefreshingController,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "$scope",
 | 
			
		||||
                            "tickerService"
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "views": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "timer",
 | 
			
		||||
                        "type": "timer",
 | 
			
		||||
                        "editable": false,
 | 
			
		||||
                        "template": timerTemplate
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "actions": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "timer.start",
 | 
			
		||||
                        "implementation": StartTimerAction,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "now"
 | 
			
		||||
                        ],
 | 
			
		||||
                        "category": "contextual",
 | 
			
		||||
                        "name": "Start",
 | 
			
		||||
                        "cssClass": "icon-play",
 | 
			
		||||
                        "priority": "preferred"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "timer.pause",
 | 
			
		||||
                        "implementation": PauseTimerAction,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "now"
 | 
			
		||||
                        ],
 | 
			
		||||
                        "category": "contextual",
 | 
			
		||||
                        "name": "Pause",
 | 
			
		||||
                        "cssClass": "icon-pause",
 | 
			
		||||
                        "priority": "preferred"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "timer.restart",
 | 
			
		||||
                        "implementation": RestartTimerAction,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "now"
 | 
			
		||||
                        ],
 | 
			
		||||
                        "category": "contextual",
 | 
			
		||||
                        "name": "Restart at 0",
 | 
			
		||||
                        "cssClass": "icon-refresh",
 | 
			
		||||
                        "priority": "preferred"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "timer.stop",
 | 
			
		||||
                        "implementation": StopTimerAction,
 | 
			
		||||
                        "depends": [
 | 
			
		||||
                            "now"
 | 
			
		||||
                        ],
 | 
			
		||||
                        "category": "contextual",
 | 
			
		||||
                        "name": "Stop",
 | 
			
		||||
                        "cssClass": "icon-box-round-corners",
 | 
			
		||||
                        "priority": "preferred"
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "types": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "timer",
 | 
			
		||||
                        "name": "Timer",
 | 
			
		||||
                        "cssClass": "icon-timer",
 | 
			
		||||
                        "description": "A timer that counts up or down to a datetime. Timers can be started, stopped and reset whenever needed, and support a variety of display formats. Each Timer displays the same value to all users. Timers can be added to Display Layouts.",
 | 
			
		||||
                        "priority": 100,
 | 
			
		||||
                        "features": [
 | 
			
		||||
                            "creation"
 | 
			
		||||
                        ],
 | 
			
		||||
                        "properties": [
 | 
			
		||||
                            {
 | 
			
		||||
                                "key": "timestamp",
 | 
			
		||||
                                "control": "datetime",
 | 
			
		||||
                                "name": "Target"
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                "key": "timerFormat",
 | 
			
		||||
                                "control": "select",
 | 
			
		||||
                                "name": "Display Format",
 | 
			
		||||
                                "options": [
 | 
			
		||||
                                    {
 | 
			
		||||
                                        "value": "long",
 | 
			
		||||
                                        "name": "DDD hh:mm:ss"
 | 
			
		||||
                                    },
 | 
			
		||||
                                    {
 | 
			
		||||
                                        "value": "short",
 | 
			
		||||
                                        "name": "hh:mm:ss"
 | 
			
		||||
                                    }
 | 
			
		||||
                                ]
 | 
			
		||||
                            }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "model": {
 | 
			
		||||
                            "timerFormat": "DDD hh:mm:ss"
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "runs": [],
 | 
			
		||||
                "licenses": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "name": "moment-duration-format",
 | 
			
		||||
                        "version": "1.3.0",
 | 
			
		||||
                        "author": "John Madhavan-Reese",
 | 
			
		||||
                        "description": "Duration parsing/formatting",
 | 
			
		||||
                        "website": "https://github.com/jsmreese/moment-duration-format",
 | 
			
		||||
                        "copyright": "Copyright 2014 John Madhavan-Reese",
 | 
			
		||||
                        "license": "license-mit",
 | 
			
		||||
                        "link": "https://github.com/jsmreese/moment-duration-format/blob/master/LICENSE"
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										37
									
								
								platform/features/clock/res/templates/timer.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								platform/features/clock/res/templates/timer.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
<!--
 | 
			
		||||
 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.
 | 
			
		||||
-->
 | 
			
		||||
<div class="c-timer u-style-receiver js-style-receiver is-{{timer.timerState}}" ng-controller="TimerController as timer">
 | 
			
		||||
    <div class="c-timer__controls">
 | 
			
		||||
        <button ng-click="timer.clickStopButton()"
 | 
			
		||||
                ng-hide="timer.timerState == 'stopped'"
 | 
			
		||||
                title="Reset"
 | 
			
		||||
                class="c-timer__ctrl-reset c-icon-button c-icon-button--major icon-reset"></button>
 | 
			
		||||
        <button ng-click="timer.clickButton()"
 | 
			
		||||
                title="{{timer.buttonText()}}"
 | 
			
		||||
                class="c-timer__ctrl-pause-play c-icon-button c-icon-button--major {{timer.buttonCssClass()}}"></button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="c-timer__direction {{timer.signClass()}}"
 | 
			
		||||
        ng-hide="!timer.signClass()"></div>
 | 
			
		||||
	<div class="c-timer__value">{{timer.text() || "--:--:--"}}
 | 
			
		||||
	</div>
 | 
			
		||||
	<span class="c-timer__ng-controller u-contents" ng-controller="RefreshingController"></span>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										70
									
								
								platform/features/clock/src/actions/PauseTimerAction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								platform/features/clock/src/actions/PauseTimerAction.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 () {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Implements the "Pause" action for timers.
 | 
			
		||||
         *
 | 
			
		||||
         * Sets the reference pausedTime in a timer to the current
 | 
			
		||||
         * time, such that it stops counting up.
 | 
			
		||||
         *
 | 
			
		||||
         * @implements {Action}
 | 
			
		||||
         * @memberof platform/features/clock
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @param {Function} now a function which returns the current
 | 
			
		||||
         *        time (typically wrapping `Date.now`)
 | 
			
		||||
         * @param {ActionContext} context the context for this action
 | 
			
		||||
         */
 | 
			
		||||
        function PauseTimerAction(now, context) {
 | 
			
		||||
            this.domainObject = context.domainObject;
 | 
			
		||||
            this.now = now;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        PauseTimerAction.appliesTo = function (context) {
 | 
			
		||||
            var model =
 | 
			
		||||
                (context.domainObject && context.domainObject.getModel())
 | 
			
		||||
                || {};
 | 
			
		||||
 | 
			
		||||
            // We show this variant for timers which have
 | 
			
		||||
            // a target time, or is in a playing state.
 | 
			
		||||
            return model.type === 'timer'
 | 
			
		||||
                    && model.timerState === 'started';
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        PauseTimerAction.prototype.perform = function () {
 | 
			
		||||
            var domainObject = this.domainObject,
 | 
			
		||||
                now = this.now;
 | 
			
		||||
 | 
			
		||||
            function updateModel(model) {
 | 
			
		||||
                model.timerState = 'paused';
 | 
			
		||||
                model.pausedTime = now();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return domainObject.useCapability('mutation', updateModel);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return PauseTimerAction;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										70
									
								
								platform/features/clock/src/actions/RestartTimerAction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								platform/features/clock/src/actions/RestartTimerAction.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 () {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Implements the "Restart at 0" action.
 | 
			
		||||
         *
 | 
			
		||||
         * Behaves the same as (and delegates functionality to)
 | 
			
		||||
         * the "Start" action.
 | 
			
		||||
         *
 | 
			
		||||
         * @implements {Action}
 | 
			
		||||
         * @memberof platform/features/clock
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @param {Function} now a function which returns the current
 | 
			
		||||
         *        time (typically wrapping `Date.now`)
 | 
			
		||||
         * @param {ActionContext} context the context for this action
 | 
			
		||||
         */
 | 
			
		||||
        function RestartTimerAction(now, context) {
 | 
			
		||||
            this.domainObject = context.domainObject;
 | 
			
		||||
            this.now = now;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        RestartTimerAction.appliesTo = function (context) {
 | 
			
		||||
            var model =
 | 
			
		||||
                (context.domainObject && context.domainObject.getModel())
 | 
			
		||||
                || {};
 | 
			
		||||
 | 
			
		||||
            // We show this variant for timers which already have a target time.
 | 
			
		||||
            return model.type === 'timer'
 | 
			
		||||
                && model.timerState !== 'stopped';
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        RestartTimerAction.prototype.perform = function () {
 | 
			
		||||
            var domainObject = this.domainObject,
 | 
			
		||||
                now = this.now;
 | 
			
		||||
 | 
			
		||||
            function updateModel(model) {
 | 
			
		||||
                model.timestamp = now();
 | 
			
		||||
                model.timerState = 'started';
 | 
			
		||||
                model.pausedTime = undefined;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return domainObject.useCapability('mutation', updateModel);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return RestartTimerAction;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										78
									
								
								platform/features/clock/src/actions/StartTimerAction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								platform/features/clock/src/actions/StartTimerAction.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 () {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Implements the "Start" action for timers.
 | 
			
		||||
         *
 | 
			
		||||
         * Sets the reference timestamp in a timer to the current
 | 
			
		||||
         * time, such that it begins counting up.
 | 
			
		||||
         *
 | 
			
		||||
         * @implements {Action}
 | 
			
		||||
         * @memberof platform/features/clock
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @param {Function} now a function which returns the current
 | 
			
		||||
         *        time (typically wrapping `Date.now`)
 | 
			
		||||
         * @param {ActionContext} context the context for this action
 | 
			
		||||
         */
 | 
			
		||||
        function StartTimerAction(now, context) {
 | 
			
		||||
            this.domainObject = context.domainObject;
 | 
			
		||||
            this.now = now;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        StartTimerAction.appliesTo = function (context) {
 | 
			
		||||
            var model =
 | 
			
		||||
                (context.domainObject && context.domainObject.getModel())
 | 
			
		||||
                || {};
 | 
			
		||||
 | 
			
		||||
            // We show this variant for timers which do not yet have
 | 
			
		||||
            // a target time.
 | 
			
		||||
            return model.type === 'timer'
 | 
			
		||||
                    && model.timerState !== 'started';
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        StartTimerAction.prototype.perform = function () {
 | 
			
		||||
            var domainObject = this.domainObject,
 | 
			
		||||
                now = this.now;
 | 
			
		||||
 | 
			
		||||
            function updateModel(model) {
 | 
			
		||||
                //if we are resuming
 | 
			
		||||
                if (model.pausedTime) {
 | 
			
		||||
                    var timeShift = now() - model.pausedTime;
 | 
			
		||||
                    model.timestamp = model.timestamp + timeShift;
 | 
			
		||||
                } else {
 | 
			
		||||
                    model.timestamp = now();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                model.timerState = 'started';
 | 
			
		||||
                model.pausedTime = undefined;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return domainObject.useCapability('mutation', updateModel);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return StartTimerAction;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										70
									
								
								platform/features/clock/src/actions/StopTimerAction.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								platform/features/clock/src/actions/StopTimerAction.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 () {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Implements the "Stop" action for timers.
 | 
			
		||||
         *
 | 
			
		||||
         * Sets the reference timestamp in a timer undefined,
 | 
			
		||||
         * such that it is reset and makes no movements.
 | 
			
		||||
         *
 | 
			
		||||
         * @implements {Action}
 | 
			
		||||
         * @memberof platform/features/clock
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @param {Function} now a function which returns the current
 | 
			
		||||
         *        time (typically wrapping `Date.now`)
 | 
			
		||||
         * @param {ActionContext} context the context for this action
 | 
			
		||||
         */
 | 
			
		||||
        function StopTimerAction(now, context) {
 | 
			
		||||
            this.domainObject = context.domainObject;
 | 
			
		||||
            this.now = now;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        StopTimerAction.appliesTo = function (context) {
 | 
			
		||||
            var model =
 | 
			
		||||
                (context.domainObject && context.domainObject.getModel())
 | 
			
		||||
                || {};
 | 
			
		||||
 | 
			
		||||
            // We show this variant for timers which do not yet have
 | 
			
		||||
            // a target time.
 | 
			
		||||
            return model.type === 'timer'
 | 
			
		||||
                    && model.timerState !== 'stopped';
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        StopTimerAction.prototype.perform = function () {
 | 
			
		||||
            var domainObject = this.domainObject;
 | 
			
		||||
 | 
			
		||||
            function updateModel(model) {
 | 
			
		||||
                model.timestamp = undefined;
 | 
			
		||||
                model.timerState = 'stopped';
 | 
			
		||||
                model.pausedTime = undefined;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return domainObject.useCapability('mutation', updateModel);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return StopTimerAction;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 () {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Continually refreshes the represented domain object.
 | 
			
		||||
         *
 | 
			
		||||
         * This is a short-term workaround to assure Timer views stay
 | 
			
		||||
         * up-to-date; should be replaced by a global auto-refresh.
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @memberof platform/features/clock
 | 
			
		||||
         * @param {angular.Scope} $scope the Angular scope
 | 
			
		||||
         * @param {platform/features/clock.TickerService} tickerService
 | 
			
		||||
         *        a service used to align behavior with clock ticks
 | 
			
		||||
         */
 | 
			
		||||
        function RefreshingController($scope, tickerService) {
 | 
			
		||||
            var unlisten;
 | 
			
		||||
 | 
			
		||||
            function triggerRefresh() {
 | 
			
		||||
                var persistence = $scope.domainObject
 | 
			
		||||
                    && $scope.domainObject.getCapability('persistence');
 | 
			
		||||
 | 
			
		||||
                return persistence && persistence.refresh();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            unlisten = tickerService.listen(triggerRefresh);
 | 
			
		||||
            $scope.$on('$destroy', unlisten);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return RefreshingController;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										239
									
								
								platform/features/clock/src/controllers/TimerController.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								platform/features/clock/src/controllers/TimerController.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,239 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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(
 | 
			
		||||
    ['./TimerFormatter'],
 | 
			
		||||
    function (TimerFormatter) {
 | 
			
		||||
 | 
			
		||||
        var FORMATTER = new TimerFormatter();
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Controller for views of a Timer domain object.
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @memberof platform/features/clock
 | 
			
		||||
         * @param {angular.Scope} $scope the Angular scope
 | 
			
		||||
         * @param $window Angular-provided window object
 | 
			
		||||
         * @param {Function} now a function which returns the current
 | 
			
		||||
         *        time (typically wrapping `Date.now`)
 | 
			
		||||
         */
 | 
			
		||||
        function TimerController($scope, $window, now) {
 | 
			
		||||
            var formatter,
 | 
			
		||||
                active = true,
 | 
			
		||||
                relativeTimestamp,
 | 
			
		||||
                lastTimestamp,
 | 
			
		||||
                relativeTimerState,
 | 
			
		||||
                self = this;
 | 
			
		||||
 | 
			
		||||
            function update() {
 | 
			
		||||
                var timeDelta = lastTimestamp - relativeTimestamp;
 | 
			
		||||
 | 
			
		||||
                if (formatter && !isNaN(timeDelta)) {
 | 
			
		||||
                    self.textValue = formatter(timeDelta);
 | 
			
		||||
                    self.signValue = timeDelta < 0 ? "-"
 | 
			
		||||
                        : timeDelta >= 1000 ? "+" : "";
 | 
			
		||||
                    self.signCssClass = timeDelta < 0 ? "icon-minus"
 | 
			
		||||
                        : timeDelta >= 1000 ? "icon-plus" : "";
 | 
			
		||||
                } else {
 | 
			
		||||
                    self.textValue = "";
 | 
			
		||||
                    self.signValue = "";
 | 
			
		||||
                    self.signCssClass = "";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateFormat(key) {
 | 
			
		||||
                formatter = FORMATTER[key] || FORMATTER.long;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateTimestamp(timestamp) {
 | 
			
		||||
                relativeTimestamp = timestamp;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateTimerState(timerState) {
 | 
			
		||||
                self.timerState = relativeTimerState = timerState;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateActions(actionCapability, actionKey) {
 | 
			
		||||
                self.relevantAction = actionCapability
 | 
			
		||||
                    && actionCapability.getActions(actionKey)[0];
 | 
			
		||||
 | 
			
		||||
                self.stopAction = relativeTimerState !== 'stopped'
 | 
			
		||||
                    ? actionCapability && actionCapability.getActions('timer.stop')[0] : undefined;
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function isPaused() {
 | 
			
		||||
                return relativeTimerState === 'paused';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function handleLegacyTimer(model) {
 | 
			
		||||
                if (model.timerState === undefined) {
 | 
			
		||||
                    model.timerState = model.timestamp === undefined
 | 
			
		||||
                        ? 'stopped' : 'started';
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function updateObject(domainObject) {
 | 
			
		||||
                var model = domainObject.getModel();
 | 
			
		||||
                handleLegacyTimer(model);
 | 
			
		||||
 | 
			
		||||
                var timestamp = model.timestamp,
 | 
			
		||||
                    formatKey = model.timerFormat,
 | 
			
		||||
                    timerState = model.timerState,
 | 
			
		||||
                    actionCapability = domainObject.getCapability('action'),
 | 
			
		||||
                    actionKey = (timerState !== 'started')
 | 
			
		||||
                        ? 'timer.start' : 'timer.pause';
 | 
			
		||||
 | 
			
		||||
                updateFormat(formatKey);
 | 
			
		||||
                updateTimestamp(timestamp);
 | 
			
		||||
                updateTimerState(timerState);
 | 
			
		||||
                updateActions(actionCapability, actionKey);
 | 
			
		||||
 | 
			
		||||
                //if paused on startup show last known position
 | 
			
		||||
                if (isPaused() && !lastTimestamp) {
 | 
			
		||||
                    lastTimestamp = model.pausedTime;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                update();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function handleObjectChange(domainObject) {
 | 
			
		||||
                if (domainObject) {
 | 
			
		||||
                    updateObject(domainObject);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function handleModification() {
 | 
			
		||||
                handleObjectChange($scope.domainObject);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function tick() {
 | 
			
		||||
                var lastSign = self.signValue,
 | 
			
		||||
                    lastText = self.textValue;
 | 
			
		||||
 | 
			
		||||
                if (!isPaused()) {
 | 
			
		||||
                    lastTimestamp = now();
 | 
			
		||||
                    update();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (relativeTimerState === undefined) {
 | 
			
		||||
                    handleModification();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // We're running in an animation frame, not in a digest cycle.
 | 
			
		||||
                // We need to trigger a digest cycle if our displayable data
 | 
			
		||||
                // changes.
 | 
			
		||||
                if (lastSign !== self.signValue || lastText !== self.textValue) {
 | 
			
		||||
                    $scope.$apply();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (active) {
 | 
			
		||||
                    $window.requestAnimationFrame(tick);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $window.requestAnimationFrame(tick);
 | 
			
		||||
 | 
			
		||||
            // Pull in the timer format from the domain object model
 | 
			
		||||
            $scope.$watch('domainObject', handleObjectChange);
 | 
			
		||||
            $scope.$watch('model.modified', handleModification);
 | 
			
		||||
 | 
			
		||||
            // When the scope is destroyed, stop requesting anim. frames
 | 
			
		||||
            $scope.$on('$destroy', function () {
 | 
			
		||||
                active = false;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            this.$scope = $scope;
 | 
			
		||||
            this.signValue = '';
 | 
			
		||||
            this.textValue = '';
 | 
			
		||||
            this.updateObject = updateObject;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get the CSS class to display the right icon
 | 
			
		||||
         * for the start/pause button.
 | 
			
		||||
         * @returns {string} cssclass to display
 | 
			
		||||
         */
 | 
			
		||||
        TimerController.prototype.buttonCssClass = function () {
 | 
			
		||||
            return this.relevantAction
 | 
			
		||||
                ? this.relevantAction.getMetadata().cssClass : "";
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get the text to show for the start/pause button
 | 
			
		||||
         * (e.g. in a tooltip)
 | 
			
		||||
         * @returns {string} name of the action
 | 
			
		||||
         */
 | 
			
		||||
        TimerController.prototype.buttonText = function () {
 | 
			
		||||
            return this.relevantAction
 | 
			
		||||
                ? this.relevantAction.getMetadata().name : "";
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Perform the action associated with the start/pause button.
 | 
			
		||||
         */
 | 
			
		||||
        TimerController.prototype.clickButton = function () {
 | 
			
		||||
            if (this.relevantAction) {
 | 
			
		||||
                this.relevantAction.perform();
 | 
			
		||||
                this.updateObject(this.$scope.domainObject);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Perform the action associated with the stop button.
 | 
			
		||||
         */
 | 
			
		||||
        TimerController.prototype.clickStopButton = function () {
 | 
			
		||||
            if (this.stopAction) {
 | 
			
		||||
                this.stopAction.perform();
 | 
			
		||||
                this.updateObject(this.$scope.domainObject);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get the sign (+ or -) of the current timer value, as
 | 
			
		||||
         * displayable text.
 | 
			
		||||
         * @returns {string} sign of the current timer value
 | 
			
		||||
         */
 | 
			
		||||
        TimerController.prototype.sign = function () {
 | 
			
		||||
            return this.signValue;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get the sign (+ or -) of the current timer value, as
 | 
			
		||||
         * a CSS class.
 | 
			
		||||
         * @returns {string} sign of the current timer value
 | 
			
		||||
         */
 | 
			
		||||
        TimerController.prototype.signClass = function () {
 | 
			
		||||
            return this.signCssClass;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get the text to display for the current timer value.
 | 
			
		||||
         * @returns {string} current timer value
 | 
			
		||||
         */
 | 
			
		||||
        TimerController.prototype.text = function () {
 | 
			
		||||
            return this.textValue;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return TimerController;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										73
									
								
								platform/features/clock/src/controllers/TimerFormatter.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								platform/features/clock/src/controllers/TimerFormatter.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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(
 | 
			
		||||
    ['moment', 'moment-duration-format'],
 | 
			
		||||
    function (moment) {
 | 
			
		||||
 | 
			
		||||
        var SHORT_FORMAT = "HH:mm:ss",
 | 
			
		||||
            LONG_FORMAT = "d[D] HH:mm:ss";
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Provides formatting functions for Timers.
 | 
			
		||||
         *
 | 
			
		||||
         * Display formats for timers are a little different from what
 | 
			
		||||
         * moment.js provides, so we have custom logic here. This specifically
 | 
			
		||||
         * supports `TimerController`.
 | 
			
		||||
         *
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @memberof platform/features/clock
 | 
			
		||||
         */
 | 
			
		||||
        function TimerFormatter() {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Round this timestamp down to the second boundary
 | 
			
		||||
        // (e.g. 1124ms goes down to 1000ms, -2400ms goes down to -3000ms)
 | 
			
		||||
        function toWholeSeconds(duration) {
 | 
			
		||||
            return Math.abs(Math.floor(duration / 1000) * 1000);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Format a duration for display, using the short form.
 | 
			
		||||
         * (e.g. 03:33:11)
 | 
			
		||||
         * @param {number} duration the duration, in milliseconds
 | 
			
		||||
         * @param {boolean} sign true if positive
 | 
			
		||||
         */
 | 
			
		||||
        TimerFormatter.prototype.short = function (duration) {
 | 
			
		||||
            return moment.duration(toWholeSeconds(duration), 'ms')
 | 
			
		||||
                .format(SHORT_FORMAT, { trim: false });
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Format a duration for display, using the long form.
 | 
			
		||||
         * (e.g. 0d 03:33:11)
 | 
			
		||||
         * @param {number} duration the duration, in milliseconds
 | 
			
		||||
         * @param {boolean} sign true if positive
 | 
			
		||||
         */
 | 
			
		||||
        TimerFormatter.prototype.long = function (duration) {
 | 
			
		||||
            return moment.duration(toWholeSeconds(duration), 'ms')
 | 
			
		||||
                .format(LONG_FORMAT, { trim: false });
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return TimerFormatter;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										87
									
								
								platform/features/clock/src/services/TickerService.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								platform/features/clock/src/services/TickerService.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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 () {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Calls functions every second, as close to the actual second
 | 
			
		||||
         * tick as is feasible.
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @memberof platform/features/clock
 | 
			
		||||
         * @param $timeout Angular's $timeout
 | 
			
		||||
         * @param {Function} now function to provide the current time in ms
 | 
			
		||||
         */
 | 
			
		||||
        function TickerService($timeout, now) {
 | 
			
		||||
            var self = this;
 | 
			
		||||
 | 
			
		||||
            function tick() {
 | 
			
		||||
                var timestamp = now(),
 | 
			
		||||
                    millis = timestamp % 1000;
 | 
			
		||||
 | 
			
		||||
                // Only update callbacks if a second has actually passed.
 | 
			
		||||
                if (timestamp >= self.last + 1000) {
 | 
			
		||||
                    self.callbacks.forEach(function (callback) {
 | 
			
		||||
                        callback(timestamp);
 | 
			
		||||
                    });
 | 
			
		||||
                    self.last = timestamp - millis;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Try to update at exactly the next second
 | 
			
		||||
                $timeout(tick, 1000 - millis, true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            tick();
 | 
			
		||||
 | 
			
		||||
            this.callbacks = [];
 | 
			
		||||
            this.last = now() - 1000;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Listen for clock ticks. The provided callback will
 | 
			
		||||
         * be invoked with the current timestamp (in milliseconds
 | 
			
		||||
         * since Jan 1 1970) at regular intervals, as near to the
 | 
			
		||||
         * second boundary as possible.
 | 
			
		||||
         *
 | 
			
		||||
         * @param {Function} callback callback to invoke
 | 
			
		||||
         * @returns {Function} a function to unregister this listener
 | 
			
		||||
         */
 | 
			
		||||
        TickerService.prototype.listen = function (callback) {
 | 
			
		||||
            var self = this;
 | 
			
		||||
 | 
			
		||||
            self.callbacks.push(callback);
 | 
			
		||||
 | 
			
		||||
            // Provide immediate feedback
 | 
			
		||||
            callback(this.last);
 | 
			
		||||
 | 
			
		||||
            // Provide a deregistration function
 | 
			
		||||
            return function () {
 | 
			
		||||
                self.callbacks = self.callbacks.filter(function (cb) {
 | 
			
		||||
                    return cb !== callback;
 | 
			
		||||
                });
 | 
			
		||||
            };
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return TickerService;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										113
									
								
								platform/features/clock/src/services/TimerService.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								platform/features/clock/src/services/TimerService.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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(['EventEmitter'], function (EventEmitter) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Tracks the currently-followed Timer object. Used by
 | 
			
		||||
     * timelines et al to synchronize to a particular timer.
 | 
			
		||||
     *
 | 
			
		||||
     * The TimerService emits `change` events when the active timer
 | 
			
		||||
     * is changed.
 | 
			
		||||
     */
 | 
			
		||||
    function TimerService(openmct) {
 | 
			
		||||
        EventEmitter.apply(this);
 | 
			
		||||
        this.time = openmct.time;
 | 
			
		||||
        this.objects = openmct.objects;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    TimerService.prototype = Object.create(EventEmitter.prototype);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set (or clear, if `timer` is undefined) the currently active timer.
 | 
			
		||||
     * @param {DomainObject} timer the new active timer
 | 
			
		||||
     * @emits change
 | 
			
		||||
     */
 | 
			
		||||
    TimerService.prototype.setTimer = function (timer) {
 | 
			
		||||
        this.timer = timer;
 | 
			
		||||
        this.emit('change', timer);
 | 
			
		||||
 | 
			
		||||
        if (this.stopObserving) {
 | 
			
		||||
            this.stopObserving();
 | 
			
		||||
            delete this.stopObserving;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (timer) {
 | 
			
		||||
            this.stopObserving =
 | 
			
		||||
                this.objects.observe(timer, '*', this.setTimer.bind(this));
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the currently active timer.
 | 
			
		||||
     * @return {DomainObject} the active timer
 | 
			
		||||
     * @emits change
 | 
			
		||||
     */
 | 
			
		||||
    TimerService.prototype.getTimer = function () {
 | 
			
		||||
        return this.timer;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if there is a currently active timer.
 | 
			
		||||
     * @return {boolean} true if there is a timer
 | 
			
		||||
     */
 | 
			
		||||
    TimerService.prototype.hasTimer = function () {
 | 
			
		||||
        return Boolean(this.timer);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Convert the provided timestamp to milliseconds relative to
 | 
			
		||||
     * the active timer.
 | 
			
		||||
     * @return {number} milliseconds since timer start
 | 
			
		||||
     */
 | 
			
		||||
    TimerService.prototype.convert = function (timestamp) {
 | 
			
		||||
        var clock = this.time.clock();
 | 
			
		||||
        var canConvert = this.hasTimer()
 | 
			
		||||
            && Boolean(clock)
 | 
			
		||||
            && this.timer.timerState !== 'stopped';
 | 
			
		||||
 | 
			
		||||
        if (!canConvert) {
 | 
			
		||||
            return undefined;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var now = clock.currentValue();
 | 
			
		||||
        var delta = this.timer.timerState === 'paused'
 | 
			
		||||
            ? now - this.timer.pausedTime : 0;
 | 
			
		||||
        var epoch = this.timer.timestamp;
 | 
			
		||||
 | 
			
		||||
        return timestamp - epoch - delta;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the value of the active clock, adjusted to be relative to the active
 | 
			
		||||
     * timer. If there is no clock or no active timer, this will return
 | 
			
		||||
     * `undefined`.
 | 
			
		||||
     * @return {number} milliseconds since the start of the active timer
 | 
			
		||||
     */
 | 
			
		||||
    TimerService.prototype.now = function () {
 | 
			
		||||
        var clock = this.time.clock();
 | 
			
		||||
 | 
			
		||||
        return clock && this.convert(clock.currentValue());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return TimerService;
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										106
									
								
								platform/features/clock/test/actions/PauseTimerActionSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								platform/features/clock/test/actions/PauseTimerActionSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,106 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/PauseTimerAction"],
 | 
			
		||||
    function (PauseTimerAction) {
 | 
			
		||||
 | 
			
		||||
        describe("A timer's Pause action", function () {
 | 
			
		||||
            var mockNow,
 | 
			
		||||
                mockDomainObject,
 | 
			
		||||
                testModel,
 | 
			
		||||
                testContext,
 | 
			
		||||
                action;
 | 
			
		||||
 | 
			
		||||
            function asPromise(value) {
 | 
			
		||||
                return (value || {}).then ? value : {
 | 
			
		||||
                    then: function (callback) {
 | 
			
		||||
                        return asPromise(callback(value));
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function testState(type, timerState, timestamp, expected) {
 | 
			
		||||
                testModel.type = type;
 | 
			
		||||
                testModel.timerState = timerState;
 | 
			
		||||
                testModel.timestamp = timestamp;
 | 
			
		||||
 | 
			
		||||
                if (expected) {
 | 
			
		||||
                    expect(PauseTimerAction.appliesTo(testContext)).toBeTruthy();
 | 
			
		||||
                } else {
 | 
			
		||||
                    expect(PauseTimerAction.appliesTo(testContext)).toBeFalsy();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockNow = jasmine.createSpy('now');
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj(
 | 
			
		||||
                    'domainObject',
 | 
			
		||||
                    ['getCapability', 'useCapability', 'getModel']
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockDomainObject.useCapability.and.callFake(function (c, v) {
 | 
			
		||||
                    if (c === 'mutation') {
 | 
			
		||||
                        testModel = v(testModel) || testModel;
 | 
			
		||||
 | 
			
		||||
                        return asPromise(true);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                mockDomainObject.getModel.and.callFake(function () {
 | 
			
		||||
                    return testModel;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                testModel = {};
 | 
			
		||||
                testContext = {domainObject: mockDomainObject};
 | 
			
		||||
 | 
			
		||||
                action = new PauseTimerAction(mockNow, testContext);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates the model with a timerState", function () {
 | 
			
		||||
                testModel.timerState = 'started';
 | 
			
		||||
                action.perform();
 | 
			
		||||
                expect(testModel.timerState).toEqual('paused');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates the model with a pausedTime", function () {
 | 
			
		||||
                testModel.pausedTime = undefined;
 | 
			
		||||
                mockNow.and.returnValue(12000);
 | 
			
		||||
                action.perform();
 | 
			
		||||
                expect(testModel.pausedTime).toEqual(12000);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("applies only to timers in a playing state", function () {
 | 
			
		||||
                //in a stopped state
 | 
			
		||||
                testState('timer', 'stopped', undefined, false);
 | 
			
		||||
 | 
			
		||||
                //in a paused state
 | 
			
		||||
                testState('timer', 'paused', 12000, false);
 | 
			
		||||
 | 
			
		||||
                //in a playing state
 | 
			
		||||
                testState('timer', 'started', 12000, true);
 | 
			
		||||
 | 
			
		||||
                //not a timer
 | 
			
		||||
                testState('clock', 'started', 12000, false);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										112
									
								
								platform/features/clock/test/actions/RestartTimerActionSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								platform/features/clock/test/actions/RestartTimerActionSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,112 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/RestartTimerAction"],
 | 
			
		||||
    function (RestartTimerAction) {
 | 
			
		||||
 | 
			
		||||
        describe("A timer's restart action", function () {
 | 
			
		||||
            var mockNow,
 | 
			
		||||
                mockDomainObject,
 | 
			
		||||
                testModel,
 | 
			
		||||
                testContext,
 | 
			
		||||
                action;
 | 
			
		||||
 | 
			
		||||
            function asPromise(value) {
 | 
			
		||||
                return (value || {}).then ? value : {
 | 
			
		||||
                    then: function (callback) {
 | 
			
		||||
                        return asPromise(callback(value));
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function testState(type, timerState, timestamp, expected) {
 | 
			
		||||
                testModel.type = type;
 | 
			
		||||
                testModel.timerState = timerState;
 | 
			
		||||
                testModel.timestamp = timestamp;
 | 
			
		||||
 | 
			
		||||
                if (expected) {
 | 
			
		||||
                    expect(RestartTimerAction.appliesTo(testContext)).toBeTruthy();
 | 
			
		||||
                } else {
 | 
			
		||||
                    expect(RestartTimerAction.appliesTo(testContext)).toBeFalsy();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockNow = jasmine.createSpy('now');
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj(
 | 
			
		||||
                    'domainObject',
 | 
			
		||||
                    ['getCapability', 'useCapability', 'getModel']
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockDomainObject.useCapability.and.callFake(function (c, v) {
 | 
			
		||||
                    if (c === 'mutation') {
 | 
			
		||||
                        testModel = v(testModel) || testModel;
 | 
			
		||||
 | 
			
		||||
                        return asPromise(true);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                mockDomainObject.getModel.and.callFake(function () {
 | 
			
		||||
                    return testModel;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                testModel = {};
 | 
			
		||||
                testContext = { domainObject: mockDomainObject };
 | 
			
		||||
 | 
			
		||||
                action = new RestartTimerAction(mockNow, testContext);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates the model with a timestamp", function () {
 | 
			
		||||
                testModel.pausedTime = 12000;
 | 
			
		||||
                mockNow.and.returnValue(12000);
 | 
			
		||||
                action.perform();
 | 
			
		||||
                expect(testModel.timestamp).toEqual(12000);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates the model with a pausedTime", function () {
 | 
			
		||||
                testModel.pausedTime = 12000;
 | 
			
		||||
                action.perform();
 | 
			
		||||
                expect(testModel.pausedTime).toEqual(undefined);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates the model with a timerState", function () {
 | 
			
		||||
                testModel.timerState = 'stopped';
 | 
			
		||||
                action.perform();
 | 
			
		||||
                expect(testModel.timerState).toEqual('started');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("applies only to timers in a non-stopped state", function () {
 | 
			
		||||
                //in a stopped state
 | 
			
		||||
                testState('timer', 'stopped', undefined, false);
 | 
			
		||||
 | 
			
		||||
                //in a paused state
 | 
			
		||||
                testState('timer', 'paused', 12000, true);
 | 
			
		||||
 | 
			
		||||
                //in a playing state
 | 
			
		||||
                testState('timer', 'started', 12000, true);
 | 
			
		||||
 | 
			
		||||
                //not a timer
 | 
			
		||||
                testState('clock', 'paused', 12000, false);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										111
									
								
								platform/features/clock/test/actions/StartTimerActionSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								platform/features/clock/test/actions/StartTimerActionSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/StartTimerAction"],
 | 
			
		||||
    function (StartTimerAction) {
 | 
			
		||||
 | 
			
		||||
        describe("A timer's start action", function () {
 | 
			
		||||
            var mockNow,
 | 
			
		||||
                mockDomainObject,
 | 
			
		||||
                testModel,
 | 
			
		||||
                testContext,
 | 
			
		||||
                action;
 | 
			
		||||
 | 
			
		||||
            function asPromise(value) {
 | 
			
		||||
                return (value || {}).then ? value : {
 | 
			
		||||
                    then: function (callback) {
 | 
			
		||||
                        return asPromise(callback(value));
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function testState(type, timerState, timestamp, expected) {
 | 
			
		||||
                testModel.type = type;
 | 
			
		||||
                testModel.timerState = timerState;
 | 
			
		||||
                testModel.timestamp = timestamp;
 | 
			
		||||
 | 
			
		||||
                if (expected) {
 | 
			
		||||
                    expect(StartTimerAction.appliesTo(testContext)).toBeTruthy();
 | 
			
		||||
                } else {
 | 
			
		||||
                    expect(StartTimerAction.appliesTo(testContext)).toBeFalsy();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockNow = jasmine.createSpy('now');
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj(
 | 
			
		||||
                    'domainObject',
 | 
			
		||||
                    ['getCapability', 'useCapability', 'getModel']
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockDomainObject.useCapability.and.callFake(function (c, v) {
 | 
			
		||||
                    if (c === 'mutation') {
 | 
			
		||||
                        testModel = v(testModel) || testModel;
 | 
			
		||||
 | 
			
		||||
                        return asPromise(true);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                mockDomainObject.getModel.and.callFake(function () {
 | 
			
		||||
                    return testModel;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                testModel = {};
 | 
			
		||||
                testContext = {domainObject: mockDomainObject};
 | 
			
		||||
 | 
			
		||||
                action = new StartTimerAction(mockNow, testContext);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates the model with a timestamp", function () {
 | 
			
		||||
                mockNow.and.returnValue(12000);
 | 
			
		||||
                action.perform();
 | 
			
		||||
                expect(testModel.timestamp).toEqual(12000);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates the model with a pausedTime", function () {
 | 
			
		||||
                testModel.pausedTime = 12000;
 | 
			
		||||
                action.perform();
 | 
			
		||||
                expect(testModel.pausedTime).toEqual(undefined);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates the model with a timerState", function () {
 | 
			
		||||
                testModel.timerState = undefined;
 | 
			
		||||
                action.perform();
 | 
			
		||||
                expect(testModel.timerState).toEqual('started');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("applies only to timers not in a playing state", function () {
 | 
			
		||||
                //in a stopped state
 | 
			
		||||
                testState('timer', 'stopped', undefined, true);
 | 
			
		||||
 | 
			
		||||
                //in a paused state
 | 
			
		||||
                testState('timer', 'paused', 12000, true);
 | 
			
		||||
 | 
			
		||||
                //in a playing state
 | 
			
		||||
                testState('timer', 'started', 12000, false);
 | 
			
		||||
 | 
			
		||||
                //not a timer
 | 
			
		||||
                testState('clock', 'paused', 12000, false);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										111
									
								
								platform/features/clock/test/actions/StopTimerActionSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								platform/features/clock/test/actions/StopTimerActionSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/StopTimerAction"],
 | 
			
		||||
    function (StopTimerAction) {
 | 
			
		||||
 | 
			
		||||
        describe("A timer's stop action", function () {
 | 
			
		||||
            var mockNow,
 | 
			
		||||
                mockDomainObject,
 | 
			
		||||
                testModel,
 | 
			
		||||
                testContext,
 | 
			
		||||
                action;
 | 
			
		||||
 | 
			
		||||
            function asPromise(value) {
 | 
			
		||||
                return (value || {}).then ? value : {
 | 
			
		||||
                    then: function (callback) {
 | 
			
		||||
                        return asPromise(callback(value));
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function testState(type, timerState, timestamp, expected) {
 | 
			
		||||
                testModel.type = type;
 | 
			
		||||
                testModel.timerState = timerState;
 | 
			
		||||
                testModel.timestamp = timestamp;
 | 
			
		||||
 | 
			
		||||
                if (expected) {
 | 
			
		||||
                    expect(StopTimerAction.appliesTo(testContext)).toBeTruthy();
 | 
			
		||||
                } else {
 | 
			
		||||
                    expect(StopTimerAction.appliesTo(testContext)).toBeFalsy();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockNow = jasmine.createSpy('now');
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj(
 | 
			
		||||
                    'domainObject',
 | 
			
		||||
                    ['getCapability', 'useCapability', 'getModel']
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockDomainObject.useCapability.and.callFake(function (c, v) {
 | 
			
		||||
                    if (c === 'mutation') {
 | 
			
		||||
                        testModel = v(testModel) || testModel;
 | 
			
		||||
 | 
			
		||||
                        return asPromise(true);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                mockDomainObject.getModel.and.callFake(function () {
 | 
			
		||||
                    return testModel;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                testModel = {};
 | 
			
		||||
                testContext = {domainObject: mockDomainObject};
 | 
			
		||||
 | 
			
		||||
                action = new StopTimerAction(mockNow, testContext);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates the model with a timestamp", function () {
 | 
			
		||||
                mockNow.and.returnValue(12000);
 | 
			
		||||
                action.perform();
 | 
			
		||||
                expect(testModel.timestamp).toEqual(undefined);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates the model with a pausedTime", function () {
 | 
			
		||||
                testModel.pausedTime = 12000;
 | 
			
		||||
                action.perform();
 | 
			
		||||
                expect(testModel.pausedTime).toEqual(undefined);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates the model with a timerState", function () {
 | 
			
		||||
                testModel.timerState = 'started';
 | 
			
		||||
                action.perform();
 | 
			
		||||
                expect(testModel.timerState).toEqual('stopped');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("applies only to timers in a non-stopped state", function () {
 | 
			
		||||
                //in a stopped state
 | 
			
		||||
                testState('timer', 'stopped', undefined, false);
 | 
			
		||||
 | 
			
		||||
                //in a paused state
 | 
			
		||||
                testState('timer', 'paused', 12000, true);
 | 
			
		||||
 | 
			
		||||
                //in a playing state
 | 
			
		||||
                testState('timer', 'started', 12000, true);
 | 
			
		||||
 | 
			
		||||
                //not a timer
 | 
			
		||||
                testState('clock', 'paused', 12000, false);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -0,0 +1,80 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/controllers/RefreshingController"],
 | 
			
		||||
    function (RefreshingController) {
 | 
			
		||||
 | 
			
		||||
        describe("The refreshing controller", function () {
 | 
			
		||||
            var mockScope,
 | 
			
		||||
                mockTicker,
 | 
			
		||||
                mockUnticker,
 | 
			
		||||
                controller;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockScope = jasmine.createSpyObj('$scope', ['$on']);
 | 
			
		||||
                mockTicker = jasmine.createSpyObj('ticker', ['listen']);
 | 
			
		||||
                mockUnticker = jasmine.createSpy('unticker');
 | 
			
		||||
 | 
			
		||||
                mockTicker.listen.and.returnValue(mockUnticker);
 | 
			
		||||
 | 
			
		||||
                controller = new RefreshingController(mockScope, mockTicker);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("refreshes the represented object on every tick", function () {
 | 
			
		||||
                var mockDomainObject = jasmine.createSpyObj(
 | 
			
		||||
                        'domainObject',
 | 
			
		||||
                        ['getCapability']
 | 
			
		||||
                    ),
 | 
			
		||||
                    mockPersistence = jasmine.createSpyObj(
 | 
			
		||||
                        'persistence',
 | 
			
		||||
                        ['persist', 'refresh']
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                mockDomainObject.getCapability.and.callFake(function (c) {
 | 
			
		||||
                    return (c === 'persistence') && mockPersistence;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                mockScope.domainObject = mockDomainObject;
 | 
			
		||||
 | 
			
		||||
                mockTicker.listen.calls.mostRecent().args[0](12321);
 | 
			
		||||
                expect(mockPersistence.refresh).toHaveBeenCalled();
 | 
			
		||||
                expect(mockPersistence.persist).not.toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("subscribes to clock ticks", function () {
 | 
			
		||||
                expect(mockTicker.listen)
 | 
			
		||||
                    .toHaveBeenCalledWith(jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("unsubscribes to ticks when destroyed", function () {
 | 
			
		||||
                // Make sure $destroy is being listened for...
 | 
			
		||||
                expect(mockScope.$on.calls.mostRecent().args[0]).toEqual('$destroy');
 | 
			
		||||
                expect(mockUnticker).not.toHaveBeenCalled();
 | 
			
		||||
 | 
			
		||||
                // ...and makes sure that its listener unsubscribes from ticker
 | 
			
		||||
                mockScope.$on.calls.mostRecent().args[1]();
 | 
			
		||||
                expect(mockUnticker).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										227
									
								
								platform/features/clock/test/controllers/TimerControllerSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								platform/features/clock/test/controllers/TimerControllerSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,227 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/controllers/TimerController"],
 | 
			
		||||
    function (TimerController) {
 | 
			
		||||
 | 
			
		||||
        // Wed, 03 Jun 2015 17:56:14 GMT
 | 
			
		||||
        var TEST_TIMESTAMP = 1433354174000;
 | 
			
		||||
 | 
			
		||||
        describe("A timer view's controller", function () {
 | 
			
		||||
            var mockScope,
 | 
			
		||||
                mockWindow,
 | 
			
		||||
                mockNow,
 | 
			
		||||
                mockDomainObject,
 | 
			
		||||
                mockActionCapability,
 | 
			
		||||
                mockStart,
 | 
			
		||||
                mockPause,
 | 
			
		||||
                mockStop,
 | 
			
		||||
                testModel,
 | 
			
		||||
                controller;
 | 
			
		||||
 | 
			
		||||
            function invokeWatch(expr, value) {
 | 
			
		||||
                mockScope.$watch.calls.all().forEach(function (call) {
 | 
			
		||||
                    if (call.args[0] === expr) {
 | 
			
		||||
                        call.args[1](value);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockScope = jasmine.createSpyObj(
 | 
			
		||||
                    '$scope',
 | 
			
		||||
                    ['$watch', '$on', '$apply']
 | 
			
		||||
                );
 | 
			
		||||
                mockWindow = jasmine.createSpyObj(
 | 
			
		||||
                    '$window',
 | 
			
		||||
                    ['requestAnimationFrame']
 | 
			
		||||
                );
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj(
 | 
			
		||||
                    'domainObject',
 | 
			
		||||
                    ['getCapability', 'useCapability', 'getModel']
 | 
			
		||||
                );
 | 
			
		||||
                mockActionCapability = jasmine.createSpyObj(
 | 
			
		||||
                    'action',
 | 
			
		||||
                    ['getActions']
 | 
			
		||||
                );
 | 
			
		||||
                mockStart = jasmine.createSpyObj(
 | 
			
		||||
                    'start',
 | 
			
		||||
                    ['getMetadata', 'perform']
 | 
			
		||||
                );
 | 
			
		||||
                mockPause = jasmine.createSpyObj(
 | 
			
		||||
                    'paused',
 | 
			
		||||
                    ['getMetadata', 'perform']
 | 
			
		||||
                );
 | 
			
		||||
                mockStop = jasmine.createSpyObj(
 | 
			
		||||
                    'stopped',
 | 
			
		||||
                    ['getMetadata', 'perform']
 | 
			
		||||
                );
 | 
			
		||||
                mockNow = jasmine.createSpy('now');
 | 
			
		||||
 | 
			
		||||
                mockDomainObject.getCapability.and.callFake(function (c) {
 | 
			
		||||
                    return (c === 'action') && mockActionCapability;
 | 
			
		||||
                });
 | 
			
		||||
                mockDomainObject.getModel.and.callFake(function () {
 | 
			
		||||
                    return testModel;
 | 
			
		||||
                });
 | 
			
		||||
                mockActionCapability.getActions.and.callFake(function (k) {
 | 
			
		||||
                    return [{
 | 
			
		||||
                        'timer.start': mockStart,
 | 
			
		||||
                        'timer.pause': mockPause,
 | 
			
		||||
                        'timer.stop': mockStop
 | 
			
		||||
                    }[k]];
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                mockStart.getMetadata.and.returnValue({
 | 
			
		||||
                    cssClass: "icon-play",
 | 
			
		||||
                    name: "Start"
 | 
			
		||||
                });
 | 
			
		||||
                mockPause.getMetadata.and.returnValue({
 | 
			
		||||
                    cssClass: "icon-pause",
 | 
			
		||||
                    name: "Pause"
 | 
			
		||||
                });
 | 
			
		||||
                mockStop.getMetadata.and.returnValue({
 | 
			
		||||
                    cssClass: "icon-box-round-corners",
 | 
			
		||||
                    name: "Stop"
 | 
			
		||||
                });
 | 
			
		||||
                mockScope.domainObject = mockDomainObject;
 | 
			
		||||
 | 
			
		||||
                testModel = {};
 | 
			
		||||
 | 
			
		||||
                controller = new TimerController(mockScope, mockWindow, mockNow);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("watches for the domain object in view", function () {
 | 
			
		||||
                expect(mockScope.$watch).toHaveBeenCalledWith(
 | 
			
		||||
                    "domainObject",
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("watches for domain object modifications", function () {
 | 
			
		||||
                expect(mockScope.$watch).toHaveBeenCalledWith(
 | 
			
		||||
                    "model.modified",
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("updates on a timer", function () {
 | 
			
		||||
                expect(mockWindow.requestAnimationFrame)
 | 
			
		||||
                    .toHaveBeenCalledWith(jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("displays nothing when there is no target", function () {
 | 
			
		||||
                // Notify that domain object is available via scope
 | 
			
		||||
                invokeWatch('domainObject', mockDomainObject);
 | 
			
		||||
                mockNow.and.returnValue(TEST_TIMESTAMP);
 | 
			
		||||
                mockWindow.requestAnimationFrame.calls.mostRecent().args[0]();
 | 
			
		||||
                expect(controller.sign()).toEqual("");
 | 
			
		||||
                expect(controller.signClass()).toEqual("");
 | 
			
		||||
                expect(controller.text()).toEqual("");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("formats time to display relative to target", function () {
 | 
			
		||||
                testModel.timestamp = TEST_TIMESTAMP;
 | 
			
		||||
                testModel.timerFormat = 'long';
 | 
			
		||||
                // Notify that domain object is available via scope
 | 
			
		||||
                invokeWatch('domainObject', mockDomainObject);
 | 
			
		||||
 | 
			
		||||
                mockNow.and.returnValue(TEST_TIMESTAMP + 121000);
 | 
			
		||||
                mockWindow.requestAnimationFrame.calls.mostRecent().args[0]();
 | 
			
		||||
                expect(controller.sign()).toEqual("+");
 | 
			
		||||
                expect(controller.signClass()).toEqual("icon-plus");
 | 
			
		||||
                expect(controller.text()).toEqual("0D 00:02:01");
 | 
			
		||||
 | 
			
		||||
                mockNow.and.returnValue(TEST_TIMESTAMP - 121000);
 | 
			
		||||
                mockWindow.requestAnimationFrame.calls.mostRecent().args[0]();
 | 
			
		||||
                expect(controller.sign()).toEqual("-");
 | 
			
		||||
                expect(controller.signClass()).toEqual("icon-minus");
 | 
			
		||||
                expect(controller.text()).toEqual("0D 00:02:01");
 | 
			
		||||
 | 
			
		||||
                mockNow.and.returnValue(TEST_TIMESTAMP);
 | 
			
		||||
                mockWindow.requestAnimationFrame.calls.mostRecent().args[0]();
 | 
			
		||||
                expect(controller.sign()).toEqual("");
 | 
			
		||||
                expect(controller.signClass()).toEqual("");
 | 
			
		||||
                expect(controller.text()).toEqual("0D 00:00:00");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("shows cssClass & name for the applicable start/pause action", function () {
 | 
			
		||||
                invokeWatch('domainObject', mockDomainObject);
 | 
			
		||||
                expect(controller.buttonCssClass()).toEqual("icon-play");
 | 
			
		||||
                expect(controller.buttonText()).toEqual("Start");
 | 
			
		||||
 | 
			
		||||
                testModel.timestamp = 12321;
 | 
			
		||||
                testModel.timerState = 'started';
 | 
			
		||||
                invokeWatch('model.modified', 1);
 | 
			
		||||
                expect(controller.buttonCssClass()).toEqual("icon-pause");
 | 
			
		||||
                expect(controller.buttonText()).toEqual("Pause");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("performs correct start/pause/stop action on click", function () {
 | 
			
		||||
                //test start
 | 
			
		||||
                invokeWatch('domainObject', mockDomainObject);
 | 
			
		||||
                expect(mockStart.perform).not.toHaveBeenCalled();
 | 
			
		||||
                controller.clickButton();
 | 
			
		||||
                expect(mockStart.perform).toHaveBeenCalled();
 | 
			
		||||
 | 
			
		||||
                //test pause
 | 
			
		||||
                testModel.timestamp = 12321;
 | 
			
		||||
                testModel.timerState = 'started';
 | 
			
		||||
                invokeWatch('model.modified', 1);
 | 
			
		||||
                expect(mockPause.perform).not.toHaveBeenCalled();
 | 
			
		||||
                controller.clickButton();
 | 
			
		||||
                expect(mockPause.perform).toHaveBeenCalled();
 | 
			
		||||
 | 
			
		||||
                //test stop
 | 
			
		||||
                expect(mockStop.perform).not.toHaveBeenCalled();
 | 
			
		||||
                controller.clickStopButton();
 | 
			
		||||
                expect(mockStop.perform).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("stops requesting animation frames when destroyed", function () {
 | 
			
		||||
                var initialCount = mockWindow.requestAnimationFrame.calls.count();
 | 
			
		||||
 | 
			
		||||
                // First, check that normally new frames keep getting requested
 | 
			
		||||
                mockWindow.requestAnimationFrame.calls.mostRecent().args[0]();
 | 
			
		||||
                expect(mockWindow.requestAnimationFrame.calls.count())
 | 
			
		||||
                    .toEqual(initialCount + 1);
 | 
			
		||||
                mockWindow.requestAnimationFrame.calls.mostRecent().args[0]();
 | 
			
		||||
                expect(mockWindow.requestAnimationFrame.calls.count())
 | 
			
		||||
                    .toEqual(initialCount + 2);
 | 
			
		||||
 | 
			
		||||
                // Now, verify that it stops after $destroy
 | 
			
		||||
                expect(mockScope.$on.calls.mostRecent().args[0])
 | 
			
		||||
                    .toEqual('$destroy');
 | 
			
		||||
                mockScope.$on.calls.mostRecent().args[1]();
 | 
			
		||||
 | 
			
		||||
                // Frames should no longer get requested
 | 
			
		||||
                mockWindow.requestAnimationFrame.calls.mostRecent().args[0]();
 | 
			
		||||
                expect(mockWindow.requestAnimationFrame.calls.count())
 | 
			
		||||
                    .toEqual(initialCount + 2);
 | 
			
		||||
                mockWindow.requestAnimationFrame.calls.mostRecent().args[0]();
 | 
			
		||||
                expect(mockWindow.requestAnimationFrame.calls.count())
 | 
			
		||||
                    .toEqual(initialCount + 2);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										111
									
								
								platform/features/clock/test/controllers/TimerFormatterSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								platform/features/clock/test/controllers/TimerFormatterSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/controllers/TimerFormatter"],
 | 
			
		||||
    function (TimerFormatter) {
 | 
			
		||||
 | 
			
		||||
        var MS_IN_SEC = 1000,
 | 
			
		||||
            MS_IN_MIN = MS_IN_SEC * 60,
 | 
			
		||||
            MS_IN_HR = MS_IN_MIN * 60,
 | 
			
		||||
            MS_IN_DAY = MS_IN_HR * 24;
 | 
			
		||||
 | 
			
		||||
        describe("The timer value formatter", function () {
 | 
			
		||||
            var formatter = new TimerFormatter();
 | 
			
		||||
 | 
			
		||||
            function sum(a, b) {
 | 
			
		||||
                return a + b;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function toDuration(days, hours, mins, secs) {
 | 
			
		||||
                return [
 | 
			
		||||
                    days * MS_IN_DAY,
 | 
			
		||||
                    hours * MS_IN_HR,
 | 
			
		||||
                    mins * MS_IN_MIN,
 | 
			
		||||
                    secs * MS_IN_SEC
 | 
			
		||||
                ].reduce(sum, 0);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            it("formats short-form values (no days)", function () {
 | 
			
		||||
                expect(formatter.short(toDuration(0, 123, 2, 3) + 123))
 | 
			
		||||
                    .toEqual("123:02:03");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("formats negative short-form values (no days)", function () {
 | 
			
		||||
                expect(formatter.short(-toDuration(0, 123, 2, 3) + 123))
 | 
			
		||||
                    .toEqual("123:02:03");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("formats long-form values (with days)", function () {
 | 
			
		||||
                expect(formatter.long(toDuration(0, 123, 2, 3) + 123))
 | 
			
		||||
                    .toEqual("5D 03:02:03");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("formats negative long-form values (no days)", function () {
 | 
			
		||||
                expect(formatter.long(-toDuration(0, 123, 2, 3) + 123))
 | 
			
		||||
                    .toEqual("5D 03:02:03");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("rounds seconds down for positive durations", function () {
 | 
			
		||||
                expect(formatter.short(MS_IN_SEC + 600))
 | 
			
		||||
                    .toEqual("00:00:01");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("rounds seconds up for negative durations", function () {
 | 
			
		||||
                expect(formatter.short(-MS_IN_SEC - 600))
 | 
			
		||||
                    .toEqual("00:00:02");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("short-formats correctly around negative time borders", function () {
 | 
			
		||||
                expect(formatter.short(-1)).toEqual("00:00:01");
 | 
			
		||||
                expect(formatter.short(-1000)).toEqual("00:00:01");
 | 
			
		||||
                expect(formatter.short(-1001)).toEqual("00:00:02");
 | 
			
		||||
                expect(formatter.short(-2000)).toEqual("00:00:02");
 | 
			
		||||
                expect(formatter.short(-59001)).toEqual("00:01:00");
 | 
			
		||||
                expect(formatter.short(-60000)).toEqual("00:01:00");
 | 
			
		||||
 | 
			
		||||
                expect(formatter.short(-MS_IN_HR + 999)).toEqual("01:00:00");
 | 
			
		||||
                expect(formatter.short(-MS_IN_HR)).toEqual("01:00:00");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("differentiates between values around zero", function () {
 | 
			
		||||
                // These are more than 1000 ms apart so should not appear
 | 
			
		||||
                // as the same second
 | 
			
		||||
                expect(formatter.short(-999))
 | 
			
		||||
                    .not.toEqual(formatter.short(999));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("handles negative days", function () {
 | 
			
		||||
                expect(formatter.long(-10 * MS_IN_DAY))
 | 
			
		||||
                    .toEqual("10D 00:00:00");
 | 
			
		||||
                expect(formatter.long(-10 * MS_IN_DAY + 100))
 | 
			
		||||
                    .toEqual("10D 00:00:00");
 | 
			
		||||
                expect(formatter.long(-10 * MS_IN_DAY + 999))
 | 
			
		||||
                    .toEqual("10D 00:00:00");
 | 
			
		||||
 | 
			
		||||
                expect(formatter.short(-10 * MS_IN_DAY + 100))
 | 
			
		||||
                    .toEqual("240:00:00");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										62
									
								
								platform/features/clock/test/services/TickerServiceSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								platform/features/clock/test/services/TickerServiceSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/services/TickerService"],
 | 
			
		||||
    function (TickerService) {
 | 
			
		||||
 | 
			
		||||
        var TEST_TIMESTAMP = 1433354174000;
 | 
			
		||||
 | 
			
		||||
        describe("The ticker service", function () {
 | 
			
		||||
            var mockTimeout,
 | 
			
		||||
                mockNow,
 | 
			
		||||
                mockCallback,
 | 
			
		||||
                tickerService;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockTimeout = jasmine.createSpy('$timeout');
 | 
			
		||||
                mockNow = jasmine.createSpy('now');
 | 
			
		||||
                mockCallback = jasmine.createSpy('callback');
 | 
			
		||||
 | 
			
		||||
                mockNow.and.returnValue(TEST_TIMESTAMP);
 | 
			
		||||
 | 
			
		||||
                tickerService = new TickerService(mockTimeout, mockNow);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("notifies listeners of clock ticks", function () {
 | 
			
		||||
                tickerService.listen(mockCallback);
 | 
			
		||||
                mockNow.and.returnValue(TEST_TIMESTAMP + 12321);
 | 
			
		||||
                mockTimeout.calls.mostRecent().args[0]();
 | 
			
		||||
                expect(mockCallback)
 | 
			
		||||
                    .toHaveBeenCalledWith(TEST_TIMESTAMP + 12321);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("allows listeners to unregister", function () {
 | 
			
		||||
                tickerService.listen(mockCallback)(); // Unregister immediately
 | 
			
		||||
                mockNow.and.returnValue(TEST_TIMESTAMP + 12321);
 | 
			
		||||
                mockTimeout.calls.mostRecent().args[0]();
 | 
			
		||||
                expect(mockCallback).not
 | 
			
		||||
                    .toHaveBeenCalledWith(TEST_TIMESTAMP + 12321);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										77
									
								
								platform/features/clock/test/services/TimerServiceSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								platform/features/clock/test/services/TimerServiceSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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/services/TimerService'
 | 
			
		||||
], function (TimerService) {
 | 
			
		||||
    describe("TimerService", function () {
 | 
			
		||||
        var callback;
 | 
			
		||||
        var mockmct;
 | 
			
		||||
        var timerService;
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            callback = jasmine.createSpy('callback');
 | 
			
		||||
            mockmct = {
 | 
			
		||||
                time: { clock: jasmine.createSpy('clock') },
 | 
			
		||||
                objects: { observe: jasmine.createSpy('observe') }
 | 
			
		||||
            };
 | 
			
		||||
            timerService = new TimerService(mockmct);
 | 
			
		||||
            timerService.on('change', callback);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("initially emits no change events", function () {
 | 
			
		||||
            expect(callback).not.toHaveBeenCalled();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("reports no current timer", function () {
 | 
			
		||||
            expect(timerService.getTimer()).toBeUndefined();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe("setTimer", function () {
 | 
			
		||||
            var testTimer;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                testTimer = { name: "I am some timer; you are nobody." };
 | 
			
		||||
                timerService.setTimer(testTimer);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("emits a change event", function () {
 | 
			
		||||
                expect(callback).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("reports the current timer", function () {
 | 
			
		||||
                expect(timerService.getTimer()).toBe(testTimer);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("observes changes to an object", function () {
 | 
			
		||||
                var newTimer = { name: "I am another timer." };
 | 
			
		||||
                expect(mockmct.objects.observe).toHaveBeenCalledWith(
 | 
			
		||||
                    testTimer,
 | 
			
		||||
                    '*',
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
                mockmct.objects.observe.calls.mostRecent().args[2](newTimer);
 | 
			
		||||
                expect(timerService.getTimer()).toBe(newTimer);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -20,30 +20,30 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import { MY_ITEMS_KEY } from "./createMyItemsIdentifier";
 | 
			
		||||
 | 
			
		||||
function myItemsInterceptor(identifierObject, openmct) {
 | 
			
		||||
 | 
			
		||||
    const myItemsModel = {
 | 
			
		||||
        identifier: identifierObject,
 | 
			
		||||
        "name": "My Items",
 | 
			
		||||
        "type": "folder",
 | 
			
		||||
        "composition": [],
 | 
			
		||||
        "location": "ROOT"
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
define([], function () {
 | 
			
		||||
    return {
 | 
			
		||||
        appliesTo: (identifier) => {
 | 
			
		||||
            return identifier.key === MY_ITEMS_KEY;
 | 
			
		||||
        },
 | 
			
		||||
        invoke: (identifier, object) => {
 | 
			
		||||
            if (openmct.objects.isMissing(object)) {
 | 
			
		||||
                return myItemsModel;
 | 
			
		||||
        name: "platform/features/my-items",
 | 
			
		||||
        definition: {
 | 
			
		||||
            "name": "My Items",
 | 
			
		||||
            "description": "Defines a root named My Items",
 | 
			
		||||
            "extensions": {
 | 
			
		||||
                "roots": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "id": "mine"
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "models": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "id": "mine",
 | 
			
		||||
                        "model": {
 | 
			
		||||
                            "name": "My Items",
 | 
			
		||||
                            "type": "folder",
 | 
			
		||||
                            "composition": [],
 | 
			
		||||
                            "location": "ROOT"
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return object;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default myItemsInterceptor;
 | 
			
		||||
});
 | 
			
		||||
@@ -44,11 +44,9 @@ define(
 | 
			
		||||
                        setText(result.name);
 | 
			
		||||
                        scope.ngModel[scope.field] = result;
 | 
			
		||||
                        control.$setValidity("file-input", true);
 | 
			
		||||
                        scope.$digest();
 | 
			
		||||
                    }, function () {
 | 
			
		||||
                        setText('Select File');
 | 
			
		||||
                        control.$setValidity("file-input", false);
 | 
			
		||||
                        scope.$digest();
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -71,15 +71,13 @@ define(['zepto', 'objectUtils'], function ($, objectUtils) {
 | 
			
		||||
        var rootObj = this.instantiate(rootModel, rootId);
 | 
			
		||||
        var newStyleParent = parent.useCapability('adapter');
 | 
			
		||||
        var newStyleRootObj = rootObj.useCapability('adapter');
 | 
			
		||||
        newStyleRootObj.location = parent.getId();
 | 
			
		||||
 | 
			
		||||
        if (this.openmct.composition.checkPolicy(newStyleParent, newStyleRootObj)) {
 | 
			
		||||
            // Instantiate all objects in tree with their newly generated ids,
 | 
			
		||||
            // adding each to its rightful parent's composition
 | 
			
		||||
            rootObj.getCapability("location").setPrimaryLocation(parent.getId());
 | 
			
		||||
            this.deepInstantiate(rootObj, tree.openmct, []);
 | 
			
		||||
            this.openmct.objects.save(newStyleRootObj);
 | 
			
		||||
            const compositionCollection = this.openmct.composition.get(newStyleParent);
 | 
			
		||||
            compositionCollection.add(newStyleRootObj);
 | 
			
		||||
            parent.getCapability("composition").add(rootObj);
 | 
			
		||||
        } else {
 | 
			
		||||
            var dialog = this.openmct.overlays.dialog({
 | 
			
		||||
                iconClass: 'alert',
 | 
			
		||||
@@ -105,6 +103,7 @@ define(['zepto', 'objectUtils'], function ($, objectUtils) {
 | 
			
		||||
            var newObj;
 | 
			
		||||
 | 
			
		||||
            seen.push(parent.getId());
 | 
			
		||||
 | 
			
		||||
            parentModel.composition.forEach(function (childId) {
 | 
			
		||||
                let keystring = this.openmct.objects.makeKeyString(childId);
 | 
			
		||||
 | 
			
		||||
@@ -116,7 +115,8 @@ define(['zepto', 'objectUtils'], function ($, objectUtils) {
 | 
			
		||||
                delete newModel.persisted;
 | 
			
		||||
 | 
			
		||||
                newObj = this.instantiate(newModel, keystring);
 | 
			
		||||
                this.openmct.objects.save(newModel);
 | 
			
		||||
                newObj.getCapability("location")
 | 
			
		||||
                    .setPrimaryLocation(tree[keystring].location);
 | 
			
		||||
                this.deepInstantiate(newObj, tree, seen);
 | 
			
		||||
            }, this);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -42,21 +42,15 @@ define(
 | 
			
		||||
                newObjects;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
 | 
			
		||||
                uniqueId = 0;
 | 
			
		||||
                newObjects = [];
 | 
			
		||||
                openmct = {
 | 
			
		||||
                    $injector: jasmine.createSpyObj('$injector', ['get']),
 | 
			
		||||
                    objects: {
 | 
			
		||||
                        makeKeyString: identifier => identifier.key,
 | 
			
		||||
                        save: o => true
 | 
			
		||||
                    },
 | 
			
		||||
                    composition: {
 | 
			
		||||
                        get: (o) => {
 | 
			
		||||
                            return {
 | 
			
		||||
                                add: v => {}
 | 
			
		||||
                            };
 | 
			
		||||
                        },
 | 
			
		||||
                        checkPolicy: (a, b) => true
 | 
			
		||||
                        makeKeyString: function (identifier) {
 | 
			
		||||
                            return identifier.key;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
                mockInstantiate = jasmine.createSpy('instantiate').and.callFake(
 | 
			
		||||
@@ -66,6 +60,14 @@ define(
 | 
			
		||||
                            "id": id,
 | 
			
		||||
                            "capabilities": {}
 | 
			
		||||
                        };
 | 
			
		||||
                        var locationCapability = {
 | 
			
		||||
                            setPrimaryLocation: jasmine.createSpy('setPrimaryLocation')
 | 
			
		||||
                                .and
 | 
			
		||||
                                .callFake(function (newLocation) {
 | 
			
		||||
                                    config.model.location = newLocation;
 | 
			
		||||
                                })
 | 
			
		||||
                        };
 | 
			
		||||
                        config.capabilities.location = locationCapability;
 | 
			
		||||
                        if (model.composition) {
 | 
			
		||||
                            var compCapability =
 | 
			
		||||
                                jasmine.createSpy('compCapability')
 | 
			
		||||
@@ -77,10 +79,6 @@ define(
 | 
			
		||||
                            config.capabilities.composition = compCapability;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        config.capabilities.adapter = {
 | 
			
		||||
                            invoke: () => model
 | 
			
		||||
                        };
 | 
			
		||||
 | 
			
		||||
                        newObjects.push(domainObjectFactory(config));
 | 
			
		||||
 | 
			
		||||
                        return domainObjectFactory(config);
 | 
			
		||||
@@ -148,33 +146,14 @@ define(
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("can import self-containing objects", function () {
 | 
			
		||||
            xit("can import self-containing objects", function () {
 | 
			
		||||
                var compDomainObject = domainObjectFactory({
 | 
			
		||||
                    name: 'compObject',
 | 
			
		||||
                    model: { name: 'compObject'},
 | 
			
		||||
                    capabilities: {
 | 
			
		||||
                        "composition": compositionCapability,
 | 
			
		||||
                        'adapter': {
 | 
			
		||||
                            invoke: () => {
 | 
			
		||||
                                return {
 | 
			
		||||
                                    name: 'parent',
 | 
			
		||||
                                    composition: [],
 | 
			
		||||
                                    id: "mine",
 | 
			
		||||
                                    identifier: {
 | 
			
		||||
                                        namespace: '',
 | 
			
		||||
                                        key: 'mine'
 | 
			
		||||
                                    },
 | 
			
		||||
                                    location: "ROOT",
 | 
			
		||||
                                    modified: 1637287323760,
 | 
			
		||||
                                    persisted: 1637287323760,
 | 
			
		||||
                                    type: "folder"
 | 
			
		||||
                                };
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    capabilities: {"composition": compositionCapability}
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                context.domainObject = compDomainObject;
 | 
			
		||||
 | 
			
		||||
                dialogService.getUserInput.and.returnValue(Promise.resolve(
 | 
			
		||||
                    {
 | 
			
		||||
                        selectFile: {
 | 
			
		||||
@@ -182,14 +161,10 @@ define(
 | 
			
		||||
                                "openmct": {
 | 
			
		||||
                                    "infiniteParent": {
 | 
			
		||||
                                        "composition": [{
 | 
			
		||||
                                            "key": "infinteChild",
 | 
			
		||||
                                            "namespace": ""
 | 
			
		||||
                                            key: "infinteChild",
 | 
			
		||||
                                            namespace: ""
 | 
			
		||||
                                        }],
 | 
			
		||||
                                        "identifier": {
 | 
			
		||||
                                            "key": "infiniteParent",
 | 
			
		||||
                                            "namespace": ""
 | 
			
		||||
                                        },
 | 
			
		||||
                                        "name": "parent",
 | 
			
		||||
                                        "name": "1",
 | 
			
		||||
                                        "type": "folder",
 | 
			
		||||
                                        "modified": 1503598129176,
 | 
			
		||||
                                        "location": "mine",
 | 
			
		||||
@@ -197,14 +172,10 @@ define(
 | 
			
		||||
                                    },
 | 
			
		||||
                                    "infinteChild": {
 | 
			
		||||
                                        "composition": [{
 | 
			
		||||
                                            "key": "infiniteParent",
 | 
			
		||||
                                            "namespace": ""
 | 
			
		||||
                                            key: "infinteParent",
 | 
			
		||||
                                            namespace: ""
 | 
			
		||||
                                        }],
 | 
			
		||||
                                        "identifier": {
 | 
			
		||||
                                            "key": "infinteChild",
 | 
			
		||||
                                            "namespace": ""
 | 
			
		||||
                                        },
 | 
			
		||||
                                        "name": "child",
 | 
			
		||||
                                        "name": "2",
 | 
			
		||||
                                        "type": "folder",
 | 
			
		||||
                                        "modified": 1503598132428,
 | 
			
		||||
                                        "location": "infiniteParent",
 | 
			
		||||
@@ -227,7 +198,7 @@ define(
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("assigns new ids to each imported object", function () {
 | 
			
		||||
            xit("assigns new ids to each imported object", function () {
 | 
			
		||||
                dialogService.getUserInput.and.returnValue(Promise.resolve(
 | 
			
		||||
                    {
 | 
			
		||||
                        selectFile: {
 | 
			
		||||
@@ -258,6 +229,7 @@ define(
 | 
			
		||||
                    expect(newObjects[0].getId()).toBe('1');
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -136,7 +136,7 @@ define([
 | 
			
		||||
         * @memberof module:openmct.MCT#
 | 
			
		||||
         * @name conductor
 | 
			
		||||
         */
 | 
			
		||||
        this.time = new api.TimeAPI(this);
 | 
			
		||||
        this.time = new api.TimeAPI();
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * An interface for interacting with the composition of domain objects.
 | 
			
		||||
@@ -253,8 +253,6 @@ define([
 | 
			
		||||
 | 
			
		||||
        this.status = new api.StatusAPI(this);
 | 
			
		||||
 | 
			
		||||
        this.priority = api.PriorityAPI;
 | 
			
		||||
 | 
			
		||||
        this.router = new ApplicationRouter(this);
 | 
			
		||||
 | 
			
		||||
        this.branding = BrandingAPI.default;
 | 
			
		||||
@@ -265,7 +263,6 @@ define([
 | 
			
		||||
        // Plugins that are installed by default
 | 
			
		||||
 | 
			
		||||
        this.install(this.plugins.Plot());
 | 
			
		||||
        this.install(this.plugins.Chart());
 | 
			
		||||
        this.install(this.plugins.TelemetryTable.default());
 | 
			
		||||
        this.install(PreviewPlugin.default());
 | 
			
		||||
        this.install(LegacyIndicatorsPlugin());
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,8 @@ export default function LegacyActionAdapter(openmct, legacyActions) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        console.warn(`DEPRECATION WARNING: Action ${action.definition.key} in bundle ${action.bundle.path} is non-contextual and should be migrated.`);
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,9 +25,11 @@ define([
 | 
			
		||||
    './capabilities/AdapterCapability',
 | 
			
		||||
    './directives/MCTView',
 | 
			
		||||
    './services/Instantiate',
 | 
			
		||||
    './services/MissingModelCompatibilityDecorator',
 | 
			
		||||
    './capabilities/APICapabilityDecorator',
 | 
			
		||||
    './policies/AdaptedViewPolicy',
 | 
			
		||||
    './runs/AlternateCompositionInitializer',
 | 
			
		||||
    './runs/TypeDeprecationChecker',
 | 
			
		||||
    './runs/LegacyTelemetryProvider',
 | 
			
		||||
    './runs/RegisterLegacyTypes',
 | 
			
		||||
    './services/LegacyObjectAPIInterceptor',
 | 
			
		||||
@@ -40,9 +42,11 @@ define([
 | 
			
		||||
    AdapterCapability,
 | 
			
		||||
    MCTView,
 | 
			
		||||
    Instantiate,
 | 
			
		||||
    MissingModelCompatibilityDecorator,
 | 
			
		||||
    APICapabilityDecorator,
 | 
			
		||||
    AdaptedViewPolicy,
 | 
			
		||||
    AlternateCompositionInitializer,
 | 
			
		||||
    TypeDeprecationChecker,
 | 
			
		||||
    LegacyTelemetryProvider,
 | 
			
		||||
    RegisterLegacyTypes,
 | 
			
		||||
    LegacyObjectAPIInterceptor,
 | 
			
		||||
@@ -95,6 +99,12 @@ define([
 | 
			
		||||
                        implementation: ActionDialogDecorator,
 | 
			
		||||
                        depends: ["openmct"]
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        type: "decorator",
 | 
			
		||||
                        provides: "modelService",
 | 
			
		||||
                        implementation: MissingModelCompatibilityDecorator,
 | 
			
		||||
                        depends: ["openmct"]
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        provides: "objectService",
 | 
			
		||||
                        type: "decorator",
 | 
			
		||||
@@ -125,6 +135,10 @@ define([
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                runs: [
 | 
			
		||||
                    {
 | 
			
		||||
                        implementation: TypeDeprecationChecker,
 | 
			
		||||
                        depends: ["types[]"]
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        implementation: AlternateCompositionInitializer,
 | 
			
		||||
                        depends: ["openmct"]
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,12 @@ define([
 | 
			
		||||
 | 
			
		||||
) {
 | 
			
		||||
    function RegisterLegacyTypes(types, openmct) {
 | 
			
		||||
        types.forEach(function (legacyDefinition) {
 | 
			
		||||
            if (!openmct.types.get(legacyDefinition.key)) {
 | 
			
		||||
                console.warn(`DEPRECATION WARNING: Migrate type ${legacyDefinition.key} from ${legacyDefinition.bundle.path} to use the new Types API.  Legacy type support will be removed soon.`);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        openmct.types.importLegacyTypes(types);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2021, United States Government
 | 
			
		||||
 * Open openmct, 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
 | 
			
		||||
 * Open openmct 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.
 | 
			
		||||
@@ -14,30 +14,33 @@
 | 
			
		||||
 * License for the specific language governing permissions and limitations
 | 
			
		||||
 * under the License.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT includes source code licensed under additional open source
 | 
			
		||||
 * Open openmct 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>
 | 
			
		||||
<li class="c-inspect-properties__row">
 | 
			
		||||
    <div class="c-inspect-properties__label">
 | 
			
		||||
        {{ detail.name }}
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="c-inspect-properties__value">
 | 
			
		||||
        {{ detail.value }}
 | 
			
		||||
    </div>
 | 
			
		||||
</li>
 | 
			
		||||
</template>
 | 
			
		||||
define([
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    props: {
 | 
			
		||||
        detail: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
            required: true
 | 
			
		||||
], function (
 | 
			
		||||
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    function checkForDeprecatedFunctionality(typeDef) {
 | 
			
		||||
        if (Object.prototype.hasOwnProperty.call(typeDef, 'telemetry')) {
 | 
			
		||||
            console.warn(
 | 
			
		||||
                'DEPRECATION WARNING: Telemetry data on type '
 | 
			
		||||
                + 'registrations will be deprecated in a future version, '
 | 
			
		||||
                + 'please convert to a custom telemetry metadata provider '
 | 
			
		||||
                + 'for type: ' + typeDef.key
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
    function TypeDeprecationChecker(types) {
 | 
			
		||||
        types.forEach(checkForDeprecatedFunctionality);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return TypeDeprecationChecker;
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
@@ -133,13 +133,9 @@ define([
 | 
			
		||||
 | 
			
		||||
        return this.objectService.getObjects([keyString])
 | 
			
		||||
            .then(function (results) {
 | 
			
		||||
                if (results[keyString]) {
 | 
			
		||||
                    let model = results[keyString].getModel();
 | 
			
		||||
                let model = results[keyString].getModel();
 | 
			
		||||
 | 
			
		||||
                    return utils.toNewFormat(model, key);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
                return utils.toNewFormat(model, key);
 | 
			
		||||
            });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										97
									
								
								src/adapter/services/MissingModelCompatibilityDecorator.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/adapter/services/MissingModelCompatibilityDecorator.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * 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([
 | 
			
		||||
    'objectUtils'
 | 
			
		||||
], function (
 | 
			
		||||
    objectUtils
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Compatibility decorator for New API.
 | 
			
		||||
     *
 | 
			
		||||
     * When the model service returns no results, this attempts to load
 | 
			
		||||
     * the model from the new Object API and returns that instead.  In order
 | 
			
		||||
     * to prevent infinite recursion, this only tries to fetch from the API
 | 
			
		||||
     * a single time.
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    function MissingModelCompatibilityDecorator(api, modelService) {
 | 
			
		||||
        this.api = api;
 | 
			
		||||
        this.modelService = modelService;
 | 
			
		||||
        this.apiFetching = {}; // to prevent loops, if we have already
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetch a set of ids from the public api and return a promise for their
 | 
			
		||||
     * models.  If a model is requested twice, respond with a missing result.
 | 
			
		||||
     */
 | 
			
		||||
    MissingModelCompatibilityDecorator.prototype.apiFetch = function (ids) {
 | 
			
		||||
        const results = {};
 | 
			
		||||
 | 
			
		||||
        const promises = ids.map(function (id) {
 | 
			
		||||
            if (this.apiFetching[id]) {
 | 
			
		||||
                return Promise.resolve();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.apiFetching[id] = true;
 | 
			
		||||
 | 
			
		||||
            return this.api.objects.get(objectUtils.parseKeyString(id))
 | 
			
		||||
                .then(function (newDO) {
 | 
			
		||||
                    results[id] = objectUtils.toOldFormat(newDO);
 | 
			
		||||
                });
 | 
			
		||||
        }, this);
 | 
			
		||||
 | 
			
		||||
        return Promise.all(promises).then(function () {
 | 
			
		||||
            return results;
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return a promise for model results based on provided ids.  Will attempt
 | 
			
		||||
     * to fetch any missing results from the object api.
 | 
			
		||||
     */
 | 
			
		||||
    MissingModelCompatibilityDecorator.prototype.getModels = function (ids) {
 | 
			
		||||
        return this.modelService.getModels(ids)
 | 
			
		||||
            .then(function (models) {
 | 
			
		||||
                const missingIds = ids.filter(function (id) {
 | 
			
		||||
                    return !models[id];
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                if (!missingIds.length) {
 | 
			
		||||
                    return models;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return this.apiFetch(missingIds)
 | 
			
		||||
                    .then(function (apiResults) {
 | 
			
		||||
                        Object.keys(apiResults).forEach(function (k) {
 | 
			
		||||
                            models[k] = apiResults[k];
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        return models;
 | 
			
		||||
                    });
 | 
			
		||||
            }.bind(this));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return MissingModelCompatibilityDecorator;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -15,6 +15,8 @@ define([
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    function LegacyViewProvider(legacyView, openmct, convertToLegacyObject) {
 | 
			
		||||
        console.warn(`DEPRECATION WARNING: Migrate ${legacyView.key} from ${legacyView.bundle.path} to use the new View APIs.  Legacy view support will be removed soon.`);
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            key: legacyView.key,
 | 
			
		||||
            name: legacyView.name,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ define([
 | 
			
		||||
 | 
			
		||||
) {
 | 
			
		||||
    function TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject) {
 | 
			
		||||
        console.warn(`DEPRECATION WARNING: Migrate ${typeDefinition.key} from ${typeDefinition.bundle.path} to use the new Inspector View APIs.  Legacy Inspector view support will be removed soon.`);
 | 
			
		||||
        let representation = openmct.$injector.get('representations[]')
 | 
			
		||||
            .filter((r) => r.key === typeDefinition.inspector)[0];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,7 @@ export default class Editor extends EventEmitter {
 | 
			
		||||
     * Initiate an editing session. This will start a transaction during
 | 
			
		||||
     * which any persist operations will be deferred until either save()
 | 
			
		||||
     * or finish() are called.
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    edit() {
 | 
			
		||||
        if (this.editing === true) {
 | 
			
		||||
@@ -41,8 +42,8 @@ export default class Editor extends EventEmitter {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.editing = true;
 | 
			
		||||
        this.getTransactionService().startTransaction();
 | 
			
		||||
        this.emit('isEditing', true);
 | 
			
		||||
        this.openmct.objects.startTransaction();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -55,36 +56,41 @@ export default class Editor extends EventEmitter {
 | 
			
		||||
    /**
 | 
			
		||||
     * Save any unsaved changes from this editing session. This will
 | 
			
		||||
     * end the current transaction.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    save() {
 | 
			
		||||
        const transaction = this.openmct.objects.getActiveTransaction();
 | 
			
		||||
        return this.getTransactionService().commit().then((result) => {
 | 
			
		||||
            this.editing = false;
 | 
			
		||||
            this.emit('isEditing', false);
 | 
			
		||||
 | 
			
		||||
        return transaction.commit()
 | 
			
		||||
            .then(() => {
 | 
			
		||||
                this.editing = false;
 | 
			
		||||
                this.emit('isEditing', false);
 | 
			
		||||
            }).catch(error => {
 | 
			
		||||
                throw error;
 | 
			
		||||
            }).finally(() => {
 | 
			
		||||
                this.openmct.objects.endTransaction();
 | 
			
		||||
            });
 | 
			
		||||
            return result;
 | 
			
		||||
        }).catch((error) => {
 | 
			
		||||
            throw error;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * End the currently active transaction and discard unsaved changes.
 | 
			
		||||
     *
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    cancel() {
 | 
			
		||||
        let cancelPromise = this.getTransactionService().cancel();
 | 
			
		||||
        this.editing = false;
 | 
			
		||||
        this.emit('isEditing', false);
 | 
			
		||||
 | 
			
		||||
        return new Promise((resolve, reject) => {
 | 
			
		||||
            const transaction = this.openmct.objects.getActiveTransaction();
 | 
			
		||||
            transaction.cancel()
 | 
			
		||||
                .then(resolve)
 | 
			
		||||
                .catch(reject)
 | 
			
		||||
                .finally(() => {
 | 
			
		||||
                    this.openmct.objects.endTransaction();
 | 
			
		||||
                });
 | 
			
		||||
        });
 | 
			
		||||
        return cancelPromise;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    getTransactionService() {
 | 
			
		||||
        if (!this.transactionService) {
 | 
			
		||||
            this.transactionService = this.openmct.$injector.get('transactionService');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.transactionService;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -110,7 +110,7 @@ class ActionsAPI extends EventEmitter {
 | 
			
		||||
        return actionsObject;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _groupAndSortActions(actionsArray = []) {
 | 
			
		||||
    _groupAndSortActions(actionsArray) {
 | 
			
		||||
        if (!Array.isArray(actionsArray) && typeof actionsArray === 'object') {
 | 
			
		||||
            actionsArray = Object.keys(actionsArray).map(key => actionsArray[key]);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -31,8 +31,7 @@ define([
 | 
			
		||||
    './Editor',
 | 
			
		||||
    './menu/MenuAPI',
 | 
			
		||||
    './actions/ActionsAPI',
 | 
			
		||||
    './status/StatusAPI',
 | 
			
		||||
    './priority/PriorityAPI'
 | 
			
		||||
    './status/StatusAPI'
 | 
			
		||||
], function (
 | 
			
		||||
    TimeAPI,
 | 
			
		||||
    ObjectAPI,
 | 
			
		||||
@@ -44,11 +43,10 @@ define([
 | 
			
		||||
    EditorAPI,
 | 
			
		||||
    MenuAPI,
 | 
			
		||||
    ActionsAPI,
 | 
			
		||||
    StatusAPI,
 | 
			
		||||
    PriorityAPI
 | 
			
		||||
    StatusAPI
 | 
			
		||||
) {
 | 
			
		||||
    return {
 | 
			
		||||
        TimeAPI: TimeAPI.default,
 | 
			
		||||
        TimeAPI: TimeAPI,
 | 
			
		||||
        ObjectAPI: ObjectAPI,
 | 
			
		||||
        CompositionAPI: CompositionAPI,
 | 
			
		||||
        TypeRegistry: TypeRegistry,
 | 
			
		||||
@@ -58,7 +56,6 @@ define([
 | 
			
		||||
        EditorAPI: EditorAPI,
 | 
			
		||||
        MenuAPI: MenuAPI.default,
 | 
			
		||||
        ActionsAPI: ActionsAPI.default,
 | 
			
		||||
        StatusAPI: StatusAPI.default,
 | 
			
		||||
        PriorityAPI: PriorityAPI.default
 | 
			
		||||
        StatusAPI: StatusAPI.default
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -31,22 +31,14 @@ define([
 | 
			
		||||
        this.indicatorObjects = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    IndicatorAPI.prototype.getIndicatorObjectsByPriority = function () {
 | 
			
		||||
        const sortedIndicators = this.indicatorObjects.sort((a, b) => b.priority - a.priority);
 | 
			
		||||
 | 
			
		||||
        return sortedIndicators;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    IndicatorAPI.prototype.simpleIndicator = function () {
 | 
			
		||||
        return new SimpleIndicator(this.openmct);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accepts an indicator object, which is a simple object
 | 
			
		||||
     * with a two attributes: 'element' which has an HTMLElement
 | 
			
		||||
     * as its value, and 'priority' with an integer that specifies its order in the layout.
 | 
			
		||||
     * The lower the priority, the further to the right the element is placed.
 | 
			
		||||
     * If undefined, the priority will be assigned -1.
 | 
			
		||||
     * with a single attribute, 'element' which has an HTMLElement
 | 
			
		||||
     * as its value.
 | 
			
		||||
     *
 | 
			
		||||
     * We provide .simpleIndicator() as a convenience function
 | 
			
		||||
     * which will create a default Open MCT indicator that can
 | 
			
		||||
@@ -55,7 +47,7 @@ define([
 | 
			
		||||
     * and dynamic behavior.
 | 
			
		||||
     *
 | 
			
		||||
     * Eg.
 | 
			
		||||
     * const myIndicator = openmct.indicators.simpleIndicator();
 | 
			
		||||
     * var myIndicator = openmct.indicators.simpleIndicator();
 | 
			
		||||
     * openmct.indicators.add(myIndicator);
 | 
			
		||||
     *
 | 
			
		||||
     * myIndicator.text("Hello World!");
 | 
			
		||||
@@ -63,10 +55,6 @@ define([
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    IndicatorAPI.prototype.add = function (indicator) {
 | 
			
		||||
        if (!indicator.priority) {
 | 
			
		||||
            indicator.priority = this.openmct.priority.DEFAULT;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.indicatorObjects.push(indicator);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,64 +19,97 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
import { createOpenMct, resetApplicationState } from '../../utils/testing';
 | 
			
		||||
import SimpleIndicator from './SimpleIndicator';
 | 
			
		||||
 | 
			
		||||
describe("The Indicator API", () => {
 | 
			
		||||
    let openmct;
 | 
			
		||||
define(
 | 
			
		||||
    [
 | 
			
		||||
        "../../MCT",
 | 
			
		||||
        "../../../platform/commonUI/general/src/directives/MCTIndicators"
 | 
			
		||||
    ],
 | 
			
		||||
    function (
 | 
			
		||||
        MCT,
 | 
			
		||||
        MCTIndicators
 | 
			
		||||
    ) {
 | 
			
		||||
        xdescribe("The Indicator API", function () {
 | 
			
		||||
            let openmct;
 | 
			
		||||
            let directive;
 | 
			
		||||
            let holderElement;
 | 
			
		||||
 | 
			
		||||
    beforeEach(() => {
 | 
			
		||||
        openmct = createOpenMct();
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                openmct = new MCT();
 | 
			
		||||
                directive = new MCTIndicators(openmct);
 | 
			
		||||
                holderElement = document.createElement('div');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("The simple indicator", function () {
 | 
			
		||||
                let simpleIndicator;
 | 
			
		||||
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    simpleIndicator = openmct.indicators.simpleIndicator();
 | 
			
		||||
                    openmct.indicators.add(simpleIndicator);
 | 
			
		||||
                    renderIndicators();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("applies the set icon class", function () {
 | 
			
		||||
                    simpleIndicator.iconClass('testIconClass');
 | 
			
		||||
 | 
			
		||||
                    expect(getIconElement().classList.contains('testIconClass')).toBe(true);
 | 
			
		||||
 | 
			
		||||
                    simpleIndicator.iconClass('anotherIconClass');
 | 
			
		||||
                    expect(getIconElement().classList.contains('testIconClass')).toBe(false);
 | 
			
		||||
                    expect(getIconElement().classList.contains('anotherIconClass')).toBe(true);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("applies the set status class", function () {
 | 
			
		||||
                    simpleIndicator.statusClass('testStatusClass');
 | 
			
		||||
 | 
			
		||||
                    expect(getIconElement().classList.contains('testStatusClass')).toBe(true);
 | 
			
		||||
                    simpleIndicator.statusClass('anotherStatusClass');
 | 
			
		||||
                    expect(getIconElement().classList.contains('testStatusClass')).toBe(false);
 | 
			
		||||
                    expect(getIconElement().classList.contains('anotherStatusClass')).toBe(true);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("displays the set text", function () {
 | 
			
		||||
                    simpleIndicator.text('some test text');
 | 
			
		||||
                    expect(getTextElement().textContent.trim()).toEqual('some test text');
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("sets the indicator's title", function () {
 | 
			
		||||
                    simpleIndicator.description('a test description');
 | 
			
		||||
                    expect(getIndicatorElement().getAttribute('title')).toEqual('a test description');
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("Hides indicator icon if no text is set", function () {
 | 
			
		||||
                    simpleIndicator.text('');
 | 
			
		||||
                    expect(getIndicatorElement().classList.contains('hidden')).toBe(true);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                function getIconElement() {
 | 
			
		||||
                    return holderElement.querySelector('.ls-indicator');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function getIndicatorElement() {
 | 
			
		||||
                    return holderElement.querySelector('.ls-indicator');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                function getTextElement() {
 | 
			
		||||
                    return holderElement.querySelector('.indicator-text');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("Supports registration of a completely custom indicator", function () {
 | 
			
		||||
                const customIndicator = document.createElement('div');
 | 
			
		||||
                customIndicator.classList.add('customIndicator');
 | 
			
		||||
                customIndicator.textContent = 'A custom indicator';
 | 
			
		||||
 | 
			
		||||
                openmct.indicators.add({element: customIndicator});
 | 
			
		||||
                renderIndicators();
 | 
			
		||||
 | 
			
		||||
                expect(holderElement.querySelector('.customIndicator').textContent.trim()).toEqual('A custom indicator');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            function renderIndicators() {
 | 
			
		||||
                directive.link({}, holderElement);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
        return resetApplicationState(openmct);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function generateIndicator(className, label, priority) {
 | 
			
		||||
        const element = document.createElement('div');
 | 
			
		||||
        element.classList.add(className);
 | 
			
		||||
        const textNode = document.createTextNode(label);
 | 
			
		||||
        element.appendChild(textNode);
 | 
			
		||||
        const testIndicator = {
 | 
			
		||||
            element,
 | 
			
		||||
            priority
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return testIndicator;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    it("can register an indicator", () => {
 | 
			
		||||
        const testIndicator = generateIndicator('test-indicator', 'This is a test indicator', 2);
 | 
			
		||||
        openmct.indicators.add(testIndicator);
 | 
			
		||||
        expect(openmct.indicators.indicatorObjects).toBeDefined();
 | 
			
		||||
        // notifier indicator is installed by default
 | 
			
		||||
        expect(openmct.indicators.indicatorObjects.length).toBe(2);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("can order indicators based on priority", () => {
 | 
			
		||||
        const testIndicator1 = generateIndicator('test-indicator-1', 'This is a test indicator', openmct.priority.LOW);
 | 
			
		||||
        openmct.indicators.add(testIndicator1);
 | 
			
		||||
 | 
			
		||||
        const testIndicator2 = generateIndicator('test-indicator-2', 'This is another test indicator', openmct.priority.DEFAULT);
 | 
			
		||||
        openmct.indicators.add(testIndicator2);
 | 
			
		||||
 | 
			
		||||
        const testIndicator3 = generateIndicator('test-indicator-3', 'This is yet another test indicator', openmct.priority.LOW);
 | 
			
		||||
        openmct.indicators.add(testIndicator3);
 | 
			
		||||
 | 
			
		||||
        const testIndicator4 = generateIndicator('test-indicator-4', 'This is yet another test indicator', openmct.priority.HIGH);
 | 
			
		||||
        openmct.indicators.add(testIndicator4);
 | 
			
		||||
 | 
			
		||||
        expect(openmct.indicators.indicatorObjects.length).toBe(5);
 | 
			
		||||
        const indicatorObjectsByPriority = openmct.indicators.getIndicatorObjectsByPriority();
 | 
			
		||||
        expect(indicatorObjectsByPriority.length).toBe(5);
 | 
			
		||||
        expect(indicatorObjectsByPriority[2].priority).toBe(openmct.priority.DEFAULT);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("the simple indicator can be added", () => {
 | 
			
		||||
        const simpleIndicator = new SimpleIndicator(openmct);
 | 
			
		||||
        openmct.indicators.add(simpleIndicator);
 | 
			
		||||
 | 
			
		||||
        expect(openmct.indicators.indicatorObjects.length).toBe(2);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,6 @@ define(['zepto', './res/indicator-template.html'],
 | 
			
		||||
        function SimpleIndicator(openmct) {
 | 
			
		||||
            this.openmct = openmct;
 | 
			
		||||
            this.element = $(indicatorTemplate)[0];
 | 
			
		||||
            this.priority = openmct.priority.DEFAULT;
 | 
			
		||||
 | 
			
		||||
            this.textElement = this.element.querySelector('.js-indicator-text');
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +0,0 @@
 | 
			
		||||
export default class ConflictError extends Error {
 | 
			
		||||
}
 | 
			
		||||
@@ -129,7 +129,9 @@ class MutableDomainObject {
 | 
			
		||||
 | 
			
		||||
        mutable.$observe('$_synchronize_model', (updatedObject) => {
 | 
			
		||||
            let clone = JSON.parse(JSON.stringify(updatedObject));
 | 
			
		||||
            utils.refresh(mutable, clone);
 | 
			
		||||
            let deleted = _.difference(Object.keys(mutable), Object.keys(updatedObject));
 | 
			
		||||
            deleted.forEach((propertyName) => delete mutable[propertyName]);
 | 
			
		||||
            Object.assign(mutable, clone);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return mutable;
 | 
			
		||||
 
 | 
			
		||||
@@ -26,8 +26,6 @@ import RootRegistry from './RootRegistry';
 | 
			
		||||
import RootObjectProvider from './RootObjectProvider';
 | 
			
		||||
import EventEmitter from 'EventEmitter';
 | 
			
		||||
import InterceptorRegistry from './InterceptorRegistry';
 | 
			
		||||
import Transaction from './Transaction';
 | 
			
		||||
import ConflictError from './ConflictError';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Utilities for loading, saving, and manipulating domain objects.
 | 
			
		||||
@@ -36,13 +34,12 @@ import ConflictError from './ConflictError';
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
function ObjectAPI(typeRegistry, openmct) {
 | 
			
		||||
    this.openmct = openmct;
 | 
			
		||||
    this.typeRegistry = typeRegistry;
 | 
			
		||||
    this.eventEmitter = new EventEmitter();
 | 
			
		||||
    this.providers = {};
 | 
			
		||||
    this.rootRegistry = new RootRegistry();
 | 
			
		||||
    this.injectIdentifierService = function () {
 | 
			
		||||
        this.identifierService = this.openmct.$injector.get("identifierService");
 | 
			
		||||
        this.identifierService = openmct.$injector.get("identifierService");
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.rootProvider = new RootObjectProvider(this.rootRegistry);
 | 
			
		||||
@@ -50,10 +47,6 @@ function ObjectAPI(typeRegistry, openmct) {
 | 
			
		||||
    this.interceptorRegistry = new InterceptorRegistry();
 | 
			
		||||
 | 
			
		||||
    this.SYNCHRONIZED_OBJECT_TYPES = ['notebook', 'plan'];
 | 
			
		||||
 | 
			
		||||
    this.errors = {
 | 
			
		||||
        Conflict: ConflictError
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -93,14 +86,6 @@ ObjectAPI.prototype.getProvider = function (identifier) {
 | 
			
		||||
    return this.providers[namespace] || this.fallbackProvider;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get an active transaction instance
 | 
			
		||||
 * @returns {Transaction} a transaction object
 | 
			
		||||
 */
 | 
			
		||||
ObjectAPI.prototype.getActiveTransaction = function () {
 | 
			
		||||
    return this.transaction;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get the root-level object.
 | 
			
		||||
 * @returns {Promise.<DomainObject>} a promise for the root object
 | 
			
		||||
@@ -184,15 +169,6 @@ ObjectAPI.prototype.get = function (identifier, abortSignal) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    identifier = utils.parseKeyString(identifier);
 | 
			
		||||
    let dirtyObject;
 | 
			
		||||
    if (this.isTransactionActive()) {
 | 
			
		||||
        dirtyObject = this.transaction.getDirtyObject(keystring);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (dirtyObject) {
 | 
			
		||||
        return Promise.resolve(dirtyObject);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const provider = this.getProvider(identifier);
 | 
			
		||||
 | 
			
		||||
    if (!provider) {
 | 
			
		||||
@@ -205,22 +181,7 @@ ObjectAPI.prototype.get = function (identifier, abortSignal) {
 | 
			
		||||
 | 
			
		||||
    let objectPromise = provider.get(identifier, abortSignal).then(result => {
 | 
			
		||||
        delete this.cache[keystring];
 | 
			
		||||
 | 
			
		||||
        result = this.applyGetInterceptors(identifier, result);
 | 
			
		||||
        if (result.isMutable) {
 | 
			
		||||
            result.$refresh(result);
 | 
			
		||||
        } else {
 | 
			
		||||
            let mutableDomainObject = this._toMutable(result);
 | 
			
		||||
            mutableDomainObject.$refresh(result);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    }).catch((result) => {
 | 
			
		||||
        console.warn(`Failed to retrieve ${keystring}:`, result);
 | 
			
		||||
 | 
			
		||||
        delete this.cache[keystring];
 | 
			
		||||
 | 
			
		||||
        result = this.applyGetInterceptors(identifier);
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    });
 | 
			
		||||
@@ -311,13 +272,6 @@ ObjectAPI.prototype.isPersistable = function (idOrKeyString) {
 | 
			
		||||
        && provider.update !== undefined;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ObjectAPI.prototype.isMissing = function (domainObject) {
 | 
			
		||||
    let identifier = utils.makeKeyString(domainObject.identifier);
 | 
			
		||||
    let missingName = 'Missing: ' + identifier;
 | 
			
		||||
 | 
			
		||||
    return domainObject.name === missingName;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Save this domain object in its current state. EXPERIMENTAL
 | 
			
		||||
 *
 | 
			
		||||
@@ -331,7 +285,6 @@ ObjectAPI.prototype.isMissing = function (domainObject) {
 | 
			
		||||
ObjectAPI.prototype.save = function (domainObject) {
 | 
			
		||||
    let provider = this.getProvider(domainObject.identifier);
 | 
			
		||||
    let savedResolve;
 | 
			
		||||
    let savedReject;
 | 
			
		||||
    let result;
 | 
			
		||||
 | 
			
		||||
    if (!this.isPersistable(domainObject.identifier)) {
 | 
			
		||||
@@ -341,22 +294,14 @@ ObjectAPI.prototype.save = function (domainObject) {
 | 
			
		||||
    } else {
 | 
			
		||||
        const persistedTime = Date.now();
 | 
			
		||||
        if (domainObject.persisted === undefined) {
 | 
			
		||||
            result = new Promise((resolve, reject) => {
 | 
			
		||||
            result = new Promise((resolve) => {
 | 
			
		||||
                savedResolve = resolve;
 | 
			
		||||
                savedReject = reject;
 | 
			
		||||
            });
 | 
			
		||||
            domainObject.persisted = persistedTime;
 | 
			
		||||
            const newObjectPromise = provider.create(domainObject);
 | 
			
		||||
            if (newObjectPromise) {
 | 
			
		||||
                newObjectPromise.then(response => {
 | 
			
		||||
                    this.mutate(domainObject, 'persisted', persistedTime);
 | 
			
		||||
                    savedResolve(response);
 | 
			
		||||
                }).catch((error) => {
 | 
			
		||||
                    savedReject(error);
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                result = Promise.reject(`[ObjectAPI][save] Object provider returned ${newObjectPromise} when creating new object.`);
 | 
			
		||||
            }
 | 
			
		||||
            provider.create(domainObject).then((response) => {
 | 
			
		||||
                this.mutate(domainObject, 'persisted', persistedTime);
 | 
			
		||||
                savedResolve(response);
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            domainObject.persisted = persistedTime;
 | 
			
		||||
            this.mutate(domainObject, 'persisted', persistedTime);
 | 
			
		||||
@@ -367,24 +312,6 @@ ObjectAPI.prototype.save = function (domainObject) {
 | 
			
		||||
    return result;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * After entering into edit mode, creates a new instance of Transaction to keep track of changes in Objects
 | 
			
		||||
 */
 | 
			
		||||
ObjectAPI.prototype.startTransaction = function () {
 | 
			
		||||
    if (this.isTransactionActive()) {
 | 
			
		||||
        throw new Error("Unable to start new Transaction: Previous Transaction is active");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.transaction = new Transaction(this);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Clear instance of Transaction
 | 
			
		||||
 */
 | 
			
		||||
ObjectAPI.prototype.endTransaction = function () {
 | 
			
		||||
    this.transaction = null;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add a root-level object.
 | 
			
		||||
 * @param {module:openmct.ObjectAPI~Identifier|function} an array of
 | 
			
		||||
@@ -474,29 +401,6 @@ ObjectAPI.prototype.mutate = function (domainObject, path, value) {
 | 
			
		||||
        //Destroy temporary mutable object
 | 
			
		||||
        this.destroyMutable(mutableDomainObject);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this.isTransactionActive()) {
 | 
			
		||||
        this.transaction.add(domainObject);
 | 
			
		||||
    } else {
 | 
			
		||||
        this.save(domainObject);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Updates a domain object based on its latest persisted state. Note that this will mutate the provided object.
 | 
			
		||||
 * @param {module:openmct.DomainObject} domainObject an object to refresh from its persistence store
 | 
			
		||||
 * @returns {Promise} the provided object, updated to reflect the latest persisted state of the object.
 | 
			
		||||
 */
 | 
			
		||||
ObjectAPI.prototype.refresh = async function (domainObject) {
 | 
			
		||||
    const refreshedObject = await this.get(domainObject.identifier);
 | 
			
		||||
 | 
			
		||||
    if (domainObject.isMutable) {
 | 
			
		||||
        domainObject.$refresh(refreshedObject);
 | 
			
		||||
    } else {
 | 
			
		||||
        utils.refresh(domainObject, refreshedObject);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return domainObject;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -533,23 +437,6 @@ ObjectAPI.prototype._toMutable = function (object) {
 | 
			
		||||
    return mutableObject;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Updates a domain object based on its latest persisted state. Note that this will mutate the provided object.
 | 
			
		||||
 * @param {module:openmct.DomainObject} domainObject an object to refresh from its persistence store
 | 
			
		||||
 * @returns {Promise} the provided object, updated to reflect the latest persisted state of the object.
 | 
			
		||||
 */
 | 
			
		||||
ObjectAPI.prototype.refresh = async function (domainObject) {
 | 
			
		||||
    const refreshedObject = await this.get(domainObject.identifier);
 | 
			
		||||
 | 
			
		||||
    if (domainObject.isMutable) {
 | 
			
		||||
        domainObject.$refresh(refreshedObject);
 | 
			
		||||
    } else {
 | 
			
		||||
        utils.refresh(domainObject, refreshedObject);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return domainObject;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param module:openmct.ObjectAPI~Identifier identifier An object identifier
 | 
			
		||||
 * @returns {boolean} true if the object can be mutated, otherwise returns false
 | 
			
		||||
@@ -626,10 +513,6 @@ ObjectAPI.prototype.isObjectPathToALink = function (domainObject, objectPath) {
 | 
			
		||||
        && domainObject.location !== this.makeKeyString(objectPath[1].identifier);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ObjectAPI.prototype.isTransactionActive = function () {
 | 
			
		||||
    return Boolean(this.transaction && this.openmct.editor.isEditing());
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Uniquely identifies a domain object.
 | 
			
		||||
 *
 | 
			
		||||
 
 | 
			
		||||
@@ -26,10 +26,6 @@ describe("The Object API", () => {
 | 
			
		||||
 | 
			
		||||
        openmct.$injector.get.and.returnValue(mockIdentifierService);
 | 
			
		||||
        objectAPI = new ObjectAPI(typeRegistry, openmct);
 | 
			
		||||
 | 
			
		||||
        openmct.editor = {};
 | 
			
		||||
        openmct.editor.isEditing = () => false;
 | 
			
		||||
 | 
			
		||||
        mockDomainObject = {
 | 
			
		||||
            identifier: {
 | 
			
		||||
                namespace: TEST_NAMESPACE,
 | 
			
		||||
@@ -227,28 +223,6 @@ describe("The Object API", () => {
 | 
			
		||||
            expect(testObject.name).toBe(MUTATED_NAME);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('Provides a way of refreshing an object from the persistence store', () => {
 | 
			
		||||
            const modifiedTestObject = JSON.parse(JSON.stringify(testObject));
 | 
			
		||||
            const OTHER_ATTRIBUTE_VALUE = 'Modified value';
 | 
			
		||||
            const NEW_ATTRIBUTE_VALUE = 'A new attribute';
 | 
			
		||||
            modifiedTestObject.otherAttribute = OTHER_ATTRIBUTE_VALUE;
 | 
			
		||||
            modifiedTestObject.newAttribute = NEW_ATTRIBUTE_VALUE;
 | 
			
		||||
            delete modifiedTestObject.objectAttribute;
 | 
			
		||||
 | 
			
		||||
            spyOn(objectAPI, 'get');
 | 
			
		||||
            objectAPI.get.and.returnValue(Promise.resolve(modifiedTestObject));
 | 
			
		||||
 | 
			
		||||
            expect(objectAPI.get).not.toHaveBeenCalled();
 | 
			
		||||
 | 
			
		||||
            return objectAPI.refresh(testObject).then(() => {
 | 
			
		||||
                expect(objectAPI.get).toHaveBeenCalledWith(testObject.identifier);
 | 
			
		||||
 | 
			
		||||
                expect(testObject.otherAttribute).toEqual(OTHER_ATTRIBUTE_VALUE);
 | 
			
		||||
                expect(testObject.newAttribute).toEqual(NEW_ATTRIBUTE_VALUE);
 | 
			
		||||
                expect(testObject.objectAttribute).not.toBeDefined();
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe ('uses a MutableDomainObject', () => {
 | 
			
		||||
            it('and retains properties of original object ', function () {
 | 
			
		||||
                expect(hasOwnProperty(mutable, 'identifier')).toBe(true);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,82 +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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
export default class Transaction {
 | 
			
		||||
    constructor(objectAPI) {
 | 
			
		||||
        this.dirtyObjects = new Set();
 | 
			
		||||
        this.objectAPI = objectAPI;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    add(object) {
 | 
			
		||||
        this.dirtyObjects.add(object);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    cancel() {
 | 
			
		||||
        return this._clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    commit() {
 | 
			
		||||
        const promiseArray = [];
 | 
			
		||||
        const save = this.objectAPI.save.bind(this.objectAPI);
 | 
			
		||||
        this.dirtyObjects.forEach(object => {
 | 
			
		||||
            promiseArray.push(this.createDirtyObjectPromise(object, save));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return Promise.all(promiseArray);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    createDirtyObjectPromise(object, action) {
 | 
			
		||||
        return new Promise((resolve, reject) => {
 | 
			
		||||
            action(object)
 | 
			
		||||
                .then(resolve)
 | 
			
		||||
                .catch(reject)
 | 
			
		||||
                .finally(() => {
 | 
			
		||||
                    this.dirtyObjects.delete(object);
 | 
			
		||||
                });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getDirtyObject(keystring) {
 | 
			
		||||
        let dirtyObject;
 | 
			
		||||
        this.dirtyObjects.forEach(object => {
 | 
			
		||||
            if (this.objectAPI.makeKeyString(object.identifier) === keystring) {
 | 
			
		||||
                dirtyObject = object;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return dirtyObject;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    start() {
 | 
			
		||||
        this.dirtyObjects = new Set();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _clear() {
 | 
			
		||||
        const promiseArray = [];
 | 
			
		||||
        const refresh = this.objectAPI.refresh.bind(this.objectAPI);
 | 
			
		||||
        this.dirtyObjects.forEach(object => {
 | 
			
		||||
            promiseArray.push(this.createDirtyObjectPromise(object, refresh));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return Promise.all(promiseArray);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -165,19 +165,12 @@ define([
 | 
			
		||||
        return identifierEquals(a.identifier, b.identifier);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function refresh(oldObject, newObject) {
 | 
			
		||||
        let deleted = _.difference(Object.keys(oldObject), Object.keys(newObject));
 | 
			
		||||
        deleted.forEach((propertyName) => delete oldObject[propertyName]);
 | 
			
		||||
        Object.assign(oldObject, newObject);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        toOldFormat: toOldFormat,
 | 
			
		||||
        toNewFormat: toNewFormat,
 | 
			
		||||
        makeKeyString: makeKeyString,
 | 
			
		||||
        parseKeyString: parseKeyString,
 | 
			
		||||
        equals: objectEquals,
 | 
			
		||||
        identifierEquals: identifierEquals,
 | 
			
		||||
        refresh: refresh
 | 
			
		||||
        identifierEquals: identifierEquals
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +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.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
const PRIORITIES = Object.freeze({
 | 
			
		||||
    HIGH: 1000,
 | 
			
		||||
    DEFAULT: 0,
 | 
			
		||||
    LOW: -1000
 | 
			
		||||
});
 | 
			
		||||
export default PRIORITIES;
 | 
			
		||||
@@ -180,6 +180,12 @@ define([
 | 
			
		||||
     * @memberof module:openmct.TelemetryAPI~TelemetryProvider#
 | 
			
		||||
     */
 | 
			
		||||
    TelemetryAPI.prototype.canProvideTelemetry = function (domainObject) {
 | 
			
		||||
        console.warn(
 | 
			
		||||
            'DEPRECATION WARNING: openmct.telemetry.canProvideTelemetry '
 | 
			
		||||
            + 'will not be supported in future versions of Open MCT.  Please '
 | 
			
		||||
            + 'use openmct.telemetry.isTelemetryObject instead.'
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return Boolean(this.findSubscriptionProvider(domainObject))
 | 
			
		||||
               || Boolean(this.findRequestProvider(domainObject));
 | 
			
		||||
    };
 | 
			
		||||
@@ -477,10 +483,6 @@ define([
 | 
			
		||||
     * @returns {Object<String, {TelemetryValueFormatter}>}
 | 
			
		||||
     */
 | 
			
		||||
    TelemetryAPI.prototype.getFormatMap = function (metadata) {
 | 
			
		||||
        if (!metadata) {
 | 
			
		||||
            return {};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.formatMapCache.has(metadata)) {
 | 
			
		||||
            const formatMap = metadata.values().reduce(function (map, valueMetadata) {
 | 
			
		||||
                map[valueMetadata.key] = this.getValueFormatter(valueMetadata);
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,7 @@ export class TelemetryCollection extends EventEmitter {
 | 
			
		||||
        this.parseTime = undefined;
 | 
			
		||||
        this.metadata = this.openmct.telemetry.getMetadata(domainObject);
 | 
			
		||||
        this.unsubscribe = undefined;
 | 
			
		||||
        this.historicalProvider = undefined;
 | 
			
		||||
        this.options = options;
 | 
			
		||||
        this.pageState = undefined;
 | 
			
		||||
        this.lastBounds = undefined;
 | 
			
		||||
@@ -64,13 +65,13 @@ export class TelemetryCollection extends EventEmitter {
 | 
			
		||||
            this._error(ERRORS.LOADED);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this._setTimeSystem(this.openmct.time.timeSystem());
 | 
			
		||||
        this._timeSystem(this.openmct.time.timeSystem());
 | 
			
		||||
        this.lastBounds = this.openmct.time.bounds();
 | 
			
		||||
 | 
			
		||||
        this._watchBounds();
 | 
			
		||||
        this._watchTimeSystem();
 | 
			
		||||
 | 
			
		||||
        this._requestHistoricalTelemetry();
 | 
			
		||||
        this._initiateHistoricalRequests();
 | 
			
		||||
        this._initiateSubscriptionTelemetry();
 | 
			
		||||
 | 
			
		||||
        this.loaded = true;
 | 
			
		||||
@@ -102,49 +103,43 @@ export class TelemetryCollection extends EventEmitter {
 | 
			
		||||
        return this.boundedTelemetry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets up  the telemetry collection for historical requests,
 | 
			
		||||
     * this uses the "standardizeRequestOptions" from Telemetry API
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _initiateHistoricalRequests() {
 | 
			
		||||
        this.openmct.telemetry.standardizeRequestOptions(this.options);
 | 
			
		||||
        this.historicalProvider = this.openmct.telemetry.
 | 
			
		||||
            findRequestProvider(this.domainObject, this.options);
 | 
			
		||||
 | 
			
		||||
        this._requestHistoricalTelemetry();
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * If a historical provider exists, then historical requests will be made
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    async _requestHistoricalTelemetry() {
 | 
			
		||||
        let options = { ...this.options };
 | 
			
		||||
        let historicalProvider;
 | 
			
		||||
 | 
			
		||||
        this.openmct.telemetry.standardizeRequestOptions(options);
 | 
			
		||||
        historicalProvider = this.openmct.telemetry.
 | 
			
		||||
            findRequestProvider(this.domainObject, options);
 | 
			
		||||
 | 
			
		||||
        if (!historicalProvider) {
 | 
			
		||||
        if (!this.historicalProvider) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let historicalData;
 | 
			
		||||
 | 
			
		||||
        options.onPartialResponse = this._processNewTelemetry.bind(this);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            if (this.requestAbort) {
 | 
			
		||||
                this.requestAbort.abort();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.requestAbort = new AbortController();
 | 
			
		||||
            options.signal = this.requestAbort.signal;
 | 
			
		||||
            this.emit('requestStarted');
 | 
			
		||||
            historicalData = await historicalProvider.request(this.domainObject, options);
 | 
			
		||||
            this.options.signal = this.requestAbort.signal;
 | 
			
		||||
            historicalData = await this.historicalProvider.request(this.domainObject, this.options);
 | 
			
		||||
            this.requestAbort = undefined;
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (error.name !== 'AbortError') {
 | 
			
		||||
                console.error('Error requesting telemetry data...');
 | 
			
		||||
                this._error(error);
 | 
			
		||||
            }
 | 
			
		||||
            console.error('Error requesting telemetry data...');
 | 
			
		||||
            this.requestAbort = undefined;
 | 
			
		||||
            this._error(error);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.emit('requestEnded');
 | 
			
		||||
        this.requestAbort = undefined;
 | 
			
		||||
 | 
			
		||||
        this._processNewTelemetry(historicalData);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This uses the built in subscription function from Telemetry API
 | 
			
		||||
     * @private
 | 
			
		||||
@@ -172,10 +167,6 @@ export class TelemetryCollection extends EventEmitter {
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _processNewTelemetry(telemetryData) {
 | 
			
		||||
        if (telemetryData === undefined) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let data = Array.isArray(telemetryData) ? telemetryData : [telemetryData];
 | 
			
		||||
        let parsedValue;
 | 
			
		||||
        let beforeStartOfBounds;
 | 
			
		||||
@@ -202,10 +193,9 @@ export class TelemetryCollection extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
                    if (endIndex > startIndex) {
 | 
			
		||||
                        let potentialDupes = this.boundedTelemetry.slice(startIndex, endIndex);
 | 
			
		||||
 | 
			
		||||
                        isDuplicate = potentialDupes.some(_.isEqual.bind(undefined, datum));
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (startIndex === this.boundedTelemetry.length) {
 | 
			
		||||
                    isDuplicate = _.isEqual(datum, this.boundedTelemetry[this.boundedTelemetry.length - 1]);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!isDuplicate) {
 | 
			
		||||
@@ -321,7 +311,7 @@ export class TelemetryCollection extends EventEmitter {
 | 
			
		||||
     * Time System
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _setTimeSystem(timeSystem) {
 | 
			
		||||
    _timeSystem(timeSystem) {
 | 
			
		||||
        let domains = this.metadata.valuesForHints(['domain']);
 | 
			
		||||
        let domain = domains.find((d) => d.key === timeSystem.key);
 | 
			
		||||
 | 
			
		||||
@@ -337,10 +327,7 @@ export class TelemetryCollection extends EventEmitter {
 | 
			
		||||
        this.parseTime = (datum) => {
 | 
			
		||||
            return valueFormatter.parse(datum);
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _setTimeSystemAndFetchData(timeSystem) {
 | 
			
		||||
        this._setTimeSystem(timeSystem);
 | 
			
		||||
        this._reset();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -377,19 +364,19 @@ export class TelemetryCollection extends EventEmitter {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * adds the _setTimeSystemAndFetchData callback to the 'timeSystem' timeAPI listener
 | 
			
		||||
     * adds the _timeSystem callback to the 'timeSystem' timeAPI listener
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _watchTimeSystem() {
 | 
			
		||||
        this.openmct.time.on('timeSystem', this._setTimeSystemAndFetchData, this);
 | 
			
		||||
        this.openmct.time.on('timeSystem', this._timeSystem, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * removes the _setTimeSystemAndFetchData callback from the 'timeSystem' timeAPI listener
 | 
			
		||||
     * removes the _timeSystem callback from the 'timeSystem' timeAPI listener
 | 
			
		||||
     * @private
 | 
			
		||||
     */
 | 
			
		||||
    _unwatchTimeSystem() {
 | 
			
		||||
        this.openmct.time.off('timeSystem', this._setTimeSystemAndFetchData, this);
 | 
			
		||||
        this.openmct.time.off('timeSystem', this._timeSystem, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,11 @@ define([
 | 
			
		||||
        valueMetadata.hints = valueMetadata.hints || {};
 | 
			
		||||
 | 
			
		||||
        if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'x')) {
 | 
			
		||||
            console.warn(
 | 
			
		||||
                'DEPRECATION WARNING: `x` hints should be replaced with '
 | 
			
		||||
                + '`domain` hints moving forward.  '
 | 
			
		||||
                + 'https://github.com/nasa/openmct/issues/1546'
 | 
			
		||||
            );
 | 
			
		||||
            if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'domain')) {
 | 
			
		||||
                valueMetadata.hints.domain = valueMetadata.hints.x;
 | 
			
		||||
            }
 | 
			
		||||
@@ -39,6 +44,11 @@ define([
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'y')) {
 | 
			
		||||
            console.warn(
 | 
			
		||||
                'DEPRECATION WARNING: `y` hints should be replaced with '
 | 
			
		||||
                + '`range` hints moving forward.  '
 | 
			
		||||
                + 'https://github.com/nasa/openmct/issues/1546'
 | 
			
		||||
            );
 | 
			
		||||
            if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'range')) {
 | 
			
		||||
                valueMetadata.hints.range = valueMetadata.hints.y;
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,106 +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 TimeContext from "./TimeContext";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The GlobalContext handles getting and setting time of the openmct application in general.
 | 
			
		||||
 * Views will use this context unless they specify an alternate/independent time context
 | 
			
		||||
 */
 | 
			
		||||
class GlobalTimeContext extends TimeContext {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        //The Time Of Interest
 | 
			
		||||
        this.toi = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the start and end time of the time conductor. Basic validation
 | 
			
		||||
     * of bounds is performed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
 | 
			
		||||
     * @throws {Error} Validation error
 | 
			
		||||
     * @fires module:openmct.TimeAPI~bounds
 | 
			
		||||
     * @returns {module:openmct.TimeAPI~TimeConductorBounds}
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method bounds
 | 
			
		||||
     */
 | 
			
		||||
    bounds(newBounds) {
 | 
			
		||||
        if (arguments.length > 0) {
 | 
			
		||||
            super.bounds.call(this, ...arguments);
 | 
			
		||||
            // If a bounds change results in a TOI outside of the current
 | 
			
		||||
            // bounds, unset it
 | 
			
		||||
            if (this.toi < newBounds.start || this.toi > newBounds.end) {
 | 
			
		||||
                this.timeOfInterest(undefined);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //Return a copy to prevent direct mutation of time conductor bounds.
 | 
			
		||||
        return JSON.parse(JSON.stringify(this.boundsVal));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update bounds based on provided time and current offsets
 | 
			
		||||
     * @private
 | 
			
		||||
     * @param {number} timestamp A time from which bounds will be calculated
 | 
			
		||||
     * using current offsets.
 | 
			
		||||
     */
 | 
			
		||||
    tick(timestamp) {
 | 
			
		||||
        super.tick.call(this, ...arguments);
 | 
			
		||||
 | 
			
		||||
        // If a bounds change results in a TOI outside of the current
 | 
			
		||||
        // bounds, unset it
 | 
			
		||||
        if (this.toi < this.boundsVal.start || this.toi > this.boundsVal.end) {
 | 
			
		||||
            this.timeOfInterest(undefined);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the Time of Interest. The Time of Interest is a single point
 | 
			
		||||
     * in time, and constitutes the temporal focus of application views. It can
 | 
			
		||||
     * be manipulated by the user from the time conductor or from other views.
 | 
			
		||||
     * The time of interest can effectively be unset by assigning a value of
 | 
			
		||||
     * 'undefined'.
 | 
			
		||||
     * @fires module:openmct.TimeAPI~timeOfInterest
 | 
			
		||||
     * @param newTOI
 | 
			
		||||
     * @returns {number} the current time of interest
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method timeOfInterest
 | 
			
		||||
     */
 | 
			
		||||
    timeOfInterest(newTOI) {
 | 
			
		||||
        if (arguments.length > 0) {
 | 
			
		||||
            this.toi = newTOI;
 | 
			
		||||
            /**
 | 
			
		||||
             * The Time of Interest has moved.
 | 
			
		||||
             * @event timeOfInterest
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {number} Current time of interest
 | 
			
		||||
             */
 | 
			
		||||
            this.emit('timeOfInterest', this.toi);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.toi;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default GlobalTimeContext;
 | 
			
		||||
@@ -1,94 +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 TimeContext from "./TimeContext";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The IndependentTimeContext handles getting and setting time of the openmct application in general.
 | 
			
		||||
 * Views will use the GlobalTimeContext unless they specify an alternate/independent time context here.
 | 
			
		||||
 */
 | 
			
		||||
class IndependentTimeContext extends TimeContext {
 | 
			
		||||
    constructor(globalTimeContext, key) {
 | 
			
		||||
        super();
 | 
			
		||||
        this.key = key;
 | 
			
		||||
 | 
			
		||||
        this.globalTimeContext = globalTimeContext;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the active clock. Tick source will be immediately subscribed to
 | 
			
		||||
     * and ticking will begin. Offsets from 'now' must also be provided. A clock
 | 
			
		||||
     * can be unset by calling {@link stopClock}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Clock || string} keyOrClock The clock to activate, or its key
 | 
			
		||||
     * @param {ClockOffsets} offsets on each tick these will be used to calculate
 | 
			
		||||
     * the start and end bounds. This maintains a sliding time window of a fixed
 | 
			
		||||
     * width that automatically updates.
 | 
			
		||||
     * @fires module:openmct.TimeAPI~clock
 | 
			
		||||
     * @return {Clock} the currently active clock;
 | 
			
		||||
     */
 | 
			
		||||
    clock(keyOrClock, offsets) {
 | 
			
		||||
        if (arguments.length === 2) {
 | 
			
		||||
            let clock;
 | 
			
		||||
 | 
			
		||||
            if (typeof keyOrClock === 'string') {
 | 
			
		||||
                clock = this.globalTimeContext.clocks.get(keyOrClock);
 | 
			
		||||
                if (clock === undefined) {
 | 
			
		||||
                    throw "Unknown clock '" + keyOrClock + "'. Has it been registered with 'addClock'?";
 | 
			
		||||
                }
 | 
			
		||||
            } else if (typeof keyOrClock === 'object') {
 | 
			
		||||
                clock = keyOrClock;
 | 
			
		||||
                if (!this.globalTimeContext.clocks.has(clock.key)) {
 | 
			
		||||
                    throw "Unknown clock '" + keyOrClock.key + "'. Has it been registered with 'addClock'?";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const previousClock = this.activeClock;
 | 
			
		||||
            if (previousClock !== undefined) {
 | 
			
		||||
                previousClock.off("tick", this.tick);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.activeClock = clock;
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * The active clock has changed. Clock can be unset by calling {@link stopClock}
 | 
			
		||||
             * @event clock
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {Clock} clock The newly activated clock, or undefined
 | 
			
		||||
             * if the system is no longer following a clock source
 | 
			
		||||
             */
 | 
			
		||||
            this.emit("clock", this.activeClock);
 | 
			
		||||
 | 
			
		||||
            if (this.activeClock !== undefined) {
 | 
			
		||||
                this.clockOffsets(offsets);
 | 
			
		||||
                this.activeClock.on("tick", this.tick);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } else if (arguments.length === 1) {
 | 
			
		||||
            throw "When setting the clock, clock offsets must also be provided";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.activeClock;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default IndependentTimeContext;
 | 
			
		||||
@@ -20,35 +20,51 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
import GlobalTimeContext from "./GlobalTimeContext";
 | 
			
		||||
import IndependentTimeContext from "@/api/time/IndependentTimeContext";
 | 
			
		||||
define(['EventEmitter'], function (EventEmitter) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The public API for setting and querying the temporal state of the
 | 
			
		||||
     * application. The concept of time is integral to Open MCT, and at least
 | 
			
		||||
     * one {@link TimeSystem}, as well as some default time bounds must be
 | 
			
		||||
     * registered and enabled via {@link TimeAPI.addTimeSystem} and
 | 
			
		||||
     * {@link TimeAPI.timeSystem} respectively for Open MCT to work.
 | 
			
		||||
     *
 | 
			
		||||
     * Time-sensitive views will typically respond to changes to bounds or other
 | 
			
		||||
     * properties of the time conductor and update the data displayed based on
 | 
			
		||||
     * the temporal state of the application. The current time bounds are also
 | 
			
		||||
     * used in queries for historical data.
 | 
			
		||||
     *
 | 
			
		||||
     * The TimeAPI extends the EventEmitter class. A number of events are
 | 
			
		||||
     * fired when properties of the time conductor change, which are documented
 | 
			
		||||
     * below.
 | 
			
		||||
     *
 | 
			
		||||
     * @interface
 | 
			
		||||
     * @memberof module:openmct
 | 
			
		||||
     */
 | 
			
		||||
    function TimeAPI() {
 | 
			
		||||
        EventEmitter.call(this);
 | 
			
		||||
 | 
			
		||||
        //The Time System
 | 
			
		||||
        this.system = undefined;
 | 
			
		||||
        //The Time Of Interest
 | 
			
		||||
        this.toi = undefined;
 | 
			
		||||
 | 
			
		||||
        this.boundsVal = {
 | 
			
		||||
            start: undefined,
 | 
			
		||||
            end: undefined
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.timeSystems = new Map();
 | 
			
		||||
        this.clocks = new Map();
 | 
			
		||||
        this.activeClock = undefined;
 | 
			
		||||
        this.offsets = undefined;
 | 
			
		||||
 | 
			
		||||
        this.tick = this.tick.bind(this);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
* The public API for setting and querying the temporal state of the
 | 
			
		||||
* application. The concept of time is integral to Open MCT, and at least
 | 
			
		||||
* one {@link TimeSystem}, as well as some default time bounds must be
 | 
			
		||||
* registered and enabled via {@link TimeAPI.addTimeSystem} and
 | 
			
		||||
* {@link TimeAPI.timeSystem} respectively for Open MCT to work.
 | 
			
		||||
*
 | 
			
		||||
* Time-sensitive views will typically respond to changes to bounds or other
 | 
			
		||||
* properties of the time conductor and update the data displayed based on
 | 
			
		||||
* the temporal state of the application. The current time bounds are also
 | 
			
		||||
* used in queries for historical data.
 | 
			
		||||
*
 | 
			
		||||
* The TimeAPI extends the GlobalTimeContext which in turn extends the TimeContext/EventEmitter class. A number of events are
 | 
			
		||||
* fired when properties of the time conductor change, which are documented
 | 
			
		||||
* below.
 | 
			
		||||
*
 | 
			
		||||
* @interface
 | 
			
		||||
* @memberof module:openmct
 | 
			
		||||
*/
 | 
			
		||||
class TimeAPI extends GlobalTimeContext {
 | 
			
		||||
    constructor(openmct) {
 | 
			
		||||
        super();
 | 
			
		||||
        this.openmct = openmct;
 | 
			
		||||
        this.independentContexts = new Map();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    TimeAPI.prototype = Object.create(EventEmitter.prototype);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A TimeSystem provides meaning to the values returned by the TimeAPI. Open
 | 
			
		||||
     * MCT supports multiple different types of time values, although all are
 | 
			
		||||
@@ -78,16 +94,16 @@ class TimeAPI extends GlobalTimeContext {
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @param {TimeSystem} timeSystem A time system object.
 | 
			
		||||
     */
 | 
			
		||||
    addTimeSystem(timeSystem) {
 | 
			
		||||
    TimeAPI.prototype.addTimeSystem = function (timeSystem) {
 | 
			
		||||
        this.timeSystems.set(timeSystem.key, timeSystem);
 | 
			
		||||
    }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @returns {TimeSystem[]}
 | 
			
		||||
     */
 | 
			
		||||
    getAllTimeSystems() {
 | 
			
		||||
    TimeAPI.prototype.getAllTimeSystems = function () {
 | 
			
		||||
        return Array.from(this.timeSystems.values());
 | 
			
		||||
    }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clocks provide a timing source that is used to
 | 
			
		||||
@@ -110,81 +126,340 @@ class TimeAPI extends GlobalTimeContext {
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @param {Clock} clock
 | 
			
		||||
     */
 | 
			
		||||
    addClock(clock) {
 | 
			
		||||
    TimeAPI.prototype.addClock = function (clock) {
 | 
			
		||||
        this.clocks.set(clock.key, clock);
 | 
			
		||||
    }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @returns {Clock[]}
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     */
 | 
			
		||||
    getAllClocks() {
 | 
			
		||||
    TimeAPI.prototype.getAllClocks = function () {
 | 
			
		||||
        return Array.from(this.clocks.values());
 | 
			
		||||
    }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set an independent time context which follows the TimeAPI timeSystem,
 | 
			
		||||
     * but with different offsets for a given domain object
 | 
			
		||||
     * @param {key | string} key The identifier key of the domain object these offsets are set for
 | 
			
		||||
     * @param {ClockOffsets | TimeBounds} value This maintains a sliding time window of a fixed width that automatically updates
 | 
			
		||||
     * @param {key | string} clockKey the real time clock key currently in use
 | 
			
		||||
     * Validate the given bounds. This can be used for pre-validation of bounds,
 | 
			
		||||
     * for example by views validating user inputs.
 | 
			
		||||
     * @param {TimeBounds} bounds The start and end time of the conductor.
 | 
			
		||||
     * @returns {string | true} A validation error, or true if valid
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method addIndependentTimeContext
 | 
			
		||||
     * @method validateBounds
 | 
			
		||||
     */
 | 
			
		||||
    addIndependentContext(key, value, clockKey) {
 | 
			
		||||
        let timeContext = this.independentContexts.get(key);
 | 
			
		||||
        if (!timeContext) {
 | 
			
		||||
            timeContext = new IndependentTimeContext(this, key);
 | 
			
		||||
            this.independentContexts.set(key, timeContext);
 | 
			
		||||
    TimeAPI.prototype.validateBounds = function (bounds) {
 | 
			
		||||
        if ((bounds.start === undefined)
 | 
			
		||||
            || (bounds.end === undefined)
 | 
			
		||||
            || isNaN(bounds.start)
 | 
			
		||||
            || isNaN(bounds.end)
 | 
			
		||||
        ) {
 | 
			
		||||
            return "Start and end must be specified as integer values";
 | 
			
		||||
        } else if (bounds.start > bounds.end) {
 | 
			
		||||
            return "Specified start date exceeds end bound";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (clockKey) {
 | 
			
		||||
            timeContext.clock(clockKey, value);
 | 
			
		||||
        } else {
 | 
			
		||||
            timeContext.stopClock();
 | 
			
		||||
            timeContext.bounds(value);
 | 
			
		||||
        return true;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Validate the given offsets. This can be used for pre-validation of
 | 
			
		||||
     * offsets, for example by views validating user inputs.
 | 
			
		||||
     * @param {ClockOffsets} offsets The start and end offsets from a 'now' value.
 | 
			
		||||
     * @returns {string | true} A validation error, or true if valid
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method validateBounds
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.validateOffsets = function (offsets) {
 | 
			
		||||
        if ((offsets.start === undefined)
 | 
			
		||||
            || (offsets.end === undefined)
 | 
			
		||||
            || isNaN(offsets.start)
 | 
			
		||||
            || isNaN(offsets.end)
 | 
			
		||||
        ) {
 | 
			
		||||
            return "Start and end offsets must be specified as integer values";
 | 
			
		||||
        } else if (offsets.start >= offsets.end) {
 | 
			
		||||
            return "Specified start offset must be < end offset";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.emit('timeContext', key);
 | 
			
		||||
 | 
			
		||||
        return () => {
 | 
			
		||||
            this.independentContexts.delete(key);
 | 
			
		||||
            timeContext.emit('timeContext', key);
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
        return true;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the independent time context which follows the TimeAPI timeSystem,
 | 
			
		||||
     * but with different offsets.
 | 
			
		||||
     * @param {key | string} key The identifier key of the domain object these offsets
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method getIndependentTimeContext
 | 
			
		||||
     * @typedef {Object} TimeBounds
 | 
			
		||||
     * @property {number} start The start time displayed by the time conductor
 | 
			
		||||
     * in ms since epoch. Epoch determined by currently active time system
 | 
			
		||||
     * @property {number} end The end time displayed by the time conductor in ms
 | 
			
		||||
     * since epoch.
 | 
			
		||||
     * @memberof module:openmct.TimeAPI~
 | 
			
		||||
     */
 | 
			
		||||
    getIndependentContext(key) {
 | 
			
		||||
        return this.independentContexts.get(key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the a timeContext for a view based on it's objectPath. If there is any object in the objectPath with an independent time context, it will be returned.
 | 
			
		||||
     * Otherwise, the global time context will be returned.
 | 
			
		||||
     * @param { Array } objectPath The view's objectPath
 | 
			
		||||
     * Get or set the start and end time of the time conductor. Basic validation
 | 
			
		||||
     * of bounds is performed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
 | 
			
		||||
     * @throws {Error} Validation error
 | 
			
		||||
     * @fires module:openmct.TimeAPI~bounds
 | 
			
		||||
     * @returns {module:openmct.TimeAPI~TimeConductorBounds}
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method getContextForView
 | 
			
		||||
     * @method bounds
 | 
			
		||||
     */
 | 
			
		||||
    getContextForView(objectPath = []) {
 | 
			
		||||
        let timeContext = this;
 | 
			
		||||
 | 
			
		||||
        objectPath.forEach(item => {
 | 
			
		||||
            const key = this.openmct.objects.makeKeyString(item.identifier);
 | 
			
		||||
            if (this.independentContexts.get(key)) {
 | 
			
		||||
                timeContext = this.independentContexts.get(key);
 | 
			
		||||
    TimeAPI.prototype.bounds = function (newBounds) {
 | 
			
		||||
        if (arguments.length > 0) {
 | 
			
		||||
            const validationResult = this.validateBounds(newBounds);
 | 
			
		||||
            if (validationResult !== true) {
 | 
			
		||||
                throw new Error(validationResult);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return timeContext;
 | 
			
		||||
    }
 | 
			
		||||
            //Create a copy to avoid direct mutation of conductor bounds
 | 
			
		||||
            this.boundsVal = JSON.parse(JSON.stringify(newBounds));
 | 
			
		||||
            /**
 | 
			
		||||
             * The start time, end time, or both have been updated.
 | 
			
		||||
             * @event bounds
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {TimeConductorBounds} bounds The newly updated bounds
 | 
			
		||||
             * @property {boolean} [tick] `true` if the bounds update was due to
 | 
			
		||||
             * a "tick" event (ie. was an automatic update), false otherwise.
 | 
			
		||||
             */
 | 
			
		||||
            this.emit('bounds', this.boundsVal, false);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
            // If a bounds change results in a TOI outside of the current
 | 
			
		||||
            // bounds, unset it
 | 
			
		||||
            if (this.toi < newBounds.start || this.toi > newBounds.end) {
 | 
			
		||||
                this.timeOfInterest(undefined);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
export default TimeAPI;
 | 
			
		||||
        //Return a copy to prevent direct mutation of time conductor bounds.
 | 
			
		||||
        return JSON.parse(JSON.stringify(this.boundsVal));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the time system of the TimeAPI.
 | 
			
		||||
     * @param {TimeSystem | string} timeSystem
 | 
			
		||||
     * @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
 | 
			
		||||
     * @fires module:openmct.TimeAPI~timeSystem
 | 
			
		||||
     * @returns {TimeSystem} The currently applied time system
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method timeSystem
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.timeSystem = function (timeSystemOrKey, bounds) {
 | 
			
		||||
        if (arguments.length >= 1) {
 | 
			
		||||
            if (arguments.length === 1 && !this.activeClock) {
 | 
			
		||||
                throw new Error(
 | 
			
		||||
                    "Must specify bounds when changing time system without "
 | 
			
		||||
                    + "an active clock."
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let timeSystem;
 | 
			
		||||
 | 
			
		||||
            if (timeSystemOrKey === undefined) {
 | 
			
		||||
                throw "Please provide a time system";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (typeof timeSystemOrKey === 'string') {
 | 
			
		||||
                timeSystem = this.timeSystems.get(timeSystemOrKey);
 | 
			
		||||
 | 
			
		||||
                if (timeSystem === undefined) {
 | 
			
		||||
                    throw "Unknown time system " + timeSystemOrKey + ". Has it been registered with 'addTimeSystem'?";
 | 
			
		||||
                }
 | 
			
		||||
            } else if (typeof timeSystemOrKey === 'object') {
 | 
			
		||||
                timeSystem = timeSystemOrKey;
 | 
			
		||||
 | 
			
		||||
                if (!this.timeSystems.has(timeSystem.key)) {
 | 
			
		||||
                    throw "Unknown time system " + timeSystem.key + ". Has it been registered with 'addTimeSystem'?";
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                throw "Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.system = timeSystem;
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * The time system used by the time
 | 
			
		||||
             * conductor has changed. A change in Time System will always be
 | 
			
		||||
             * followed by a bounds event specifying new query bounds.
 | 
			
		||||
             *
 | 
			
		||||
             * @event module:openmct.TimeAPI~timeSystem
 | 
			
		||||
             * @property {TimeSystem} The value of the currently applied
 | 
			
		||||
             * Time System
 | 
			
		||||
             * */
 | 
			
		||||
            this.emit('timeSystem', this.system);
 | 
			
		||||
            if (bounds) {
 | 
			
		||||
                this.bounds(bounds);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.system;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the Time of Interest. The Time of Interest is a single point
 | 
			
		||||
     * in time, and constitutes the temporal focus of application views. It can
 | 
			
		||||
     * be manipulated by the user from the time conductor or from other views.
 | 
			
		||||
     * The time of interest can effectively be unset by assigning a value of
 | 
			
		||||
     * 'undefined'.
 | 
			
		||||
     * @fires module:openmct.TimeAPI~timeOfInterest
 | 
			
		||||
     * @param newTOI
 | 
			
		||||
     * @returns {number} the current time of interest
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method timeOfInterest
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.timeOfInterest = function (newTOI) {
 | 
			
		||||
        if (arguments.length > 0) {
 | 
			
		||||
            this.toi = newTOI;
 | 
			
		||||
            /**
 | 
			
		||||
             * The Time of Interest has moved.
 | 
			
		||||
             * @event timeOfInterest
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {number} Current time of interest
 | 
			
		||||
             */
 | 
			
		||||
            this.emit('timeOfInterest', this.toi);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.toi;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update bounds based on provided time and current offsets
 | 
			
		||||
     * @private
 | 
			
		||||
     * @param {number} timestamp A time from which boudns will be calculated
 | 
			
		||||
     * using current offsets.
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.tick = function (timestamp) {
 | 
			
		||||
        const newBounds = {
 | 
			
		||||
            start: timestamp + this.offsets.start,
 | 
			
		||||
            end: timestamp + this.offsets.end
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.boundsVal = newBounds;
 | 
			
		||||
        this.emit('bounds', this.boundsVal, true);
 | 
			
		||||
 | 
			
		||||
        // If a bounds change results in a TOI outside of the current
 | 
			
		||||
        // bounds, unset it
 | 
			
		||||
        if (this.toi < newBounds.start || this.toi > newBounds.end) {
 | 
			
		||||
            this.timeOfInterest(undefined);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the active clock. Tick source will be immediately subscribed to
 | 
			
		||||
     * and ticking will begin. Offsets from 'now' must also be provided. A clock
 | 
			
		||||
     * can be unset by calling {@link stopClock}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Clock || string} The clock to activate, or its key
 | 
			
		||||
     * @param {ClockOffsets} offsets on each tick these will be used to calculate
 | 
			
		||||
     * the start and end bounds. This maintains a sliding time window of a fixed
 | 
			
		||||
     * width that automatically updates.
 | 
			
		||||
     * @fires module:openmct.TimeAPI~clock
 | 
			
		||||
     * @return {Clock} the currently active clock;
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.clock = function (keyOrClock, offsets) {
 | 
			
		||||
        if (arguments.length === 2) {
 | 
			
		||||
            let clock;
 | 
			
		||||
 | 
			
		||||
            if (typeof keyOrClock === 'string') {
 | 
			
		||||
                clock = this.clocks.get(keyOrClock);
 | 
			
		||||
                if (clock === undefined) {
 | 
			
		||||
                    throw "Unknown clock '" + keyOrClock + "'. Has it been registered with 'addClock'?";
 | 
			
		||||
                }
 | 
			
		||||
            } else if (typeof keyOrClock === 'object') {
 | 
			
		||||
                clock = keyOrClock;
 | 
			
		||||
                if (!this.clocks.has(clock.key)) {
 | 
			
		||||
                    throw "Unknown clock '" + keyOrClock.key + "'. Has it been registered with 'addClock'?";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const previousClock = this.activeClock;
 | 
			
		||||
            if (previousClock !== undefined) {
 | 
			
		||||
                previousClock.off("tick", this.tick);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.activeClock = clock;
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * The active clock has changed. Clock can be unset by calling {@link stopClock}
 | 
			
		||||
             * @event clock
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {Clock} clock The newly activated clock, or undefined
 | 
			
		||||
             * if the system is no longer following a clock source
 | 
			
		||||
             */
 | 
			
		||||
            this.emit("clock", this.activeClock);
 | 
			
		||||
 | 
			
		||||
            if (this.activeClock !== undefined) {
 | 
			
		||||
                this.clockOffsets(offsets);
 | 
			
		||||
                this.activeClock.on("tick", this.tick);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } else if (arguments.length === 1) {
 | 
			
		||||
            throw "When setting the clock, clock offsets must also be provided";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.activeClock;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clock offsets are used to calculate temporal bounds when the system is
 | 
			
		||||
     * ticking on a clock source.
 | 
			
		||||
     *
 | 
			
		||||
     * @typedef {object} ClockOffsets
 | 
			
		||||
     * @property {number} start A time span relative to the current value of the
 | 
			
		||||
     * ticking clock, from which start bounds will be calculated. This value must
 | 
			
		||||
     * be < 0. When a clock is active, bounds will be calculated automatically
 | 
			
		||||
     * based on the value provided by the clock, and the defined clock offsets.
 | 
			
		||||
     * @property {number} end A time span relative to the current value of the
 | 
			
		||||
     * ticking clock, from which end bounds will be calculated. This value must
 | 
			
		||||
     * be >= 0.
 | 
			
		||||
     */
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the currently applied clock offsets. If no parameter is provided,
 | 
			
		||||
     * the current value will be returned. If provided, the new value will be
 | 
			
		||||
     * used as the new clock offsets.
 | 
			
		||||
     * @param {ClockOffsets} offsets
 | 
			
		||||
     * @returns {ClockOffsets}
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.clockOffsets = function (offsets) {
 | 
			
		||||
        if (arguments.length > 0) {
 | 
			
		||||
 | 
			
		||||
            const validationResult = this.validateOffsets(offsets);
 | 
			
		||||
            if (validationResult !== true) {
 | 
			
		||||
                throw new Error(validationResult);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.offsets = offsets;
 | 
			
		||||
 | 
			
		||||
            const currentValue = this.activeClock.currentValue();
 | 
			
		||||
            const newBounds = {
 | 
			
		||||
                start: currentValue + offsets.start,
 | 
			
		||||
                end: currentValue + offsets.end
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            this.bounds(newBounds);
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Event that is triggered when clock offsets change.
 | 
			
		||||
             * @event clockOffsets
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {ClockOffsets} clockOffsets The newly activated clock
 | 
			
		||||
             * offsets.
 | 
			
		||||
             */
 | 
			
		||||
            this.emit("clockOffsets", offsets);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.offsets;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Stop the currently active clock from ticking, and unset it. This will
 | 
			
		||||
     * revert all views to showing a static time frame defined by the current
 | 
			
		||||
     * bounds.
 | 
			
		||||
     */
 | 
			
		||||
    TimeAPI.prototype.stopClock = function () {
 | 
			
		||||
        if (this.activeClock) {
 | 
			
		||||
            this.clock(undefined, undefined);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return TimeAPI;
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -19,243 +19,241 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
import TimeAPI from "./TimeAPI";
 | 
			
		||||
import {createOpenMct} from "utils/testing";
 | 
			
		||||
 | 
			
		||||
describe("The Time API", function () {
 | 
			
		||||
    let api;
 | 
			
		||||
    let timeSystemKey;
 | 
			
		||||
    let timeSystem;
 | 
			
		||||
    let clockKey;
 | 
			
		||||
    let clock;
 | 
			
		||||
    let bounds;
 | 
			
		||||
    let eventListener;
 | 
			
		||||
    let toi;
 | 
			
		||||
    let openmct;
 | 
			
		||||
 | 
			
		||||
    beforeEach(function () {
 | 
			
		||||
        openmct = createOpenMct();
 | 
			
		||||
        api = new TimeAPI(openmct);
 | 
			
		||||
        timeSystemKey = "timeSystemKey";
 | 
			
		||||
        timeSystem = {key: timeSystemKey};
 | 
			
		||||
        clockKey = "someClockKey";
 | 
			
		||||
        clock = jasmine.createSpyObj("clock", [
 | 
			
		||||
            "on",
 | 
			
		||||
            "off",
 | 
			
		||||
            "currentValue"
 | 
			
		||||
        ]);
 | 
			
		||||
        clock.currentValue.and.returnValue(100);
 | 
			
		||||
        clock.key = clockKey;
 | 
			
		||||
        bounds = {
 | 
			
		||||
            start: 0,
 | 
			
		||||
            end: 1
 | 
			
		||||
        };
 | 
			
		||||
        eventListener = jasmine.createSpy("eventListener");
 | 
			
		||||
        toi = 111;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Supports setting and querying of time of interest", function () {
 | 
			
		||||
        expect(api.timeOfInterest()).not.toBe(toi);
 | 
			
		||||
        api.timeOfInterest(toi);
 | 
			
		||||
        expect(api.timeOfInterest()).toBe(toi);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Allows setting of valid bounds", function () {
 | 
			
		||||
        bounds = {
 | 
			
		||||
            start: 0,
 | 
			
		||||
            end: 1
 | 
			
		||||
        };
 | 
			
		||||
        expect(api.bounds()).not.toBe(bounds);
 | 
			
		||||
        expect(api.bounds.bind(api, bounds)).not.toThrow();
 | 
			
		||||
        expect(api.bounds()).toEqual(bounds);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Disallows setting of invalid bounds", function () {
 | 
			
		||||
        bounds = {
 | 
			
		||||
            start: 1,
 | 
			
		||||
            end: 0
 | 
			
		||||
        };
 | 
			
		||||
        expect(api.bounds()).not.toEqual(bounds);
 | 
			
		||||
        expect(api.bounds.bind(api, bounds)).toThrow();
 | 
			
		||||
        expect(api.bounds()).not.toEqual(bounds);
 | 
			
		||||
 | 
			
		||||
        bounds = {start: 1};
 | 
			
		||||
        expect(api.bounds()).not.toEqual(bounds);
 | 
			
		||||
        expect(api.bounds.bind(api, bounds)).toThrow();
 | 
			
		||||
        expect(api.bounds()).not.toEqual(bounds);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Allows setting of previously registered time system with bounds", function () {
 | 
			
		||||
        api.addTimeSystem(timeSystem);
 | 
			
		||||
        expect(api.timeSystem()).not.toBe(timeSystem);
 | 
			
		||||
        expect(function () {
 | 
			
		||||
            api.timeSystem(timeSystem, bounds);
 | 
			
		||||
        }).not.toThrow();
 | 
			
		||||
        expect(api.timeSystem()).toBe(timeSystem);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Disallows setting of time system without bounds", function () {
 | 
			
		||||
        api.addTimeSystem(timeSystem);
 | 
			
		||||
        expect(api.timeSystem()).not.toBe(timeSystem);
 | 
			
		||||
        expect(function () {
 | 
			
		||||
            api.timeSystem(timeSystemKey);
 | 
			
		||||
        }).toThrow();
 | 
			
		||||
        expect(api.timeSystem()).not.toBe(timeSystem);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("allows setting of timesystem without bounds with clock", function () {
 | 
			
		||||
        api.addTimeSystem(timeSystem);
 | 
			
		||||
        api.addClock(clock);
 | 
			
		||||
        api.clock(clockKey, {
 | 
			
		||||
            start: 0,
 | 
			
		||||
            end: 1
 | 
			
		||||
        });
 | 
			
		||||
        expect(api.timeSystem()).not.toBe(timeSystem);
 | 
			
		||||
        expect(function () {
 | 
			
		||||
            api.timeSystem(timeSystemKey);
 | 
			
		||||
        }).not.toThrow();
 | 
			
		||||
        expect(api.timeSystem()).toBe(timeSystem);
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Emits an event when time system changes", function () {
 | 
			
		||||
        api.addTimeSystem(timeSystem);
 | 
			
		||||
        expect(eventListener).not.toHaveBeenCalled();
 | 
			
		||||
        api.on("timeSystem", eventListener);
 | 
			
		||||
        api.timeSystem(timeSystemKey, bounds);
 | 
			
		||||
        expect(eventListener).toHaveBeenCalledWith(timeSystem);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Emits an event when time of interest changes", function () {
 | 
			
		||||
        expect(eventListener).not.toHaveBeenCalled();
 | 
			
		||||
        api.on("timeOfInterest", eventListener);
 | 
			
		||||
        api.timeOfInterest(toi);
 | 
			
		||||
        expect(eventListener).toHaveBeenCalledWith(toi);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Emits an event when bounds change", function () {
 | 
			
		||||
        expect(eventListener).not.toHaveBeenCalled();
 | 
			
		||||
        api.on("bounds", eventListener);
 | 
			
		||||
        api.bounds(bounds);
 | 
			
		||||
        expect(eventListener).toHaveBeenCalledWith(bounds, false);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("If bounds are set and TOI lies inside them, do not change TOI", function () {
 | 
			
		||||
        api.timeOfInterest(6);
 | 
			
		||||
        api.bounds({
 | 
			
		||||
            start: 1,
 | 
			
		||||
            end: 10
 | 
			
		||||
        });
 | 
			
		||||
        expect(api.timeOfInterest()).toEqual(6);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("If bounds are set and TOI lies outside them, reset TOI", function () {
 | 
			
		||||
        api.timeOfInterest(11);
 | 
			
		||||
        api.bounds({
 | 
			
		||||
            start: 1,
 | 
			
		||||
            end: 10
 | 
			
		||||
        });
 | 
			
		||||
        expect(api.timeOfInterest()).toBeUndefined();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Maintains delta during tick", function () {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Allows registered time system to be activated", function () {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Allows a registered tick source to be activated", function () {
 | 
			
		||||
        const mockTickSource = jasmine.createSpyObj("mockTickSource", [
 | 
			
		||||
            "on",
 | 
			
		||||
            "off",
 | 
			
		||||
            "currentValue"
 | 
			
		||||
        ]);
 | 
			
		||||
        mockTickSource.key = 'mockTickSource';
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe(" when enabling a tick source", function () {
 | 
			
		||||
        let mockTickSource;
 | 
			
		||||
        let anotherMockTickSource;
 | 
			
		||||
        const mockOffsets = {
 | 
			
		||||
            start: 0,
 | 
			
		||||
            end: 1
 | 
			
		||||
        };
 | 
			
		||||
define(['./TimeAPI'], function (TimeAPI) {
 | 
			
		||||
    describe("The Time API", function () {
 | 
			
		||||
        let api;
 | 
			
		||||
        let timeSystemKey;
 | 
			
		||||
        let timeSystem;
 | 
			
		||||
        let clockKey;
 | 
			
		||||
        let clock;
 | 
			
		||||
        let bounds;
 | 
			
		||||
        let eventListener;
 | 
			
		||||
        let toi;
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            mockTickSource = jasmine.createSpyObj("clock", [
 | 
			
		||||
            api = new TimeAPI();
 | 
			
		||||
            timeSystemKey = "timeSystemKey";
 | 
			
		||||
            timeSystem = {key: timeSystemKey};
 | 
			
		||||
            clockKey = "someClockKey";
 | 
			
		||||
            clock = jasmine.createSpyObj("clock", [
 | 
			
		||||
                "on",
 | 
			
		||||
                "off",
 | 
			
		||||
                "currentValue"
 | 
			
		||||
            ]);
 | 
			
		||||
            mockTickSource.currentValue.and.returnValue(10);
 | 
			
		||||
            clock.currentValue.and.returnValue(100);
 | 
			
		||||
            clock.key = clockKey;
 | 
			
		||||
            bounds = {
 | 
			
		||||
                start: 0,
 | 
			
		||||
                end: 1
 | 
			
		||||
            };
 | 
			
		||||
            eventListener = jasmine.createSpy("eventListener");
 | 
			
		||||
            toi = 111;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Supports setting and querying of time of interest", function () {
 | 
			
		||||
            expect(api.timeOfInterest()).not.toBe(toi);
 | 
			
		||||
            api.timeOfInterest(toi);
 | 
			
		||||
            expect(api.timeOfInterest()).toBe(toi);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Allows setting of valid bounds", function () {
 | 
			
		||||
            bounds = {
 | 
			
		||||
                start: 0,
 | 
			
		||||
                end: 1
 | 
			
		||||
            };
 | 
			
		||||
            expect(api.bounds()).not.toBe(bounds);
 | 
			
		||||
            expect(api.bounds.bind(api, bounds)).not.toThrow();
 | 
			
		||||
            expect(api.bounds()).toEqual(bounds);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Disallows setting of invalid bounds", function () {
 | 
			
		||||
            bounds = {
 | 
			
		||||
                start: 1,
 | 
			
		||||
                end: 0
 | 
			
		||||
            };
 | 
			
		||||
            expect(api.bounds()).not.toEqual(bounds);
 | 
			
		||||
            expect(api.bounds.bind(api, bounds)).toThrow();
 | 
			
		||||
            expect(api.bounds()).not.toEqual(bounds);
 | 
			
		||||
 | 
			
		||||
            bounds = {start: 1};
 | 
			
		||||
            expect(api.bounds()).not.toEqual(bounds);
 | 
			
		||||
            expect(api.bounds.bind(api, bounds)).toThrow();
 | 
			
		||||
            expect(api.bounds()).not.toEqual(bounds);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Allows setting of previously registered time system with bounds", function () {
 | 
			
		||||
            api.addTimeSystem(timeSystem);
 | 
			
		||||
            expect(api.timeSystem()).not.toBe(timeSystem);
 | 
			
		||||
            expect(function () {
 | 
			
		||||
                api.timeSystem(timeSystem, bounds);
 | 
			
		||||
            }).not.toThrow();
 | 
			
		||||
            expect(api.timeSystem()).toBe(timeSystem);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Disallows setting of time system without bounds", function () {
 | 
			
		||||
            api.addTimeSystem(timeSystem);
 | 
			
		||||
            expect(api.timeSystem()).not.toBe(timeSystem);
 | 
			
		||||
            expect(function () {
 | 
			
		||||
                api.timeSystem(timeSystemKey);
 | 
			
		||||
            }).toThrow();
 | 
			
		||||
            expect(api.timeSystem()).not.toBe(timeSystem);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("allows setting of timesystem without bounds with clock", function () {
 | 
			
		||||
            api.addTimeSystem(timeSystem);
 | 
			
		||||
            api.addClock(clock);
 | 
			
		||||
            api.clock(clockKey, {
 | 
			
		||||
                start: 0,
 | 
			
		||||
                end: 1
 | 
			
		||||
            });
 | 
			
		||||
            expect(api.timeSystem()).not.toBe(timeSystem);
 | 
			
		||||
            expect(function () {
 | 
			
		||||
                api.timeSystem(timeSystemKey);
 | 
			
		||||
            }).not.toThrow();
 | 
			
		||||
            expect(api.timeSystem()).toBe(timeSystem);
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Emits an event when time system changes", function () {
 | 
			
		||||
            api.addTimeSystem(timeSystem);
 | 
			
		||||
            expect(eventListener).not.toHaveBeenCalled();
 | 
			
		||||
            api.on("timeSystem", eventListener);
 | 
			
		||||
            api.timeSystem(timeSystemKey, bounds);
 | 
			
		||||
            expect(eventListener).toHaveBeenCalledWith(timeSystem);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Emits an event when time of interest changes", function () {
 | 
			
		||||
            expect(eventListener).not.toHaveBeenCalled();
 | 
			
		||||
            api.on("timeOfInterest", eventListener);
 | 
			
		||||
            api.timeOfInterest(toi);
 | 
			
		||||
            expect(eventListener).toHaveBeenCalledWith(toi);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Emits an event when bounds change", function () {
 | 
			
		||||
            expect(eventListener).not.toHaveBeenCalled();
 | 
			
		||||
            api.on("bounds", eventListener);
 | 
			
		||||
            api.bounds(bounds);
 | 
			
		||||
            expect(eventListener).toHaveBeenCalledWith(bounds, false);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("If bounds are set and TOI lies inside them, do not change TOI", function () {
 | 
			
		||||
            api.timeOfInterest(6);
 | 
			
		||||
            api.bounds({
 | 
			
		||||
                start: 1,
 | 
			
		||||
                end: 10
 | 
			
		||||
            });
 | 
			
		||||
            expect(api.timeOfInterest()).toEqual(6);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("If bounds are set and TOI lies outside them, reset TOI", function () {
 | 
			
		||||
            api.timeOfInterest(11);
 | 
			
		||||
            api.bounds({
 | 
			
		||||
                start: 1,
 | 
			
		||||
                end: 10
 | 
			
		||||
            });
 | 
			
		||||
            expect(api.timeOfInterest()).toBeUndefined();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Maintains delta during tick", function () {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Allows registered time system to be activated", function () {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Allows a registered tick source to be activated", function () {
 | 
			
		||||
            const mockTickSource = jasmine.createSpyObj("mockTickSource", [
 | 
			
		||||
                "on",
 | 
			
		||||
                "off",
 | 
			
		||||
                "currentValue"
 | 
			
		||||
            ]);
 | 
			
		||||
            mockTickSource.key = 'mockTickSource';
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe(" when enabling a tick source", function () {
 | 
			
		||||
            let mockTickSource;
 | 
			
		||||
            let anotherMockTickSource;
 | 
			
		||||
            const mockOffsets = {
 | 
			
		||||
                start: 0,
 | 
			
		||||
                end: 1
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockTickSource = jasmine.createSpyObj("clock", [
 | 
			
		||||
                    "on",
 | 
			
		||||
                    "off",
 | 
			
		||||
                    "currentValue"
 | 
			
		||||
                ]);
 | 
			
		||||
                mockTickSource.currentValue.and.returnValue(10);
 | 
			
		||||
                mockTickSource.key = "mts";
 | 
			
		||||
 | 
			
		||||
                anotherMockTickSource = jasmine.createSpyObj("clock", [
 | 
			
		||||
                    "on",
 | 
			
		||||
                    "off",
 | 
			
		||||
                    "currentValue"
 | 
			
		||||
                ]);
 | 
			
		||||
                anotherMockTickSource.key = "amts";
 | 
			
		||||
                anotherMockTickSource.currentValue.and.returnValue(10);
 | 
			
		||||
 | 
			
		||||
                api.addClock(mockTickSource);
 | 
			
		||||
                api.addClock(anotherMockTickSource);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("sets bounds based on current value", function () {
 | 
			
		||||
                api.clock("mts", mockOffsets);
 | 
			
		||||
                expect(api.bounds()).toEqual({
 | 
			
		||||
                    start: 10,
 | 
			
		||||
                    end: 11
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("a new tick listener is registered", function () {
 | 
			
		||||
                api.clock("mts", mockOffsets);
 | 
			
		||||
                expect(mockTickSource.on).toHaveBeenCalledWith("tick", jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("listener of existing tick source is reregistered", function () {
 | 
			
		||||
                api.clock("mts", mockOffsets);
 | 
			
		||||
                api.clock("amts", mockOffsets);
 | 
			
		||||
                expect(mockTickSource.off).toHaveBeenCalledWith("tick", jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("Allows the active clock to be set and unset", function () {
 | 
			
		||||
                expect(api.clock()).toBeUndefined();
 | 
			
		||||
                api.clock("mts", mockOffsets);
 | 
			
		||||
                expect(api.clock()).toBeDefined();
 | 
			
		||||
                api.stopClock();
 | 
			
		||||
                expect(api.clock()).toBeUndefined();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("on tick, observes offsets, and indicates tick in bounds callback", function () {
 | 
			
		||||
            const mockTickSource = jasmine.createSpyObj("clock", [
 | 
			
		||||
                "on",
 | 
			
		||||
                "off",
 | 
			
		||||
                "currentValue"
 | 
			
		||||
            ]);
 | 
			
		||||
            mockTickSource.currentValue.and.returnValue(100);
 | 
			
		||||
            let tickCallback;
 | 
			
		||||
            const boundsCallback = jasmine.createSpy("boundsCallback");
 | 
			
		||||
            const clockOffsets = {
 | 
			
		||||
                start: -100,
 | 
			
		||||
                end: 100
 | 
			
		||||
            };
 | 
			
		||||
            mockTickSource.key = "mts";
 | 
			
		||||
 | 
			
		||||
            anotherMockTickSource = jasmine.createSpyObj("clock", [
 | 
			
		||||
                "on",
 | 
			
		||||
                "off",
 | 
			
		||||
                "currentValue"
 | 
			
		||||
            ]);
 | 
			
		||||
            anotherMockTickSource.key = "amts";
 | 
			
		||||
            anotherMockTickSource.currentValue.and.returnValue(10);
 | 
			
		||||
 | 
			
		||||
            api.addClock(mockTickSource);
 | 
			
		||||
            api.addClock(anotherMockTickSource);
 | 
			
		||||
            api.clock("mts", clockOffsets);
 | 
			
		||||
 | 
			
		||||
            api.on("bounds", boundsCallback);
 | 
			
		||||
 | 
			
		||||
            tickCallback = mockTickSource.on.calls.mostRecent().args[1];
 | 
			
		||||
            tickCallback(1000);
 | 
			
		||||
            expect(boundsCallback).toHaveBeenCalledWith({
 | 
			
		||||
                start: 900,
 | 
			
		||||
                end: 1100
 | 
			
		||||
            }, true);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("sets bounds based on current value", function () {
 | 
			
		||||
            api.clock("mts", mockOffsets);
 | 
			
		||||
            expect(api.bounds()).toEqual({
 | 
			
		||||
                start: 10,
 | 
			
		||||
                end: 11
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("a new tick listener is registered", function () {
 | 
			
		||||
            api.clock("mts", mockOffsets);
 | 
			
		||||
            expect(mockTickSource.on).toHaveBeenCalledWith("tick", jasmine.any(Function));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("listener of existing tick source is reregistered", function () {
 | 
			
		||||
            api.clock("mts", mockOffsets);
 | 
			
		||||
            api.clock("amts", mockOffsets);
 | 
			
		||||
            expect(mockTickSource.off).toHaveBeenCalledWith("tick", jasmine.any(Function));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("Allows the active clock to be set and unset", function () {
 | 
			
		||||
            expect(api.clock()).toBeUndefined();
 | 
			
		||||
            api.clock("mts", mockOffsets);
 | 
			
		||||
            expect(api.clock()).toBeDefined();
 | 
			
		||||
            api.stopClock();
 | 
			
		||||
            expect(api.clock()).toBeUndefined();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("on tick, observes offsets, and indicates tick in bounds callback", function () {
 | 
			
		||||
        const mockTickSource = jasmine.createSpyObj("clock", [
 | 
			
		||||
            "on",
 | 
			
		||||
            "off",
 | 
			
		||||
            "currentValue"
 | 
			
		||||
        ]);
 | 
			
		||||
        mockTickSource.currentValue.and.returnValue(100);
 | 
			
		||||
        let tickCallback;
 | 
			
		||||
        const boundsCallback = jasmine.createSpy("boundsCallback");
 | 
			
		||||
        const clockOffsets = {
 | 
			
		||||
            start: -100,
 | 
			
		||||
            end: 100
 | 
			
		||||
        };
 | 
			
		||||
        mockTickSource.key = "mts";
 | 
			
		||||
 | 
			
		||||
        api.addClock(mockTickSource);
 | 
			
		||||
        api.clock("mts", clockOffsets);
 | 
			
		||||
 | 
			
		||||
        api.on("bounds", boundsCallback);
 | 
			
		||||
 | 
			
		||||
        tickCallback = mockTickSource.on.calls.mostRecent().args[1];
 | 
			
		||||
        tickCallback(1000);
 | 
			
		||||
        expect(boundsCallback).toHaveBeenCalledWith({
 | 
			
		||||
            start: 900,
 | 
			
		||||
            end: 1100
 | 
			
		||||
        }, true);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,360 +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 EventEmitter from 'EventEmitter';
 | 
			
		||||
 | 
			
		||||
class TimeContext extends EventEmitter {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        //The Time System
 | 
			
		||||
        this.timeSystems = new Map();
 | 
			
		||||
 | 
			
		||||
        this.system = undefined;
 | 
			
		||||
 | 
			
		||||
        this.clocks = new Map();
 | 
			
		||||
 | 
			
		||||
        this.boundsVal = {
 | 
			
		||||
            start: undefined,
 | 
			
		||||
            end: undefined
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.activeClock = undefined;
 | 
			
		||||
        this.offsets = undefined;
 | 
			
		||||
 | 
			
		||||
        this.tick = this.tick.bind(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the time system of the TimeAPI.
 | 
			
		||||
     * @param {TimeSystem | string} timeSystem
 | 
			
		||||
     * @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
 | 
			
		||||
     * @fires module:openmct.TimeAPI~timeSystem
 | 
			
		||||
     * @returns {TimeSystem} The currently applied time system
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method timeSystem
 | 
			
		||||
     */
 | 
			
		||||
    timeSystem(timeSystemOrKey, bounds) {
 | 
			
		||||
        if (arguments.length >= 1) {
 | 
			
		||||
            if (arguments.length === 1 && !this.activeClock) {
 | 
			
		||||
                throw new Error(
 | 
			
		||||
                    "Must specify bounds when changing time system without "
 | 
			
		||||
                    + "an active clock."
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let timeSystem;
 | 
			
		||||
 | 
			
		||||
            if (timeSystemOrKey === undefined) {
 | 
			
		||||
                throw "Please provide a time system";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (typeof timeSystemOrKey === 'string') {
 | 
			
		||||
                timeSystem = this.timeSystems.get(timeSystemOrKey);
 | 
			
		||||
 | 
			
		||||
                if (timeSystem === undefined) {
 | 
			
		||||
                    throw "Unknown time system " + timeSystemOrKey + ". Has it been registered with 'addTimeSystem'?";
 | 
			
		||||
                }
 | 
			
		||||
            } else if (typeof timeSystemOrKey === 'object') {
 | 
			
		||||
                timeSystem = timeSystemOrKey;
 | 
			
		||||
 | 
			
		||||
                if (!this.timeSystems.has(timeSystem.key)) {
 | 
			
		||||
                    throw "Unknown time system " + timeSystem.key + ". Has it been registered with 'addTimeSystem'?";
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                throw "Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.system = timeSystem;
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * The time system used by the time
 | 
			
		||||
             * conductor has changed. A change in Time System will always be
 | 
			
		||||
             * followed by a bounds event specifying new query bounds.
 | 
			
		||||
             *
 | 
			
		||||
             * @event module:openmct.TimeAPI~timeSystem
 | 
			
		||||
             * @property {TimeSystem} The value of the currently applied
 | 
			
		||||
             * Time System
 | 
			
		||||
             * */
 | 
			
		||||
            this.emit('timeSystem', this.system);
 | 
			
		||||
            if (bounds) {
 | 
			
		||||
                this.bounds(bounds);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.system;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clock offsets are used to calculate temporal bounds when the system is
 | 
			
		||||
     * ticking on a clock source.
 | 
			
		||||
     *
 | 
			
		||||
     * @typedef {object} ValidationResult
 | 
			
		||||
     * @property {boolean} valid Result of the validation - true or false.
 | 
			
		||||
     * @property {string} message An error message if valid is false.
 | 
			
		||||
     */
 | 
			
		||||
    /**
 | 
			
		||||
     * Validate the given bounds. This can be used for pre-validation of bounds,
 | 
			
		||||
     * for example by views validating user inputs.
 | 
			
		||||
     * @param {TimeBounds} bounds The start and end time of the conductor.
 | 
			
		||||
     * @returns {ValidationResult} A validation error, or true if valid
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method validateBounds
 | 
			
		||||
     */
 | 
			
		||||
    validateBounds(bounds) {
 | 
			
		||||
        if ((bounds.start === undefined)
 | 
			
		||||
            || (bounds.end === undefined)
 | 
			
		||||
            || isNaN(bounds.start)
 | 
			
		||||
            || isNaN(bounds.end)
 | 
			
		||||
        ) {
 | 
			
		||||
            return {
 | 
			
		||||
                valid: false,
 | 
			
		||||
                message: "Start and end must be specified as integer values"
 | 
			
		||||
            };
 | 
			
		||||
        } else if (bounds.start > bounds.end) {
 | 
			
		||||
            return {
 | 
			
		||||
                valid: false,
 | 
			
		||||
                message: "Specified start date exceeds end bound"
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            valid: true,
 | 
			
		||||
            message: ''
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the start and end time of the time conductor. Basic validation
 | 
			
		||||
     * of bounds is performed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
 | 
			
		||||
     * @throws {Error} Validation error
 | 
			
		||||
     * @fires module:openmct.TimeAPI~bounds
 | 
			
		||||
     * @returns {module:openmct.TimeAPI~TimeConductorBounds}
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method bounds
 | 
			
		||||
     */
 | 
			
		||||
    bounds(newBounds) {
 | 
			
		||||
        if (arguments.length > 0) {
 | 
			
		||||
            const validationResult = this.validateBounds(newBounds);
 | 
			
		||||
            if (validationResult.valid !== true) {
 | 
			
		||||
                throw new Error(validationResult.message);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //Create a copy to avoid direct mutation of conductor bounds
 | 
			
		||||
            this.boundsVal = JSON.parse(JSON.stringify(newBounds));
 | 
			
		||||
            /**
 | 
			
		||||
             * The start time, end time, or both have been updated.
 | 
			
		||||
             * @event bounds
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {TimeConductorBounds} bounds The newly updated bounds
 | 
			
		||||
             * @property {boolean} [tick] `true` if the bounds update was due to
 | 
			
		||||
             * a "tick" event (ie. was an automatic update), false otherwise.
 | 
			
		||||
             */
 | 
			
		||||
            this.emit('bounds', this.boundsVal, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //Return a copy to prevent direct mutation of time conductor bounds.
 | 
			
		||||
        return JSON.parse(JSON.stringify(this.boundsVal));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Validate the given offsets. This can be used for pre-validation of
 | 
			
		||||
     * offsets, for example by views validating user inputs.
 | 
			
		||||
     * @param {ClockOffsets} offsets The start and end offsets from a 'now' value.
 | 
			
		||||
     * @returns { ValidationResult } A validation error, and true/false if valid or not
 | 
			
		||||
     * @memberof module:openmct.TimeAPI#
 | 
			
		||||
     * @method validateOffsets
 | 
			
		||||
     */
 | 
			
		||||
    validateOffsets(offsets) {
 | 
			
		||||
        if ((offsets.start === undefined)
 | 
			
		||||
            || (offsets.end === undefined)
 | 
			
		||||
            || isNaN(offsets.start)
 | 
			
		||||
            || isNaN(offsets.end)
 | 
			
		||||
        ) {
 | 
			
		||||
            return {
 | 
			
		||||
                valid: false,
 | 
			
		||||
                message: "Start and end offsets must be specified as integer values"
 | 
			
		||||
            };
 | 
			
		||||
        } else if (offsets.start >= offsets.end) {
 | 
			
		||||
            return {
 | 
			
		||||
                valid: false,
 | 
			
		||||
                message: "Specified start offset must be < end offset"
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            valid: true,
 | 
			
		||||
            message: ''
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @typedef {Object} TimeBounds
 | 
			
		||||
     * @property {number} start The start time displayed by the time conductor
 | 
			
		||||
     * in ms since epoch. Epoch determined by currently active time system
 | 
			
		||||
     * @property {number} end The end time displayed by the time conductor in ms
 | 
			
		||||
     * since epoch.
 | 
			
		||||
     * @memberof module:openmct.TimeAPI~
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clock offsets are used to calculate temporal bounds when the system is
 | 
			
		||||
     * ticking on a clock source.
 | 
			
		||||
     *
 | 
			
		||||
     * @typedef {object} ClockOffsets
 | 
			
		||||
     * @property {number} start A time span relative to the current value of the
 | 
			
		||||
     * ticking clock, from which start bounds will be calculated. This value must
 | 
			
		||||
     * be < 0. When a clock is active, bounds will be calculated automatically
 | 
			
		||||
     * based on the value provided by the clock, and the defined clock offsets.
 | 
			
		||||
     * @property {number} end A time span relative to the current value of the
 | 
			
		||||
     * ticking clock, from which end bounds will be calculated. This value must
 | 
			
		||||
     * be >= 0.
 | 
			
		||||
     */
 | 
			
		||||
    /**
 | 
			
		||||
     * Get or set the currently applied clock offsets. If no parameter is provided,
 | 
			
		||||
     * the current value will be returned. If provided, the new value will be
 | 
			
		||||
     * used as the new clock offsets.
 | 
			
		||||
     * @param {ClockOffsets} offsets
 | 
			
		||||
     * @returns {ClockOffsets}
 | 
			
		||||
     */
 | 
			
		||||
    clockOffsets(offsets) {
 | 
			
		||||
        if (arguments.length > 0) {
 | 
			
		||||
 | 
			
		||||
            const validationResult = this.validateOffsets(offsets);
 | 
			
		||||
            if (validationResult.valid !== true) {
 | 
			
		||||
                throw new Error(validationResult.message);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.offsets = offsets;
 | 
			
		||||
 | 
			
		||||
            const currentValue = this.activeClock.currentValue();
 | 
			
		||||
            const newBounds = {
 | 
			
		||||
                start: currentValue + offsets.start,
 | 
			
		||||
                end: currentValue + offsets.end
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            this.bounds(newBounds);
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Event that is triggered when clock offsets change.
 | 
			
		||||
             * @event clockOffsets
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {ClockOffsets} clockOffsets The newly activated clock
 | 
			
		||||
             * offsets.
 | 
			
		||||
             */
 | 
			
		||||
            this.emit("clockOffsets", offsets);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.offsets;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Stop the currently active clock from ticking, and unset it. This will
 | 
			
		||||
     * revert all views to showing a static time frame defined by the current
 | 
			
		||||
     * bounds.
 | 
			
		||||
     */
 | 
			
		||||
    stopClock() {
 | 
			
		||||
        if (this.activeClock) {
 | 
			
		||||
            this.clock(undefined, undefined);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the active clock. Tick source will be immediately subscribed to
 | 
			
		||||
     * and ticking will begin. Offsets from 'now' must also be provided. A clock
 | 
			
		||||
     * can be unset by calling {@link stopClock}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Clock || string} keyOrClock The clock to activate, or its key
 | 
			
		||||
     * @param {ClockOffsets} offsets on each tick these will be used to calculate
 | 
			
		||||
     * the start and end bounds. This maintains a sliding time window of a fixed
 | 
			
		||||
     * width that automatically updates.
 | 
			
		||||
     * @fires module:openmct.TimeAPI~clock
 | 
			
		||||
     * @return {Clock} the currently active clock;
 | 
			
		||||
     */
 | 
			
		||||
    clock(keyOrClock, offsets) {
 | 
			
		||||
        if (arguments.length === 2) {
 | 
			
		||||
            let clock;
 | 
			
		||||
 | 
			
		||||
            if (typeof keyOrClock === 'string') {
 | 
			
		||||
                clock = this.clocks.get(keyOrClock);
 | 
			
		||||
                if (clock === undefined) {
 | 
			
		||||
                    throw "Unknown clock '" + keyOrClock + "'. Has it been registered with 'addClock'?";
 | 
			
		||||
                }
 | 
			
		||||
            } else if (typeof keyOrClock === 'object') {
 | 
			
		||||
                clock = keyOrClock;
 | 
			
		||||
                if (!this.clocks.has(clock.key)) {
 | 
			
		||||
                    throw "Unknown clock '" + keyOrClock.key + "'. Has it been registered with 'addClock'?";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const previousClock = this.activeClock;
 | 
			
		||||
            if (previousClock !== undefined) {
 | 
			
		||||
                previousClock.off("tick", this.tick);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.activeClock = clock;
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * The active clock has changed. Clock can be unset by calling {@link stopClock}
 | 
			
		||||
             * @event clock
 | 
			
		||||
             * @memberof module:openmct.TimeAPI~
 | 
			
		||||
             * @property {Clock} clock The newly activated clock, or undefined
 | 
			
		||||
             * if the system is no longer following a clock source
 | 
			
		||||
             */
 | 
			
		||||
            this.emit("clock", this.activeClock);
 | 
			
		||||
 | 
			
		||||
            if (this.activeClock !== undefined) {
 | 
			
		||||
                this.clockOffsets(offsets);
 | 
			
		||||
                this.activeClock.on("tick", this.tick);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } else if (arguments.length === 1) {
 | 
			
		||||
            throw "When setting the clock, clock offsets must also be provided";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.activeClock;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update bounds based on provided time and current offsets
 | 
			
		||||
     * @param {number} timestamp A time from which bounds will be calculated
 | 
			
		||||
     * using current offsets.
 | 
			
		||||
     */
 | 
			
		||||
    tick(timestamp) {
 | 
			
		||||
        if (!this.activeClock) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const newBounds = {
 | 
			
		||||
            start: timestamp + this.offsets.start,
 | 
			
		||||
            end: timestamp + this.offsets.end
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.boundsVal = newBounds;
 | 
			
		||||
        this.emit('bounds', this.boundsVal, true);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default TimeContext;
 | 
			
		||||
@@ -1,155 +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 TimeAPI from "./TimeAPI";
 | 
			
		||||
import {createOpenMct} from "utils/testing";
 | 
			
		||||
describe("The Independent Time API", function () {
 | 
			
		||||
    let api;
 | 
			
		||||
    let domainObjectKey;
 | 
			
		||||
    let clockKey;
 | 
			
		||||
    let clock;
 | 
			
		||||
    let bounds;
 | 
			
		||||
    let independentBounds;
 | 
			
		||||
    let eventListener;
 | 
			
		||||
    let openmct;
 | 
			
		||||
 | 
			
		||||
    beforeEach(function () {
 | 
			
		||||
        openmct = createOpenMct();
 | 
			
		||||
        api = new TimeAPI(openmct);
 | 
			
		||||
        clockKey = "someClockKey";
 | 
			
		||||
        clock = jasmine.createSpyObj("clock", [
 | 
			
		||||
            "on",
 | 
			
		||||
            "off",
 | 
			
		||||
            "currentValue"
 | 
			
		||||
        ]);
 | 
			
		||||
        clock.currentValue.and.returnValue(100);
 | 
			
		||||
        clock.key = clockKey;
 | 
			
		||||
        api.addClock(clock);
 | 
			
		||||
        domainObjectKey = 'test-key';
 | 
			
		||||
        bounds = {
 | 
			
		||||
            start: 0,
 | 
			
		||||
            end: 1
 | 
			
		||||
        };
 | 
			
		||||
        api.bounds(bounds);
 | 
			
		||||
        independentBounds = {
 | 
			
		||||
            start: 10,
 | 
			
		||||
            end: 11
 | 
			
		||||
        };
 | 
			
		||||
        eventListener = jasmine.createSpy("eventListener");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Creates an independent time context", () => {
 | 
			
		||||
        let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
 | 
			
		||||
        let timeContext = api.getIndependentContext(domainObjectKey);
 | 
			
		||||
        expect(timeContext.bounds()).toEqual(independentBounds);
 | 
			
		||||
        destroyTimeContext();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Gets an independent time context given the objectPath", () => {
 | 
			
		||||
        let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
 | 
			
		||||
        let timeContext = api.getContextForView([{
 | 
			
		||||
            identifier: {
 | 
			
		||||
                namespace: '',
 | 
			
		||||
                key: 'blah'
 | 
			
		||||
            }
 | 
			
		||||
        }, { identifier: domainObjectKey }]);
 | 
			
		||||
        expect(timeContext.bounds()).toEqual(independentBounds);
 | 
			
		||||
        destroyTimeContext();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("defaults to the global time context given the objectPath", () => {
 | 
			
		||||
        let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
 | 
			
		||||
        let timeContext = api.getContextForView([{
 | 
			
		||||
            identifier: {
 | 
			
		||||
                namespace: '',
 | 
			
		||||
                key: 'blah'
 | 
			
		||||
            }
 | 
			
		||||
        }]);
 | 
			
		||||
        expect(timeContext.bounds()).toEqual(bounds);
 | 
			
		||||
        destroyTimeContext();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Allows setting of valid bounds", function () {
 | 
			
		||||
        bounds = {
 | 
			
		||||
            start: 0,
 | 
			
		||||
            end: 1
 | 
			
		||||
        };
 | 
			
		||||
        let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
 | 
			
		||||
        let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
 | 
			
		||||
        expect(timeContext.bounds()).not.toEqual(bounds);
 | 
			
		||||
        timeContext.bounds(bounds);
 | 
			
		||||
        expect(timeContext.bounds()).toEqual(bounds);
 | 
			
		||||
        destroyTimeContext();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Disallows setting of invalid bounds", function () {
 | 
			
		||||
        bounds = {
 | 
			
		||||
            start: 1,
 | 
			
		||||
            end: 0
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
 | 
			
		||||
        let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
 | 
			
		||||
        expect(timeContext.bounds()).not.toBe(bounds);
 | 
			
		||||
 | 
			
		||||
        expect(timeContext.bounds.bind(timeContext, bounds)).toThrow();
 | 
			
		||||
        expect(timeContext.bounds()).not.toEqual(bounds);
 | 
			
		||||
 | 
			
		||||
        bounds = {start: 1};
 | 
			
		||||
        expect(timeContext.bounds()).not.toEqual(bounds);
 | 
			
		||||
        expect(timeContext.bounds.bind(timeContext, bounds)).toThrow();
 | 
			
		||||
        expect(timeContext.bounds()).not.toEqual(bounds);
 | 
			
		||||
        destroyTimeContext();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Emits an event when bounds change", function () {
 | 
			
		||||
        let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
 | 
			
		||||
        let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
 | 
			
		||||
        expect(eventListener).not.toHaveBeenCalled();
 | 
			
		||||
        timeContext.on('bounds', eventListener);
 | 
			
		||||
        timeContext.bounds(bounds);
 | 
			
		||||
        expect(eventListener).toHaveBeenCalledWith(bounds, false);
 | 
			
		||||
        destroyTimeContext();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    describe(" when using real time clock", function () {
 | 
			
		||||
        const mockOffsets = {
 | 
			
		||||
            start: 10,
 | 
			
		||||
            end: 11
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        it("Emits an event when bounds change based on current value", function () {
 | 
			
		||||
            let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
 | 
			
		||||
            let timeContext = api.getContextForView([{identifier: domainObjectKey}]);
 | 
			
		||||
            expect(eventListener).not.toHaveBeenCalled();
 | 
			
		||||
            timeContext.clock('someClockKey', mockOffsets);
 | 
			
		||||
            timeContext.on('bounds', eventListener);
 | 
			
		||||
            timeContext.tick(10);
 | 
			
		||||
            expect(eventListener).toHaveBeenCalledWith({
 | 
			
		||||
                start: 20,
 | 
			
		||||
                end: 21
 | 
			
		||||
            }, true);
 | 
			
		||||
            destroyTimeContext();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -63,6 +63,12 @@ define(['./Type'], function (Type) {
 | 
			
		||||
     */
 | 
			
		||||
    TypeRegistry.prototype.standardizeType = function (typeDef) {
 | 
			
		||||
        if (Object.prototype.hasOwnProperty.call(typeDef, 'label')) {
 | 
			
		||||
            console.warn(
 | 
			
		||||
                'DEPRECATION WARNING typeDef: ' + typeDef.label + '.  '
 | 
			
		||||
                + '`label` is deprecated in type definitions.  Please use '
 | 
			
		||||
                + '`name` instead.  This will cause errors in a future version '
 | 
			
		||||
                + 'of Open MCT.  For more information, see '
 | 
			
		||||
                + 'https://github.com/nasa/openmct/issues/1568');
 | 
			
		||||
            if (!typeDef.name) {
 | 
			
		||||
                typeDef.name = typeDef.label;
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -37,9 +37,11 @@ const DEFAULTS = [
 | 
			
		||||
    'platform/execution',
 | 
			
		||||
    'platform/exporters',
 | 
			
		||||
    'platform/telemetry',
 | 
			
		||||
    'platform/features/clock',
 | 
			
		||||
    'platform/forms',
 | 
			
		||||
    'platform/identity',
 | 
			
		||||
    'platform/persistence/aggregator',
 | 
			
		||||
    'platform/persistence/queue',
 | 
			
		||||
    'platform/policy',
 | 
			
		||||
    'platform/entanglement',
 | 
			
		||||
    'platform/search',
 | 
			
		||||
@@ -76,6 +78,8 @@ define([
 | 
			
		||||
    '../platform/entanglement/bundle',
 | 
			
		||||
    '../platform/execution/bundle',
 | 
			
		||||
    '../platform/exporters/bundle',
 | 
			
		||||
    '../platform/features/clock/bundle',
 | 
			
		||||
    '../platform/features/my-items/bundle',
 | 
			
		||||
    '../platform/features/static-markup/bundle',
 | 
			
		||||
    '../platform/forms/bundle',
 | 
			
		||||
    '../platform/framework/bundle',
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ describe('the plugin', function () {
 | 
			
		||||
    let openmct;
 | 
			
		||||
    let composition;
 | 
			
		||||
 | 
			
		||||
    beforeEach(() => {
 | 
			
		||||
    beforeEach((done) => {
 | 
			
		||||
 | 
			
		||||
        openmct = createOpenMct();
 | 
			
		||||
 | 
			
		||||
@@ -47,6 +47,11 @@ describe('the plugin', function () {
 | 
			
		||||
            }
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        openmct.on('start', done);
 | 
			
		||||
        openmct.startHeadless();
 | 
			
		||||
 | 
			
		||||
        composition = openmct.composition.get({identifier});
 | 
			
		||||
 | 
			
		||||
        spyOn(couchPlugin.couchProvider, 'getObjectsByFilter').and.returnValue(Promise.resolve([
 | 
			
		||||
            {
 | 
			
		||||
                identifier: {
 | 
			
		||||
@@ -61,19 +66,6 @@ describe('the plugin', function () {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        ]));
 | 
			
		||||
 | 
			
		||||
        spyOn(couchPlugin.couchProvider, "get").and.callFake((id) => {
 | 
			
		||||
            return Promise.resolve({
 | 
			
		||||
                identifier: id
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return new Promise((resolve) => {
 | 
			
		||||
            openmct.once('start', resolve);
 | 
			
		||||
            openmct.startHeadless();
 | 
			
		||||
        }).then(() => {
 | 
			
		||||
            composition = openmct.composition.get({identifier});
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    afterEach(() => {
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user