diff --git a/.circleci/config.yml b/.circleci/config.yml index 368984a985..6814113ac1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2.1 executors: pw-focal-development: docker: - - image: mcr.microsoft.com/playwright:focal + - image: mcr.microsoft.com/playwright:v1.23.0-focal environment: NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed parameters: @@ -12,7 +12,7 @@ parameters: type: boolean commands: build_and_install: - description: "All steps used to build and install. Will not work on node10" + description: "All steps used to build and install. Will use cache if found" parameters: node-version: type: string @@ -23,7 +23,7 @@ commands: - node/install: install-npm: true node-version: << parameters.node-version >> - - run: npm install + - run: npm install --prefer-offline --no-audit --progress=false restore_cache_cmd: description: "Custom command for restoring cache with the ability to bust cache. When BUST_CACHE is set to true, jobs will not restore cache" parameters: @@ -31,7 +31,7 @@ commands: type: string steps: - when: - condition: + condition: equal: [false, << pipeline.parameters.BUST_CACHE >> ] steps: - restore_cache: @@ -41,7 +41,7 @@ commands: parameters: node-version: type: string - steps: + steps: - save_cache: key: deps-{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}-{{ checksum ".circleci/config.yml" }} paths: @@ -58,13 +58,17 @@ commands: ls -latR >> /tmp/artifacts/dir.txt - store_artifacts: path: /tmp/artifacts/ - upload_code_covio: - description: "Command to upload code coverage reports to codecov.io" - steps: - - run: curl -Os https://uploader.codecov.io/latest/linux/codecov;chmod +x codecov;./codecov + generate_e2e_code_cov_report: + description: "Generate e2e code coverage artifacts and publish to codecov.io. Needed to that we can ignore the exit code status of the npm run test" + parameters: + suite: + type: string + steps: + - run: npm run cov:e2e:report + - run: npm run cov:e2e:<>:publish orbs: node: circleci/node@4.9.0 - browser-tools: circleci/browser-tools@1.2.3 + browser-tools: circleci/browser-tools@1.3.0 jobs: npm-audit: parameters: @@ -76,14 +80,14 @@ jobs: node-version: <> - run: npm audit --audit-level=low - generate_and_store_version_and_filesystem_artifacts - node10-lint: + lint: + parameters: + node-version: + type: string executor: pw-focal-development steps: - - checkout - - node/install: - install-npm: false #Cannot install latest npm version with node10. - node-version: lts/dubnium - - run: npm install + - build_and_install: + node-version: <> - run: npm run lint - generate_and_store_version_and_filesystem_artifacts unit-test: @@ -101,7 +105,7 @@ jobs: equal: [ "FirefoxESR", <> ] steps: - browser-tools/install-firefox: - version: "91.4.0esr" #https://archive.mozilla.org/pub/firefox/releases/ + version: "91.7.1esr" #https://archive.mozilla.org/pub/firefox/releases/ - when: condition: equal: [ "FirefoxHeadless", <> ] @@ -113,13 +117,14 @@ jobs: steps: - browser-tools/install-chrome: replace-existing: false - - run: npm run test:coverage -- --browsers=<> + - run: npm run test -- --browsers=<> + - run: npm run cov:unit:publish - save_cache_cmd: node-version: <> - store_test_results: path: dist/reports/tests/ - store_artifacts: - path: dist/reports/ + path: coverage - generate_and_store_version_and_filesystem_artifacts e2e-test: parameters: @@ -128,48 +133,69 @@ jobs: suite: type: string executor: pw-focal-development + parallelism: 4 steps: - build_and_install: node-version: <> - - run: npx playwright install - - run: npm run test:e2e:<> + - when: #Only install chrome-beta when running the full suite to save $$$ + condition: + equal: [ "full", <> ] + steps: + - run: npx playwright install chrome-beta + - run: SHARD="$((${CIRCLE_NODE_INDEX}+1))"; npm run test:e2e:<> -- --shard=${SHARD}/${CIRCLE_NODE_TOTAL} + - generate_e2e_code_cov_report: + suite: <> - store_test_results: path: test-results/results.xml - store_artifacts: path: test-results + - store_artifacts: + path: coverage + - store_artifacts: + path: html-test-results + - generate_and_store_version_and_filesystem_artifacts + perf-test: + parameters: + node-version: + type: string + executor: pw-focal-development + steps: + - build_and_install: + node-version: <> + - run: npm run test:perf + - store_test_results: + path: test-results/results.xml + - store_artifacts: + path: test-results + - store_artifacts: + path: html-test-results - generate_and_store_version_and_filesystem_artifacts workflows: overall-circleci-commit-status: #These jobs run on every commit jobs: - - node10-lint + - lint: + name: node14-lint + node-version: lts/fermium - unit-test: - name: node12-chrome - node-version: lts/erbium + name: node16-chrome + node-version: lts/gallium browser: ChromeHeadless - - unit-test: - name: node14-chrome - node-version: lts/fermium + - unit-test: + name: node18-chrome + node-version: "18" browser: ChromeHeadless - post-steps: - - upload_code_covio - e2e-test: - name: e2e-smoke - node-version: lts/fermium + name: e2e-ci + node-version: lts/gallium suite: ci + - perf-test: + node-version: lts/gallium the-nightly: #These jobs do not run on PRs, but against master at night jobs: - unit-test: - name: node10-chrome-nightly - node-version: lts/dubnium - browser: ChromeHeadless - - unit-test: - name: node12-firefoxESR-nightly - node-version: lts/erbium + name: node16-firefoxESR-nightly + node-version: lts/gallium browser: FirefoxESR - - unit-test: - name: node12-chrome-nightly - node-version: lts/erbium - browser: ChromeHeadless - unit-test: name: node14-firefox-nightly node-version: lts/fermium @@ -178,11 +204,19 @@ workflows: name: node14-chrome-nightly node-version: lts/fermium browser: ChromeHeadless + - unit-test: + name: node16-chrome-nightly + node-version: lts/gallium + browser: ChromeHeadless + - unit-test: + name: node18-chrome + node-version: "18" + browser: ChromeHeadless - npm-audit: - node-version: lts/fermium + node-version: lts/gallium - e2e-test: name: e2e-full-nightly - node-version: lts/fermium + node-version: lts/gallium suite: full triggers: - schedule: diff --git a/.eslintrc.js b/.eslintrc.js index 03a5b1fefe..26e8074908 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,4 @@ -const LEGACY_FILES = ["platform/**", "example/**"]; +const LEGACY_FILES = ["example/**"]; module.exports = { "env": { "browser": true, @@ -11,12 +11,14 @@ module.exports = { }, "extends": [ "eslint:recommended", + "plugin:compat/recommended", "plugin:vue/recommended", "plugin:you-dont-need-lodash-underscore/compatible" ], "parser": "vue-eslint-parser", "parserOptions": { - "parser": "babel-eslint", + "parser": "@babel/eslint-parser", + "requireConfigFile": false, "allowImportExportEverywhere": true, "ecmaVersion": 2015, "ecmaFeatures": { @@ -27,6 +29,7 @@ module.exports = { "you-dont-need-lodash-underscore/omit": "off", "you-dont-need-lodash-underscore/throttle": "off", "you-dont-need-lodash-underscore/flatten": "off", + "you-dont-need-lodash-underscore/get": "off", "no-bitwise": "error", "curly": "error", "eqeqeq": "error", @@ -35,7 +38,6 @@ module.exports = { "no-inner-declarations": "off", "no-use-before-define": ["error", "nofunc"], "no-caller": "error", - "no-sequences": "error", "no-irregular-whitespace": "error", "no-new": "error", "no-shadow": "error", @@ -239,13 +241,12 @@ module.exports = { ], "vue/max-attributes-per-line": ["error", { "singleline": 1, - "multiline": { - "max": 1, - "allowFirstLine": true - } + "multiline": 1, }], + "vue/first-attribute-linebreak": "error", "vue/multiline-html-element-content-newline": "off", "vue/singleline-html-element-content-newline": "off", + "vue/multi-word-component-names": "off", // TODO enable, align with conventions "vue/no-mutating-props": "off" }, diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index cdf9474c19..2fd6b80917 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -17,15 +17,6 @@ assignees: '' #### Expected vs Current Behavior -#### Impact Check List - - -- [ ] Data loss or misrepresented data? -- [ ] Regression? Did this used to work or has it always been broken? -- [ ] Is there a workaround available? -- [ ] Does this impact a critical component? -- [ ] Is this just a visual bug with no functional impact? - #### Steps to Reproduce @@ -35,10 +26,22 @@ assignees: '' 4. #### Environment + * Open MCT Version: * Deployment Type: * OS: * Browser: +#### Impact Check List + +- [ ] Data loss or misrepresented data? +- [ ] Regression? Did this used to work or has it always been broken? +- [ ] Is there a workaround available? +- [ ] Does this impact a critical component? +- [ ] Is this just a visual bug with no functional impact? +- [ ] Does this block the execution of e2e tests? +- [ ] Does this have an impact on Performance? + #### Additional Information diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 819bdf6fb6..8cbde6e73d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,9 @@ + +Closes + +### Describe your changes: + + ### All Submissions: * [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)? @@ -10,4 +16,14 @@ * [ ] Unit tests included and/or updated with changes? * [ ] Command line build passes? * [ ] Has this been smoke tested? -* [ ] Testing instructions included in associated issue? +* [ ] Testing instructions included in associated issue OR is this a dependency/testcase change? + +### 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? +* [ ] Has associated issue been labelled unverified? (only applicable if this PR closes the issue) +* [ ] Has associated issue been labelled bug? (only applicable if this PR is for a bug fix) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1e240e5439..a5545f0cd7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,18 +5,14 @@ updates: directory: "/" schedule: interval: "daily" - open-pull-requests-limit: 4 + open-pull-requests-limit: 10 labels: - "type:maintenance" - "dependencies" - "pr:e2e" - allow: - - dependency-name: "*eslint*" - - dependency-name: "*karma*" - - dependency-name: "*jasmine*" - - dependency-name: "*playwright*" - - dependency-name: "*percy*" - - dependency-name: "*vue-loader*" + - "pr:daveit" + - "pr:visual" + - "pr:platform" - package-ecosystem: "github-actions" directory: "/" @@ -25,3 +21,4 @@ updates: labels: - "type:maintenance" - "dependencies" + - "pr:daveit" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 558fb4b1ad..eb834fd9d9 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -28,16 +28,16 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: javascript - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/e2e-pr.yml b/.github/workflows/e2e-pr.yml index b16920369d..b21bf8ce79 100644 --- a/.github/workflows/e2e-pr.yml +++ b/.github/workflows/e2e-pr.yml @@ -2,52 +2,61 @@ name: "e2e-pr" on: workflow_dispatch: pull_request: - types: [ labeled ] + types: + - labeled + - opened jobs: e2e-full: if: ${{ github.event.label.name == 'pr:e2e' }} - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + - windows-latest steps: - name: Trigger Success - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: script: | github.rest.issues.createComment({ issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, + owner: "nasa", + repo: "openmct", body: 'Started e2e Run. Follow along: https://github.com/nasa/openmct/actions/runs/' + context.runId }) - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: - node-version: '14' + node-version: '16' + - run: npx playwright@1.23.0 install + - run: npx playwright install chrome-beta - run: npm install - run: npm run test:e2e:full - name: Archive test results - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: path: test-results - name: Test success if: ${{ success() }} - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: script: | github.rest.issues.createComment({ issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, + owner: "nasa", + repo: "openmct", body: 'Success ✅ ! Build artifacts are here: https://github.com/nasa/openmct/actions/runs/' + context.runId }) - name: Test failure if: ${{ failure() }} - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: script: | github.rest.issues.createComment({ issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, + owner: "nasa", + repo: "openmct", body: 'Failure ❌ ! Build artifacts are here: https://github.com/nasa/openmct/actions/runs/' + context.runId }) diff --git a/.github/workflows/e2e-visual.yml b/.github/workflows/e2e-visual.yml index fdc415e92a..11c8da3caf 100644 --- a/.github/workflows/e2e-visual.yml +++ b/.github/workflows/e2e-visual.yml @@ -4,6 +4,7 @@ on: pull_request: types: - labeled + - opened schedule: - cron: '28 21 * * 1-5' @@ -12,10 +13,11 @@ jobs: if: ${{ github.event.label.name == 'pr:visual' }} || ${{ github.event.workflow_dispatch }} || ${{ github.event.schedule }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: - node-version: '14' + node-version: '16' + - run: npx playwright@1.23.0 install - run: npm install - name: Run the e2e visual tests run: npm run test:e2e:visual diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index bcde03a7f1..4dfd2435b8 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -10,12 +10,12 @@ jobs: e2e: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: ref: ${{ github.event.inputs.version }} - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 with: - node-version: '14' + node-version: '16' - run: npm install - name: Run the e2e tests run: npm run test:e2e:ci diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index 4a67227c0b..a2df653447 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -5,16 +5,94 @@ on: version: description: 'Which branch do you want to test?' # Limited to branch for now required: false - default: 'master' + default: 'master' + pull_request: + types: + - labeled jobs: - lighthouse: + lighthouse-pr: + if: ${{ github.event.label.name == 'pr:lighthouse' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout Master for Baseline + uses: actions/checkout@v3 + with: + ref: master #explicitly checkout master for baseline + - name: Install Node 16 + uses: actions/setup-node@v3 + with: + node-version: '16' + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }} + - name: npm install with lighthouse cli + run: npm install && npm install -g @lhci/cli + - name: Run lhci against master to generate baseline and ignore exit codes + run: lhci autorun || true + - name: Perform clean checkout of PR + uses: actions/checkout@v3 + with: + clean: true + - name: Install Node version which is compatible with PR + uses: actions/setup-node@v3 + - name: npm install with lighthouse cli + run: npm install && npm install -g @lhci/cli + - name: Run lhci with PR + run: lhci autorun + env: + LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }} + lighthouse-nightly: + if: ${{ github.event.schedule }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Node 16 + uses: actions/setup-node@v3 + with: + node-version: '16' + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }} + - name: npm install with lighthouse cli + run: npm install && npm install -g @lhci/cli + - name: Run lhci against master to generate baseline + run: lhci autorun + env: + LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }} + lighthouse-dispatch: + if: ${{ github.event.workflow_dispatch }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 with: ref: ${{ github.event.inputs.version }} - - uses: actions/setup-node@v2 + - name: Install Node 14 + uses: actions/setup-node@v3 with: - node-version: '14' - - run: npm install && npm install -g @lhci/cli #Don't want to include this in our deps - - run: lhci autorun \ No newline at end of file + node-version: '16' + - name: Cache node modules + uses: actions/cache@v3 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }} + - name: npm install with lighthouse cli + run: npm install && npm install -g @lhci/cli + - name: Run lhci against master to generate baseline + run: lhci autorun + \ No newline at end of file diff --git a/.github/workflows/npm-prerelease.yml b/.github/workflows/npm-prerelease.yml index 9230bfff1d..b92d217902 100644 --- a/.github/workflows/npm-prerelease.yml +++ b/.github/workflows/npm-prerelease.yml @@ -11,10 +11,10 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 16 - run: npm install - run: npm test @@ -22,10 +22,10 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 16 registry-url: https://registry.npmjs.org/ - run: npm install - run: npm publish --access public --tag unstable diff --git a/.github/workflows/pr-platform.yml b/.github/workflows/pr-platform.yml new file mode 100644 index 0000000000..dc46123078 --- /dev/null +++ b/.github/workflows/pr-platform.yml @@ -0,0 +1,34 @@ +name: "pr-platform" +on: + workflow_dispatch: + pull_request: + types: [ labeled ] + +jobs: + e2e-full: + if: ${{ github.event.label.name == 'pr:platform' }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - macos-latest + - windows-latest + node_version: + - 14 + - 16 + - 18 + architecture: + - x64 + name: Node ${{ matrix.node_version }} - ${{ matrix.architecture }} on ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node_version }} + architecture: ${{ matrix.architecture }} + - run: npm install + - run: npm test + - run: npm run lint -- --quiet diff --git a/.github/workflows/prcop-config.json b/.github/workflows/prcop-config.json new file mode 100644 index 0000000000..5c278b0ec0 --- /dev/null +++ b/.github/workflows/prcop-config.json @@ -0,0 +1,19 @@ +{ + "linters": [ + { + "name": "descriptionRegexp", + "config": { + "regexp": "x] Testing instructions", + "errorMessage": ":police_officer: PR Description does not confirm that associated issue(s) contain Testing instructions" + } + }, + { + "name": "descriptionMinWords", + "config": { + "minWordsCount": 160, + "errorMessage": ":police_officer: Please, be sure to use existing PR template." + } + } + ], + "disableWord": "pr:daveit" +} diff --git a/.github/workflows/prcop.yml b/.github/workflows/prcop.yml new file mode 100644 index 0000000000..4ee2c5569c --- /dev/null +++ b/.github/workflows/prcop.yml @@ -0,0 +1,26 @@ +name: PRCop + +on: + pull_request: + types: + - opened + - reopened + - edited + - synchronize + - ready_for_review + - review_requested + - review_request_removed + pull_request_review_comment: + types: + - created + +jobs: + prcop: + runs-on: ubuntu-latest + name: Template Check + steps: + - name: Linting Pull Request + uses: makaroni4/prcop@v1.0.35 + with: + config-file: ".github/workflows/prcop-config.json" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 7c608343c6..11481c4c40 100644 --- a/.gitignore +++ b/.gitignore @@ -15,8 +15,6 @@ *.idea *.iml -# External dependencies - # Build output target dist @@ -24,30 +22,24 @@ dist # Mac OS X Finder .DS_Store -# Closed source libraries -closed-lib - # Node, Bower dependencies node_modules bower_components -# Protractor logs -protractor/logs - # npm-debug log npm-debug.log # karma reports report.*.json -# Lighthouse reports -.lighthouseci - # e2e test artifacts test-results -allure-results +html-test-results -package-lock.json - -#codecov artifacts +# codecov artifacts +.nyc_output +coverage codecov + +# :( +package-lock.json diff --git a/.npmignore b/.npmignore index 13b8b511e2..4dc1a00188 100644 --- a/.npmignore +++ b/.npmignore @@ -1,44 +1,27 @@ -*.scssc -*.zip -*.gzip -*.tgz -*.DS_Store +# Ignore everything first (will not ignore special files like LICENSE.md, +# README.md, and package.json)... +/**/* -*.sass-cache -*COMPILE.css +# ...but include these folders... +!/dist/**/* +!/src/**/* -# Intellij project configuration files -*.idea -*.iml +# We might be able to remove this if it is not imported by any project directly. +# https://github.com/nasa/openmct/issues/4992 +!/example/**/* -# External dependencies +# We will remove this in https://github.com/nasa/openmct/issues/4922 +!/app.js -# Build output -target +# ...except for these files in the above folders. +/src/**/*Spec.js +/src/**/test/ +# TODO move test utils into test/ folders +/src/utils/testing.js -# Mac OS X Finder -.DS_Store - -# Closed source libraries -closed-lib - -# Node, Bower dependencies -node_modules -bower_components - -Procfile - -# Protractor logs -protractor/logs - -# npm-debug log -npm-debug.log - -# Infra and tests -.circleci -.github -e2e -codecov.yml -lighthouserc.yml -*.Spec.js -karma.conf.js +# Also include these special top-level files. +!copyright-notice.js +!copyright-notice.html +!index.html +!openmct.js +!SECURITY.md \ No newline at end of file diff --git a/.npmrc b/.npmrc index 3b88981879..d747c7f48b 100644 --- a/.npmrc +++ b/.npmrc @@ -1,6 +1,4 @@ loglevel=warn -# Temporary: istanbul-instrumenter-loader is working with webpack 5, but states -# webpack 4 being the latest version it supports, so this legacy-peer-deps -# allows us to install it anyway. -legacy-peer-deps=true \ No newline at end of file +#Prevent folks from ignoring an important error when building from source +engine-strict=true \ No newline at end of file diff --git a/API.md b/API.md index c5f27843c2..a56a0de187 100644 --- a/API.md +++ b/API.md @@ -52,6 +52,8 @@ - [The URL Status Indicator](#the-url-status-indicator) - [Creating a Simple Indicator](#creating-a-simple-indicator) - [Custom Indicators](#custom-indicators) + - [Priority API](#priority-api) + - [Priority Types](#priority-types) @@ -247,16 +249,24 @@ To do so, use the `addRoot` method of the object API. eg. ```javascript openmct.objects.addRoot({ - namespace: "example.namespace", - key: "my-key" - }); + namespace: "example.namespace", + key: "my-key" +}, +openmct.priority.HIGH); ``` -The `addRoot` function takes a single [object identifier](#domain-objects-and-identifiers) -as an argument. +The `addRoot` function takes a two arguments, the first can be an [object identifier](#domain-objects-and-identifiers) for a root level object, or an array of identifiers for root +level objects, or a function that returns a promise for an identifier or an array of root level objects, the second is a [priority](#priority-api) or numeric value. -Root objects are loaded just like any other objects, i.e. via an object -provider. +When using the `getAll` method of the object API, they will be returned in order of priority. + +eg. +```javascript +openmct.objects.addRoot(identifier, openmct.priority.LOW); // low = -1000, will appear last in composition or tree +openmct.objects.addRoot(otherIdentifier, openmct.priority.HIGH); // high = 1000, will appear first in composition or tree +``` + +Root objects are loaded just like any other objects, i.e. via an object provider. ## Object Providers @@ -1051,3 +1061,25 @@ A completely custom indicator can be added by simply providing a DOM element to element: domNode }); ``` + +## Priority API + +Open MCT provides some built-in priority values that can be used in the application for view providers, indicators, root object order, and more. + +### Priority Types + +Currently, the Open MCT Priority API provides (type: numeric value): +- HIGH: 1000 +- Default: 0 +- LOW: -1000 + +View provider Example: + +``` javascript + class ViewProvider { + ... + priority() { + return openmct.priority.HIGH; + } +} +``` \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md index 04d36576a2..2e87024fd1 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # Open MCT License -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, Copyright (c) 2014-2022, 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. diff --git a/README.md b/README.md index dbc04ecf77..8321674764 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Open MCT [![license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/nasa/openmct.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/nasa/openmct/context:javascript) [![codecov](https://codecov.io/gh/nasa/openmct/branch/master/graph/badge.svg?token=7DQIipp3ej)](https://codecov.io/gh/nasa/openmct) [![This project is using Percy.io for visual regression testing.](https://percy.io/static/images/percy-badge.svg)](https://percy.io/b2e34b17/openmct) +# Open MCT [![license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/nasa/openmct.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/nasa/openmct/context:javascript) [![codecov](https://codecov.io/gh/nasa/openmct/branch/master/graph/badge.svg?token=7DQIipp3ej)](https://codecov.io/gh/nasa/openmct) [![This project is using Percy.io for visual regression testing.](https://percy.io/static/images/percy-badge.svg)](https://percy.io/b2e34b17/openmct) [![npm version](https://img.shields.io/npm/v/openmct.svg)](https://www.npmjs.com/package/openmct) 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. @@ -11,6 +11,22 @@ Once you've created something amazing with Open MCT, showcase your work in our G Try Open MCT now with our [live demo](https://openmct-demo.herokuapp.com/). ![Demo](https://nasa.github.io/openmct/static/res/images/Open-MCT.Browse.Layout.Mars-Weather-1.jpg) +## Open MCT v2.0.0 +Support for our legacy bundle-based API, and the libraries that it was built on (like Angular 1.x), have now been removed entirely from this repository. + +For now if you have an Open MCT application that makes use of the legacy API, [a plugin](https://github.com/nasa/openmct-legacy-plugin) is provided that bootstraps the legacy bundling mechanism and API. This plugin will not be maintained over the long term however, and the legacy support plugin will not be tested for compatibility with future versions of Open MCT. It is provided for convenience only. + +### How do I know if I am using legacy API? +You might still be using legacy API if your source code + +* Contains files named bundle.js, or bundle.json, +* Makes calls to `openmct.$injector()`, or `openmct.$angular`, +* Makes calls to `openmct.legacyRegistry`, `openmct.legacyExtension`, or `openmct.legacyBundle`. + + +### What should I do if I am using legacy API? +Please refer to [the modern Open MCT API](https://nasa.github.io/openmct/documentation/). Post any questions to the [Discussions section](https://github.com/nasa/openmct/discussions) of the Open MCT GitHub repository. + ## Building and Running Open MCT Locally Building and running Open MCT in your local dev environment is very easy. Be sure you have [Git](https://git-scm.com/downloads) and [Node.js](https://nodejs.org/) installed, then follow the directions below. Need additional information? Check out the [Getting Started](https://nasa.github.io/openmct/getting-started/) page on our website. @@ -20,7 +36,7 @@ Building and running Open MCT in your local dev environment is very easy. Be sur `git clone https://github.com/nasa/openmct.git` -2. Install development dependencies +2. Install development dependencies. Note: Check the package.json engine for our tested and supported node versions. `npm install` @@ -30,11 +46,6 @@ Building and running Open MCT in your local dev environment is very easy. Be sur Open MCT is now running, and can be accessed by pointing a web browser at [http://localhost:8080/](http://localhost:8080/) -## Open MCT v1.0.0 -This represents a major overhaul of Open MCT with significant changes under the hood. We aim to maintain backward compatibility but if you do find compatibility issues, please let us know by filing an issue in this repository. If you are having major issues with v1.0.0 please check-out the v0.14.0 tag until we can resolve them for you. - -If you are migrating an application built with Open MCT as a dependency to v1.0.0 from an earlier version, please refer to [our migration guide](https://nasa.github.io/openmct/documentation/migration-guide). - ## Documentation Documentation is available on the [Open MCT website](https://nasa.github.io/openmct/documentation/). @@ -54,6 +65,12 @@ Open MCT is built using [`npm`](http://npmjs.com/) and [`webpack`](https://webpa See our documentation for a guide on [building Applications with Open MCT](https://github.com/nasa/openmct/blob/master/API.md#starting-an-open-mct-application). +## Compatibility + +This is a fast moving project and we do our best to test and support the widest possible range of browsers, operating systems, and nodejs APIs. We have a published list of support available in our package.json's `browserslist` key. + +If you encounter an issue with a particular browser, OS, or nodejs API, please file a [GitHub issue](https://github.com/nasa/openmct/issues/new/choose) + ## Plugins Open MCT can be extended via plugins that make calls to the Open MCT API. A plugin is a group diff --git a/app.js b/app.js index 76f9f66ff4..baa951e129 100644 --- a/app.js +++ b/app.js @@ -1,4 +1,4 @@ -/*global require,process,console*/ +/*global process*/ /** * Usage: @@ -12,6 +12,7 @@ const express = require('express'); const app = express(); const fs = require('fs'); const request = require('request'); +const __DEV__ = process.env.NODE_ENV === 'development'; // Defaults options.port = options.port || options.p || 8080; @@ -49,14 +50,18 @@ class WatchRunPlugin { } const webpack = require('webpack'); -const webpackConfig = require('./webpack.dev.js'); -webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin()); -webpackConfig.plugins.push(new WatchRunPlugin()); - -webpackConfig.entry.openmct = [ - 'webpack-hot-middleware/client?reload=true', - webpackConfig.entry.openmct -]; +let webpackConfig; +if (__DEV__) { + webpackConfig = require('./webpack.dev'); + webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin()); + webpackConfig.entry.openmct = [ + 'webpack-hot-middleware/client?reload=true', + webpackConfig.entry.openmct + ]; + webpackConfig.plugins.push(new WatchRunPlugin()); +} else { + webpackConfig = require('./webpack.coverage'); +} const compiler = webpack(webpackConfig); @@ -64,14 +69,16 @@ app.use(require('webpack-dev-middleware')( compiler, { publicPath: '/dist', - logLevel: 'warn' + stats: 'errors-warnings' } )); -app.use(require('webpack-hot-middleware')( - compiler, - {} -)); +if (__DEV__) { + app.use(require('webpack-hot-middleware')( + compiler, + {} + )); +} // Expose index.html for development users. app.get('/', function (req, res) { @@ -79,6 +86,6 @@ app.get('/', function (req, res) { }); // Finally, open the HTTP server and log the instance to the console -app.listen(options.port, options.host, function() { +app.listen(options.port, options.host, function () { console.log('Open MCT application running at %s:%s', options.host, options.port); }); diff --git a/babel.coverage.js b/babel.coverage.js new file mode 100644 index 0000000000..6a752a9e52 --- /dev/null +++ b/babel.coverage.js @@ -0,0 +1,9 @@ +// This is a Babel config that webpack.coverage.js uses in order to instrument +// code with coverage instrumentation. +const babelConfig = { + plugins: [['babel-plugin-istanbul', { + extension: ['.js', '.vue'] + }]] +}; + +module.exports = babelConfig; diff --git a/build-docs.sh b/build-docs.sh index 6627f3d176..d30af4f109 100755 --- a/build-docs.sh +++ b/build-docs.sh @@ -1,7 +1,7 @@ #!/bin/bash #***************************************************************************** -#* Open MCT, Copyright (c) 2014-2021, United States Government +#* Open MCT, Copyright (c) 2014-2022, United States Government #* as represented by the Administrator of the National Aeronautics and Space #* Administration. All rights reserved. #* diff --git a/codecov.yml b/codecov.yml index 245a63ec71..d69c35bbdb 100644 --- a/codecov.yml +++ b/codecov.yml @@ -13,19 +13,16 @@ coverage: round: down range: "66...100" -ignore: - - "**/*Spec.js" - - "e2e" - -parsers: - gcov: - branch_detection: - conditional: true - loop: true - method: false - macro: false +flags: + unit: + carryforward: true + e2e-ci: + carryforward: true + e2e-full: + carryforward: true comment: layout: "reach,diff,flags,files,footer" behavior: default require_changes: false + show_carryforward_flags: true \ No newline at end of file diff --git a/copyright-notice.html b/copyright-notice.html index 4a5b0519ed..135675cd0e 100644 --- a/copyright-notice.html +++ b/copyright-notice.html @@ -1,5 +1,5 @@ [Platform bundle #1] - [Dependency injection framework]-->[Platform bundle #2] - [Dependency injection framework]-->[Plugin bundle #1] - [Dependency injection framework]-->[Plugin bundle #2] - [Platform bundle #1|[Extensions]] - [Platform bundle #2|[Extensions]] - [Plugin bundle #1|[Extensions]] - [Plugin bundle #2|[Extensions]] - [Platform bundle #1]<->[Platform bundle #2] - [Plugin bundle #1]<->[Platform bundle #2] - [Plugin bundle #1]<->[Plugin bundle #2] -] -``` - -The "dependency injection framework" in this case is -[AngularJS](https://angularjs.org/). Open MCT's framework layer -is really just a thin wrapper over Angular that recognizes the -concepts of bundles and extensions (as declared in JSON files) and -registering extensions with Angular. It additionally acts as a -mediator between Angular and [RequireJS](http://requirejs.org/), -which is used to load JavaScript sources which implement -extensions. - -```nomnoml -[Framework layer| - [AngularJS]<-[Framework Component] - [RequireJS]<-[Framework Component] - [Framework Component]1o-*[Bundles] -] -``` - -It is worth noting that _no other components_ are "aware" of the -framework component directly; Angular and Require are _used by_ the -framework components, and extensions in various bundles will have -their dependencies satisfied by Angular as a consequence of registration -activities which were performed by the framework component. - - -## Application Initialization - -The framework component initializes an Open MCT application following -a simple sequence of steps. - -```nomnoml -[ Start]->[ Load bundles.json] -[Load bundles.json]->[ Load bundle.json files] -[Load bundle.json files]->[ Resolve implementations] -[Resolve implementations]->[ Register with Angular] -[Register with Angular]->[ Bootstrap application] -[Bootstrap application]->[ End] -``` - -1. __Loading bundles.json.__ A file named `bundles.json` is loaded to determine - which bundles to load. Bundles are given in this file as relative paths - which point to bundle directories. -2. __Load bundle.json files.__ Individual bundle definitions are loaded; a - `bundle.json` file is expected in each bundle directory. -2. __Resolving implementations.__ Any scripts which provide implementations for - extensions exposed by bundles are loaded, using RequireJS. -3. __Register with Angular.__ Resolved extensions are registered with Angular, - such that they can be used by the application at run-time. This stage - includes both registration of Angular built-ins (directives, controllers, - routes, constants, and services) as well as registration of non-Angular - extensions. -4. __Bootstrap application.__ Once all extensions have been registered, - the Angular application - [is bootstrapped](https://docs.angularjs.org/guide/bootstrap). - -## Architectural Paradigm - -```nomnoml -[Extension] -[Extension]o->[Dependency #1] -[Extension]o->[Dependency #2] -[Extension]o->[Dependency #3] -``` - -Open MCT's architecture relies on a simple premise: Individual units -(extensions) only have access to the dependencies they declare that they -need, and they acquire references to these dependencies via dependency -injection. This has several desirable traits: - -* Programming to an interface is enforced. Any given dependency can be - swapped out for something which exposes an equivalent interface. This - improves flexibility against refactoring, simplifies testing, and - provides a common mechanism for extension and reconfiguration. -* The dependencies of a unit must be explicitly defined. This means that - it can be easily determined what a given unit's role is within the - larger system, in terms of what other components it will interact with. - It also helps to enforce good separation of concerns: When a set of - declared dependencies becomes long it is obvious, and this is usually - a sign that a given unit is involved in too many concerns and should - be refactored into smaller pieces. -* Individual units do not need to be aware of the framework; they need - only be aware of the interfaces to the components they specifically - use. This avoids introducing a ubiquitous dependency upon the framework - layer itself; it is plausible to modify or replace the framework - without making changes to individual software components which run upon - the framework. - -A drawback to this approach is that it makes it difficult to define -"the architecture" of Open MCT, in terms of describing the specific -units that interact at run-time. The run-time architecture is determined -by the framework as the consequence of wiring together dependencies. -As such, the specific architecture of any given application built on -Open MCT can look very different. - -Keeping that in mind, there are a few useful patterns supported by the -framework that are useful to keep in mind. - -The specific service infrastructure provided by the platform is described -in the [Platform Architecture](platform.md). - -## Extension Categories - -One of the capabilities that the framework component layers on top of -AngularJS is support for many-to-one dependencies. That is, a specific -extension may declare a dependency to _all extensions of a specific -category_, instead of being limited to declaring specific dependencies. - -```nomnoml -#direction: right -[Specific Extension] 1 o-> * [Extension of Some Category] -``` - -This is useful for introducing specific extension points to an application. -Some unit of software will depend upon all extensions of a given category -and integrate their behavior into the system in some fashion; plugin authors -can then add new extensions of that category to augment existing behaviors. - -Some developers may be familiar with the use of registries to achieve -similar characteristics. This approach is similar, except that the registry -is effectively implicit whenever a new extension category is used or -depended-upon. This has some advantages over a more straightforward -registry-based approach: - -* These many-to-one relationships are expressed as dependencies; an - extension category is registered as having dependencies on all individual - extensions of this category. This avoids ordering issues that may occur - with more conventional registries, which may be observed before all - dependencies are resolved. -* The need for service registries of specific types is removed, reducing - the number of interfaces to manage within the system. Groups of - extensions are provided as arrays. - -## Composite Services - -Composite services (registered via extension category `components`) are -a pattern supported by the framework. These allow service instances to -be built from multiple components at run-time; support for this pattern -allows additional bundles to introduce or modify behavior associated -with these services without modifying or replacing original service -instances. - -```nomnoml -#direction: down -[ FooService] -[FooDecorator #1]--:>[FooService] -[FooDecorator #n]--:>[FooService] -[FooAggregator]--:>[FooService] -[FooProvider #1]--:>[FooService] -[FooProvider #n]--:>[FooService] - -[FooDecorator #1]o->[ ...decorators...] -[...decorators...]o->[FooDecorator #n] -[FooDecorator #n]o->[FooAggregator] -[FooAggregator]o->[FooProvider #1] -[FooAggregator]o->[ ...providers...] -[FooAggregator]o->[FooProvider #n] - -[FooDecorator #1]--[ Exposed as fooService] -``` - -In this pattern, components all implement an interface which is -standardized for that service. Components additionally declare -that they belong to one of three types: - -* __Providers.__ A provider actually implements the behavior - (satisfies the contract) for that kind of service. For instance, - if a service is responsible for looking up documents by an identifier, - one provider may do so by querying a database, while another may - do so by reading a static JSON document. From the outside, either - provider would look the same (they expose the same interface) and - they could be swapped out easily. -* __Aggregator.__ An aggregator takes many providers and makes them - behave as one. Again, this implements the same interface as an - individual provider, so users of the service do not need to be - concerned about the difference between consulting many providers - and consulting one. Continuing with the example of a service that - looks up documents by identifiers, an aggregator here might consult - all providers, and return any document is found (perhaps picking one - over the other or merging documents if there are multiple matches.) -* __Decorators.__ A decorator exposes the same interface as other - components, but instead of fully implementing the behavior associated - with that kind of service, it only acts as an intermediary, delegating - the actual behavior to a different component. Decorators may transform - inputs or outputs, or initiate some side effects associated with a - service. This is useful if certain common behavior associated with a - service (caching, for instance) may be useful across many different - implementations of that same service. - -The framework will register extensions in this category such that an -aggregator will depend on all of its providers, and decorators will -depend upon on one another in a chain. The result of this compositing step -(the last decorator, if any; otherwise the aggregator, if any; -otherwise a single provider) will be exposed as a single service that -other extensions can acquire through dependency injection. Because all -components of the same type of service expose the same interface, users -of that service do not need to be aware that they are talking to an -aggregator or a provider, for instance. diff --git a/docs/src/architecture/index.md b/docs/src/architecture/index.md deleted file mode 100644 index 6f2fbcfcef..0000000000 --- a/docs/src/architecture/index.md +++ /dev/null @@ -1,76 +0,0 @@ -# Introduction - -The purpose of this document is to familiarize developers with the -overall architecture of Open MCT. - -The target audience includes: - -* _Platform maintainers_: Individuals involved in developing, - extending, and maintaining capabilities of the platform. -* _Integration developers_: Individuals tasked with integrated - Open MCT into a larger system, who need to understand - its inner workings sufficiently to complete this integration. - -As the focus of this document is on architecture, whenever possible -implementation details (such as relevant API or JSON syntax) have been -omitted. These details may be found in the developer guide. - -# Overview - -Open MCT is client software: It runs in a web browser and -provides a user interface, while communicating with various -server-side resources through browser APIs. - -```nomnoml -#direction: right -[Client|[Browser|[Open MCT]->[Browser APIs]]] -[Server|[Web services]] -[Client]<->[Server] -``` - -While Open MCT can be configured to run as a standalone client, -this is rarely very useful. Instead, it is intended to be used as a -display and interaction layer for information obtained from a -variety of back-end services. Doing so requires authoring or utilizing -adapter plugins which allow Open MCT to interact with these services. - -Typically, the pattern here is to provide a known interface that -Open MCT can utilize, and implement it such that it interacts with -whatever back-end provides the relevant information. -Examples of back-ends that can be utilized in this fashion include -databases for the persistence of user-created objects, or sources of -telemetry data. - -## Software Architecture - -The simplest overview of Open MCT is to look at it as a "layered" -architecture, where each layer more clearly specifies the behavior -of the software. - -```nomnoml -#direction: down -[Open MCT| - [Platform]<->[Application] - [Framework]->[Application] - [Framework]->[Platform] -] -``` - -These layers are: - -* [_Framework_](framework.md): The framework layer is responsible for - managing the interactions between application components. It has no - application-specific knowledge; at this layer, we have only - established an abstraction by which different software components - may communicate and/or interact. -* [_Platform_](platform.md): The platform layer defines the general look, - feel, and behavior of Open MCT. This includes user-facing components like - Browse mode and Edit mode, as well as underlying elements of the - information model and the general service infrastructure. -* _Application_: The application layer defines specific features of - an application built on Open MCT. This includes adapters to - specific back-ends, new types of things for users to create, and - new ways of visualizing objects within the system. This layer - typically consists of a mix of custom plug-ins to Open MCT, - as well as optional features (such as Plot view) included alongside - the platform. diff --git a/docs/src/architecture/platform.md b/docs/src/architecture/platform.md deleted file mode 100644 index 48ceebb9ef..0000000000 --- a/docs/src/architecture/platform.md +++ /dev/null @@ -1,726 +0,0 @@ -# Overview - -The Open MCT platform utilizes the [framework layer](framework.md) -to provide an extensible baseline for applications which includes: - -* A common user interface (and user interface paradigm) for dealing with - domain objects of various sorts. -* A variety of extension points for introducing new functionality - of various kinds within the context of the common user interface. -* A service infrastructure to support building additional components. - -## Platform Architecture - -While the framework provides a more general architectural paradigm for -building application, the platform adds more specificity by defining -additional extension types and allowing for integration with back end -components. - -The run-time architecture of an Open MCT application can be categorized -into certain high-level tiers: - -```nomnoml -[DOM]->[ AngularJS] -[AngularJS]->[Presentation Layer] -[Presentation Layer]->[Information Model] -[Presentation Layer]->[Service Infrastructure] -[Information Model]->[Service Infrastructure] -[Service Infrastructure]->[ Browser APIs] -[Browser APIs]->[Back-end] -``` - -Applications built using Open MCT may add or configure functionality -in __any of these tiers__. - -* _DOM_: The rendered HTML document, composed from HTML templates which - have been processed by AngularJS and will be updated by AngularJS - to reflect changes from the presentation layer. User interactions - are initiated from here and invoke behavior in the presentation layer. HTML  - templates are written in Angular’s template syntax; see the [Angular documentation on templates](https://docs.angularjs.org/guide/templates)​.  - These describe the page as actually seen by the user. Conceptually,  - stylesheets (controlling the look-and-feel of the rendered templates) belong  - in this grouping as well.  -* [_Presentation layer_](#presentation-layer): The presentation layer - is responsible for updating (and providing information to update) - the displayed state of the application. The presentation layer consists - primarily of _controllers_ and _directives_. The presentation layer is - concerned with inspecting the information model and preparing it for - display. -* [_Information model_](#information-model): ​Provides a common (within Open MCT  - Web) set of interfaces for dealing with “things” ­ domain objects ­ within the  - system. User-facing concerns in a Open MCT Web application are expressed as  - domain objects; examples include folders (used to organize other domain  - objects), layouts (used to build displays), or telemetry points (used as  - handles for streams of remote measurements.) These domain objects expose a  - common set of interfaces to allow reusable user interfaces to be built in the  - presentation and template tiers; the specifics of these behaviors are then  - mapped to interactions with underlying services.  -* [_Service infrastructure_](#service-infrastructure): The service - infrastructure is responsible for providing the underlying general - functionality needed to support the information model. This includes - exposing underlying sets of extensions and mediating with the - back-end. -* _Back-end_: The back-end is out of the scope of Open MCT, except - for the interfaces which are utilized by adapters participating in the - service infrastructure. Includes the underlying persistence stores, telemetry  - streams, and so forth which the Open MCT Web client is being used to interact  - with. - -## Application Start-up - -Once the -[application has been initialized](Framework.md#application-initialization) -Open MCT primarily operates in an event-driven paradigm; various -events (mouse clicks, timers firing, receiving responses to XHRs) trigger -the invocation of functions, typically in the presentation layer for -user actions or in the service infrastructure for server responses. - -The "main point of entry" into an initialized Open MCT application -is effectively the -[route](https://docs.angularjs.org/api/ngRoute/service/$route#example) -which is associated with the URL used to access Open MCT (or a -default route.) This route will be associated with a template which -will be displayed; this template will include references to directives -and controllers which will be interpreted by Angular and used to -initialize the state of the display in a manner which is backed by -both the information model and the service infrastructure. - -```nomnoml -[ Start]->[ page load] -[page load]->[ route selection] -[route selection]->[ compile, display template] -[compile, display template]->[Template] -[Template]->[ use Controllers] -[Template]->[ use Directives] -[use Controllers]->[Controllers] -[use Directives]->[Directives] -[Controllers]->[ consult information model] -[consult information model]->[ expose data] -[expose data]->[Angular] -[Angular]->[ update display] -[Directives]->[ add event listeners] -[Directives]->[ update display] -[add event listeners]->[ End] -[update display]->[ End] -``` - - -# Presentation Layer - -The presentation layer of Open MCT is responsible for providing -information to display within templates, and for handling interactions -which are initiated from templated DOM elements. AngularJS acts as -an intermediary between the web page as the user sees it, and the -presentation layer implemented as Open MCT extensions. - -```nomnoml -[Presentation Layer| - [Angular built-ins| - [routes] - [controllers] - [directives] - [templates] - ] - [Domain object representation| - [views] - [representations] - [representers] - [gestures] - ] -] -``` - -## Angular built-ins - -Several extension categories in the presentation layer map directly -to primitives from AngularJS: - -* [_Controllers_](https://docs.angularjs.org/guide/controller) provide - data to templates, and expose functionality that can be called from - templates. -* [_Directives_](https://docs.angularjs.org/guide/directive) effectively - extend HTML to provide custom behavior associated with specific - attributes and tags. -* [_Routes_](https://docs.angularjs.org/api/ngRoute/service/$route#example) - are used to associate specific URLs (including the fragment identifier) - with specific application states. (In Open MCT, these are used to - describe the mode of usage - e.g. browse or edit - as well as to - identify the object being used.) -* [_Templates_](https://docs.angularjs.org/guide/templates) are partial - HTML documents that will be rendered and kept up-to-date by AngularJS. - Open MCT introduces a custom `mct-include` directive which acts - as a wrapper around `ng-include` to allow templates to be referred - to by symbolic names. - -## Domain object representation - -The remaining extension categories in the presentation layer are specific -to displaying domain objects. - -* _Representations_ are templates that will be used to display - domain objects in specific ways (e.g. "as a tree node.") -* _Views_ are representations which are exposed to the user as options - for displaying domain objects. -* _Representers_ are extensions which modify or augment the process - of representing domain objects generally (e.g. by attaching - gestures to them.) -* _Gestures_ provide associations between specific user actions - (expressed as DOM events) and resulting behavior upon domain objects - (typically expressed as members of the `actions` extension category) - that can be reused across domain objects. For instance, `drag` and - `drop` are both gestures associated with using drag-and-drop to - modify the composition of domain objects by interacting with their - representations. - -# Information Model - -```nomnoml -#direction: right -[Information Model| - [DomainObject| - getId() : string - getModel() : object - getCapability(key : string) : Capability - hasCapability(key : string) : boolean - useCapability(key : string, args...) : * - ] - [DomainObject] 1 +- 1 [Model] - [DomainObject] 1 o- * [Capability] -] -``` - -Domain objects are the most fundamental component of Open MCT's -information model. A domain object is some distinct thing relevant to a -user's work flow, such as a telemetry channel, display, or similar. -Open MCT is a tool for viewing, browsing, manipulating, and otherwise -interacting with a graph of domain objects. - -A domain object should be conceived of as the union of the following: - -* _Identifier_: A machine-readable string that uniquely identifies the - domain object within this application instance. -* _Model_: The persistent state of the domain object. A domain object's - model is a JavaScript object that can be losslessly converted to JSON. -* _Capabilities_: Dynamic behavior associated with the domain object. - Capabilities are JavaScript objects which provide additional methods - for interacting with the domain objects which expose those capabilities. - Not all domain objects expose all capabilities. The interface exposed - by any given capability will depend on its type (as identified - by the `key` argument.) For instance, a `persistence` capability - has a different interface from a `telemetry` capability. Using - capabilities requires some prior knowledge of their interface. - -## Capabilities and Services - -```nomnoml -#direction: right -[DomainObject]o-[FooCapability] -[FooCapability]o-[FooService] -[FooService]o-[foos] -``` - -At run-time, the user is primarily concerned with interacting with -domain objects. These interactions are ultimately supported via back-end -services, but to allow customization per-object, these are often mediated -by capabilities. - -A common pattern that emerges in the Open MCT Platform is as follows: - -* A `DomainObject` has some particular behavior that will be supported - by a service. -* A `Capability` of that domain object will define that behavior, - _for that domain object_, supported by a service. -* A `Service` utilized by that capability will perform the actual behavior. -* An extension category will be utilized by that capability to determine - the set of possible behaviors. - -Concrete examples of capabilities which follow this pattern -(or a subset of this pattern) include: - -```nomnoml -#direction: right -[DomainObject]1 o- *[Capability] -[Capability]<:--[TypeCapability] -[Capability]<:--[ActionCapability] -[Capability]<:--[PersistenceCapability] -[Capability]<:--[TelemetryCapability] -[TypeCapability]o-[TypeService] -[TypeService]o-[types] -[ActionCapability]o-[ActionService] -[ActionService]o-[actions] -[PersistenceCapability]o-[PersistenceService] -[TelemetryCapability]o-[TelemetryService] -``` - -# Service Infrastructure - -Most services exposed by the Open MCT platform follow the -[composite services](Framework.md#composite-services) to permit -a higher degree of flexibility in how a service can be modified -or customized for specific applications. - -To simplify usage for plugin developers, the platform also usually -includes a provider implementation for these service type that consumes -some extension category. For instance, an `ActionService` provider is -included which depends upon extension category `actions`, and exposes -all actions declared as such to the system. As such, plugin developers -can simply implement the new actions they wish to be made available without -worrying about the details of composite services or implementing a new -`ActionService` provider; however, the ability to implement a new provider -remains useful when the expressive power of individual extensions is -insufficient. - -```nomnoml -[ Service Infrastructure | - [ObjectService]->[ModelService] - [ModelService]->[PersistenceService] - [ObjectService]->[CapabilityService] - [CapabilityService]->[capabilities] - [capabilities]->[TelemetryService] - [capabilities]->[PersistenceService] - [capabilities]->[TypeService] - [capabilities]->[ActionService] - [capabilities]->[ViewService] - [PersistenceService]->[ Document store] - [TelemetryService]->[ Telemetry source] - [ActionService]->[actions] - [ActionService]->[PolicyService] - [ViewService]->[PolicyService] - [ViewService]->[views] - [PolicyService]->[policies] - [TypeService]->[types] -] -``` - -A short summary of the roles of these services: - -* _[ObjectService](#object-service)_: Allows retrieval of domain objects by - their identifiers; in practice, often the main point of entry into the - [information model](#information-model). -* _[ModelService](#model-service)_: Provides domain object models, retrieved - by their identifier. -* _[CapabilityService](#capability-service)_: Provides capabilities, as they - apply to specific domain objects (as judged from their model.) -* _[TelemetryService](#telemetry-service)_: Provides access to historical - and real-time telemetry data. -* _[PersistenceService](#persistence-service)_: Provides the ability to - store and retrieve documents (such as domain object models.) -* _[ActionService](#action-service)_: Provides distinct user actions that - can take place within the system (typically, upon or using domain objects.) -* _[ViewService](#view-service)_: Provides views for domain objects. A view - is a user-selectable representation of a domain object (in practice, an - HTML template.) -* _[PolicyService](#policy-service)_: Handles decisions about which - behavior are allowed within certain specific contexts. -* _[TypeService](#type-service)_: Provides information to distinguish - different types of domain objects from one another within the system. - -## Object Service - -```nomnoml -#direction: right -[ ObjectService| - getObjects(ids : Array.) : Promise.> -] -[DomainObjectProvider]--:>[ObjectService] -[DomainObjectProvider]o-[ModelService] -[DomainObjectProvider]o-[CapabilityService] -``` - -As domain objects are central to Open MCT's information model, -acquiring domain objects is equally important. - -```nomnoml -#direction: right -[ Start]->[ Look up models] -[ Look up models]->[ Look up capabilities] -[ Look up capabilities]->[ Instantiate DomainObject] -[ Instantiate DomainObject]->[ End] -``` - -Open MCT includes an implementation of an `ObjectService` which -satisfies this capability by: - -* Consulting the [Model Service](#model-service) to acquire domain object - models by identifier. -* Passing these models to a [Capability Service](#capability-service) to - determine which capabilities are applicable. -* Combining these results together as [DomainObject](#information-model) - instances. - -## Model Service - -```nomnoml -#direction: down -[ ModelService| - getModels(ids : Array.) : Promise.> -] -[StaticModelProvider]--:>[ModelService] -[RootModelProvider]--:>[ModelService] -[PersistedModelProvider]--:>[ModelService] -[ModelAggregator]--:>[ModelService] -[CachingModelDecorator]--:>[ModelService] -[MissingModelDecorator]--:>[ModelService] - -[MissingModelDecorator]o-[CachingModelDecorator] -[CachingModelDecorator]o-[ModelAggregator] -[ModelAggregator]o-[StaticModelProvider] -[ModelAggregator]o-[RootModelProvider] -[ModelAggregator]o-[PersistedModelProvider] - -[PersistedModelProvider]o-[PersistenceService] -[RootModelProvider]o-[roots] -[StaticModelProvider]o-[models] -``` - -The platform's model service is responsible for providing domain object -models (effectively, JSON documents describing the persistent state -associated with domain objects.) These are retrieved by identifier. - -The platform includes multiple components of this variety: - -* `PersistedModelProvider` looks up domain object models from - a persistence store (the [`PersistenceService`](#persistence-service)); - this is how user-created and user-modified - domain object models are retrieved. -* `RootModelProvider` provides domain object models that have been - declared via the `roots` extension category. These will appear at the - top level of the tree hierarchy in the user interface. -* `StaticModelProvider` provides domain object models that have been - declared via the `models` extension category. This is useful for - allowing plugins to expose new domain objects declaratively. -* `ModelAggregator` merges together the results from multiple providers. - If multiple providers return models for the same domain object, - the most recently modified version (as determined by the `modified` - property of the model) is chosen. -* `CachingModelDecorator` caches model instances in memory. This - ensures that only a single instance of a domain object model is - present at any given time within the application, and prevent - redundant retrievals. -* `MissingModelDecorator` adds in placeholders when no providers - have returned domain object models for a specific identifier. This - allows the user to easily see that something was expected to be - present, but wasn't. - -## Capability Service - -```nomnoml -#direction: down -[ CapabilityService| - getCapabilities(model : object) : object. -] -[CoreCapabilityProvider]--:>[CapabilityService] -[QueuingPersistenceCapabilityDecorator]--:>[CapabilityService] - -[CoreCapabilityProvider]o-[capabilities] -[QueuingPersistenceCapabilityDecorator]o-[CoreCapabilityProvider] -``` - -The capability service is responsible for determining which capabilities -are applicable for a given domain object, based on its model. Primarily, -this is handled by the `CoreCapabilityProvider`, which examines -capabilities exposed via the `capabilities` extension category. - -Additionally, `platform/persistence/queue` decorates the persistence -capability specifically to batch persistence attempts among multiple -objects (this allows failures to be recognized and handled in groups.) - -## Telemetry Service - -```nomnoml -[ TelemetryService| - requestData(requests : Array.) : Promise. - subscribe(requests : Array.) : Function -]<--:[TelemetryAggregator] -``` - -The telemetry service is responsible for acquiring telemetry data. - -Notably, the platform does not include any providers for -`TelemetryService`; applications built on Open MCT will need to -implement a provider for this service if they wish to expose telemetry -data. This is usually the most important step for integrating Open MCT -into an existing telemetry system. - -Requests for telemetry data are usually initiated in the -[presentation layer](#presentation-layer) by some `Controller` referenced -from a view. The `telemetryHandler` service is most commonly used (although -one could also use an object's `telemetry` capability directly) as this -handles capability delegation, by which a domain object such as a Telemetry -Panel can declare that its `telemetry` capability should be handled by the -objects it contains. Ultimately, the request for historical data and the -new subscriptions will reach the `TelemetryService`, and, by way of the -provider(s) which are present for that `TelemetryService`, will pass the -same requests to the back-end. - -```nomnoml -[ Start]->[Controller] -[Controller]->[ declares object of interest] -[declares object of interest]->[TelemetryHandler] -[TelemetryHandler]->[ requests telemetry from capabilities] -[TelemetryHandler]->[ subscribes to telemetry using capabilities] -[requests telemetry from capabilities]->[TelemetryCapability] -[subscribes to telemetry using capabilities]->[TelemetryCapability] -[TelemetryCapability]->[ requests telemetry] -[TelemetryCapability]->[ subscribes to telemetry] -[requests telemetry]->[TelemetryService] -[subscribes to telemetry]->[TelemetryService] -[TelemetryService]->[ issues request] -[TelemetryService]->[ updates subscriptions] -[TelemetryService]->[ listens for real-time data] -[issues request]->[ Telemetry Back-end] -[updates subscriptions]->[Telemetry Back-end] -[listens for real-time data]->[Telemetry Back-end] -[Telemetry Back-end]->[ End] -``` - -The back-end, in turn, is expected to provide whatever historical -telemetry is available to satisfy the request that has been issue. - -```nomnoml -[ Start]->[ Telemetry Back-end] -[Telemetry Back-end]->[ transmits historical telemetry] -[transmits historical telemetry]->[TelemetryService] -[TelemetryService]->[ packages telemetry, fulfills requests] -[packages telemetry, fulfills requests]->[TelemetryCapability] -[TelemetryCapability]->[ unpacks telemetry per-object, fulfills request] -[unpacks telemetry per-object, fulfills request]->[TelemetryHandler] -[TelemetryHandler]->[ exposes data] -[TelemetryHandler]->[ notifies controller] -[exposes data]->[Controller] -[notifies controller]->[Controller] -[Controller]->[ prepares data for template] -[prepares data for template]->[Template] -[Template]->[ displays data] -[displays data]->[ End] -``` - -One peculiarity of this approach is that we package many responses -together at once in the `TelemetryService`, then unpack these in the -`TelemetryCapability`, then repackage these in the `TelemetryHandler`. -The rationale for this is as follows: - -* In the `TelemetryService`, we want to have the ability to combine - multiple requests into one call to the back-end, as many back-ends - will support this. It follows that we give the response as a single - object, packages in a manner that allows responses to individual - requests to be easily identified. -* In the `TelemetryCapability`, we want to provide telemetry for a - _single object_, so the telemetry data gets unpacked. This allows - for the unpacking of data to be handled in a single place, and - also permits a flexible substitution method; domain objects may have - implementations of the `telemetry` capability that do not use the - `TelemetryService` at all, while still maintaining compatibility - with any presentation layer code written to utilize this capability. - (This is true of capabilities generally.) -* In the `TelemetryHandler`, we want to group multiple responses back - together again to make it easy for the presentation layer to consume. - In this case, the grouping is different from what may have occurred - in the `TelemetryService`; this grouping is based on what is expected - to be useful _in a specific view_. The `TelemetryService` - may be receiving requests from multiple views. - -```nomnoml -[ Start]->[ Telemetry Back-end] -[Telemetry Back-end]->[ notifies client of new data] -[notifies client of new data]->[TelemetryService] -[TelemetryService]->[ relevant subscribers?] -[relevant subscribers?] yes ->[ notify subscribers] -[relevant subscribers?] no ->[ ignore] -[ignore]->[ Ignored] -[notify subscribers]->[TelemetryCapability] -[TelemetryCapability]->[ notify listener] -[notify listener]->[TelemetryHandler] -[TelemetryHandler]->[ exposes data] -[TelemetryHandler]->[ notifies controller] -[exposes data]->[Controller] -[notifies controller]->[Controller] -[Controller]->[ prepares data for template] -[prepares data for template]->[Template] -[Template]->[ displays data] -[displays data]->[ End] -``` - -The flow of real-time data is similar, and is handled by a sequence -of callbacks between the presentation layer component which is -interested in data and the telemetry service. Providers in the -telemetry service listen to the back-end for new data (via whatever -mechanism their specific back-end supports), package this data in -the same manner as historical data, and pass that to the callbacks -which are associated with relevant requests. - -## Persistence Service - -```nomnoml -#direction: right -[ PersistenceService| - listSpaces() : Promise.> - listObjects() : Promise.> - createObject(space : string, key : string, document : object) : Promise. - readObject(space : string, key : string, document : object) : Promise. - updateObject(space : string, key : string, document : object) : Promise. - deleteObject(space : string, key : string, document : object) : Promise. -] - -[ElasticPersistenceProvider]--:>[PersistenceService] -[ElasticPersistenceProvider]->[ ElasticSearch] - -[CouchPersistenceProvider]--:>[PersistenceService] -[CouchPersistenceProvider]->[ CouchDB] -``` - -Closely related to the notion of domain objects models is their -persistence. The `PersistenceService` allows these to be saved -and loaded. (Currently, this capability is only used for domain -object models, but the interface has been designed without this idea -in mind; other kinds of documents could be saved and loaded in the -same manner.) - -There is no single definitive implementation of a `PersistenceService` in -the platform. Optional adapters are provided to store and load documents -from CouchDB and ElasticSearch, respectively; plugin authors may also -write additional adapters to utilize different back end technologies. - -## Action Service - -```nomnoml -[ActionService| - getActions(context : ActionContext) : Array. -] -[ActionProvider]--:>[ActionService] -[CreateActionProvider]--:>[ActionService] -[ActionAggregator]--:>[ActionService] -[LoggingActionDecorator]--:>[ActionService] -[PolicyActionDecorator]--:>[ActionService] - -[LoggingActionDecorator]o-[PolicyActionDecorator] -[PolicyActionDecorator]o-[ActionAggregator] -[ActionAggregator]o-[ActionProvider] -[ActionAggregator]o-[CreateActionProvider] - -[ActionProvider]o-[actions] -[CreateActionProvider]o-[TypeService] -[PolicyActionDecorator]o-[PolicyService] -``` - -Actions are discrete tasks or behaviors that can be initiated by a user -upon or using a domain object. Actions may appear as menu items or -buttons in the user interface, or may be triggered by certain gestures. - -Responsibilities of platform components of the action service are as -follows: - -* `ActionProvider` exposes actions registered via extension category - `actions`, supporting simple addition of new actions. Actions are - filtered down to match action contexts based on criteria defined as - part of an action's extension definition. -* `CreateActionProvider` provides the various Create actions which - populate the Create menu. These are driven by the available types, - so do not map easily to extension category `actions`; instead, these - are generated after looking up which actions are available from the - [`TypeService`](#type-service). -* `ActionAggregator` merges together actions from multiple providers. -* `PolicyActionDecorator` enforces the `action` policy category by - filtering out actions which violate this policy, as determined by - consulting the [`PolicyService`](#policy-service). -* `LoggingActionDecorator` wraps exposed actions and writes to the - console when they are performed. - -## View Service - -```nomnoml -[ViewService| - getViews(domainObject : DomainObject) : Array. -] -[ViewProvider]--:>[ViewService] -[PolicyViewDecorator]--:>[ViewService] - -[ViewProvider]o-[views] -[PolicyViewDecorator]o-[ViewProvider] -``` - -The view service provides views that are relevant to a specified domain -object. A "view" is a user-selectable visualization of a domain object. - -The responsibilities of components of the view service are as follows: - -* `ViewProvider` exposes views registered via extension category - `views`, supporting simple addition of new views. Views are - filtered down to match domain objects based on criteria defined as - part of a view's extension definition. -* `PolicyViewDecorator` enforces the `view` policy category by - filtering out views which violate this policy, as determined by - consulting the [`PolicyService`](#policy-service). - -## Policy Service - -```nomnoml -[PolicyService| - allow(category : string, candidate : object, context : object, callback? : Function) : boolean -] -[PolicyProvider]--:>[PolicyService] -[PolicyProvider]o-[policies] -``` - -The policy service provides a general-purpose extensible decision-making -mechanism; plugins can add new extensions of category `policies` to -modify decisions of a known category. - -Often, the policy service is referenced from a decorator for another -service, to filter down the results of using that service based on some -appropriate policy category. - -The policy provider works by looking up all registered policy extensions -which are relevant to a particular _category_, then consulting each in -order to see if they allow a particular _candidate_ in a particular -_context_; the types for the `candidate` and `context` arguments will -vary depending on the `category`. Any one policy may disallow the -decision as a whole. - - -```nomnoml -[ Start]->[ is something allowed?] -[is something allowed?]->[PolicyService] -[PolicyService]->[ look up relevant policies by category] -[look up relevant policies by category]->[ consult policy #1] -[consult policy #1]->[Policy #1] -[Policy #1]->[ policy #1 allows?] -[policy #1 allows?] no ->[ decision disallowed] -[policy #1 allows?] yes ->[ consult policy #2] -[consult policy #2]->[Policy #2] -[Policy #2]->[ policy #2 allows?] -[policy #2 allows?] no ->[ decision disallowed] -[policy #2 allows?] yes ->[ consult policy #3] -[consult policy #3]->[ ...] -[...]->[ consult policy #n] -[consult policy #n]->[Policy #n] -[Policy #n]->[ policy #n allows?] -[policy #n allows?] no ->[ decision disallowed] -[policy #n allows?] yes ->[ decision allowed] -[decision disallowed]->[ Disallowed] -[decision allowed]->[ Allowed] -``` - -The policy decision is effectively an "and" operation over the individual -policy decisions: That is, all policies must agree to allow a particular -policy decision, and the first policy to disallow a decision will cause -the entire decision to be disallowed. As a consequence of this, policies -should generally be written with a default behavior of "allow", and -should only disallow the specific circumstances they are intended to -disallow. - -## Type Service - -```nomnoml -[TypeService| - listTypes() : Array. - getType(key : string) : Type -] -[TypeProvider]--:>[TypeService] -[TypeProvider]o-[types] -``` - -The type service provides metadata about the different types of domain -objects that exist within an Open MCT application. The platform -implementation reads these types in from extension category `types` -and wraps them in a JavaScript interface. diff --git a/docs/src/design/index.md b/docs/src/design/index.md deleted file mode 100644 index 7b4c3e4ebf..0000000000 --- a/docs/src/design/index.md +++ /dev/null @@ -1,3 +0,0 @@ -Design proposals: - -* [API Redesign](proposals/APIRedesign.md) \ No newline at end of file diff --git a/docs/src/design/planning/APIRefactor.md b/docs/src/design/planning/APIRefactor.md deleted file mode 100644 index ec98a115f4..0000000000 --- a/docs/src/design/planning/APIRefactor.md +++ /dev/null @@ -1,338 +0,0 @@ -# API Refactoring - -This document summarizes a path toward implementing API changes -from the [API Redesign](../proposals/APIRedesign.md) for Open MCT -v1.0.0. - -# Goals - -These plans are intended to minimize: - -* Waste; avoid allocating effort to temporary changes. -* Downtime; avoid making changes in large increments that blocks - delivery of new features for substantial periods of time. -* Risk; ensure that changes can be validated quickly, avoid putting - large effort into changes that have not been validated. - -# Plan - -```nomnoml -#comment: This diagram is in nomnoml syntax and should be rendered. -#comment: See https://github.com/nasa/openmctweb/issues/264#issuecomment-167166471 - - -[ Start]->[ Imperative bundle registration] - -[ Imperative bundle registration]->[ Build and packaging] -[ Imperative bundle registration]->[ Refactor API] - -[ Build and packaging | - [ Start]->[ Incorporate a build step] - [ Incorporate a build step | - [ Start]->[ Choose package manager] - [ Start]->[ Choose build system] - [ Choose build system]<->[ Choose package manager] - [ Choose package manager]->[ Implement] - [ Choose build system]->[ Implement] - [ Implement]->[ End] - ]->[ Separate repositories] - [ Separate repositories]->[ End] -]->[ Release candidacy] - - -[ Start]->[ Design registration API] - -[ Design registration API | - [ Start]->[ Decide on role of Angular] - [ Decide on role of Angular]->[ Design API] - [ Design API]->[ Passes review?] - [ Passes review?] no ->[ Design API] - [ Passes review?]-> yes [ End] -]->[ Refactor API] - -[ Refactor API | - [ Start]->[ Imperative extension registration] - [ Imperative extension registration]->[ Refactor individual extensions] - - [ Refactor individual extensions | - [ Start]->[ Prioritize] - [ Prioritize]->[ Sufficient value added?] - [ Sufficient value added?] no ->[ End] - [ Sufficient value added?] yes ->[ Design] - [ Design]->[ Passes review?] - [ Passes review?] no ->[ Design] - [ Passes review?]-> yes [ Implement] - [ Implement]->[ End] - ]->[ Remove legacy bundle support] - - [ Remove legacy bundle support]->[ End] -]->[ Release candidacy] - -[ Release candidacy | - [ Start]->[ Verify | - [ Start]->[ API well-documented?] - [ Start]->[ API well-tested?] - [ API well-documented?]-> no [ Write documentation] - [ API well-documented?] yes ->[ End] - [ Write documentation]->[ API well-documented?] - [ API well-tested?]-> no [ Write test cases] - [ API well-tested?]-> yes [ End] - [ Write test cases]->[ API well-tested?] - ] - [ Start]->[ Validate | - [ Start]->[ Passes review?] - [ Start]->[ Use internally] - [ Use internally]->[ Proves useful?] - [ Passes review?]-> no [ Address feedback] - [ Address feedback]->[ Passes review?] - [ Passes review?] yes -> [ End] - [ Proves useful?] yes -> [ End] - [ Proves useful?] no -> [ Fix problems] - [ Fix problems]->[ Use internally] - ] - [ Validate]->[ End] - [ Verify]->[ End] -]->[ Release] - -[ Release]->[ End] -``` - -## Step 1. Imperative bundle registration - -Register whole bundles imperatively, using their current format. - -For example, in each bundle add a `bundle.js` file: - -```js -define([ - 'mctRegistry', - 'json!bundle.json' -], function (mctRegistry, bundle) { - mctRegistry.install(bundle, "path/to/bundle"); -}); -``` - -Where `mctRegistry.install` is placeholder API that wires into the -existing bundle registration mechanisms. The main point of entry -would need to be adapted to clearly depend on these bundles -(in the require sense of a dependency), and the framework layer -would need to implement and integrate with this transitional -API. - -Benefits: - -* Achieves an API Redesign goal with minimal immediate effort. -* Conversion to an imperative syntax may be trivially automated. -* Minimal change; reuse existing bundle definitions, primarily. -* Allows early validation of switch to imperative; unforeseen - consequences of the change may be detected at this point. -* Allows implementation effort to progress in parallel with decisions - about API changes, including fundamental ones such as the role of - Angular. May act in some sense as a prototype to inform those - decisions. -* Creates a location (framework layer) where subsequent changes to - the manner in which extensions are registered may be centralized. - When there is a one-to-one correspondence between the existing - form of an extension and its post-refactor form, adapters can be - written here to defer the task of making changes ubiquitously - throughout bundles, allowing for earlier validation and - verification of those changes, and avoiding ubiquitous changes - which might require us to go dark. (Mitigates - ["greenfield paradox"](http://stepaheadsoftware.blogspot.com/2012/09/greenfield-or-refactor-legacy-code-base.html); - want to add value with new API but don't want to discard value - of tested/proven legacy codebase.) - -Detriments: - -* Requires transitional API to be implemented/supported; this is - waste. May mitigate this by time-bounding the effort put into - this step to ensure that waste is minimal. - -Note that API changes at this point do not meaningfully reflect -the desired 1.0.0 API, so no API reviews are necessary. - -## Step 2. Incorporate a build step - -After the previous step is completed, there should be a -straightforward dependency graph among AMD modules, and an -imperative (albeit transitional) API allowing for other plugins -to register themselves. This should allow for a build step to -be included in a straightforward fashion. - -Some goals for this build step: - -* Compile (and, preferably, optimize/minify) Open MCT - sources into a single `.js` file. - * It is desirable to do the same for HTML sources, but - may wish to defer this until a subsequent refactoring - step if appropriate. -* Provide non-code assets in a format that can be reused by - derivative projects in a straightforward fashion. - -Should also consider which dependency/packaging manager should -be used by dependent projects to obtain Open MCT. Approaches -include: - -1. Plain `npm`. Dependents then declare their dependency with - `npm` and utilize built sources and assets in a documented - fashion. (Note that there are - [documented challenges](http://blog.npmjs.org/post/101775448305/npm-and-front-end-packaging) - in using `npm` in this fashion.) -2. Build with `npm`, but recommend dependents install using - `bower`, as this is intended for front-end development. This may - require checking in built products, however, which - we wish to avoid (this could be solved by maintaining - a separate repository for built products.) - -In all cases, there is a related question of which build system -to use for asset generation/management and compilation/minification/etc. - -1. [`webpack`](https://webpack.github.io/) - is well-suited in principle, as it is specifically - designed for modules with non-JS dependencies. However, - there may be limitations and/or undesired behavior here - (for instance, CSS dependencies get in-lined as style tags, - removing our ability to control ordering) so it may -2. `gulp` or `grunt`. Commonplace, but both still require - non-trivial coding and/or configuration in order to produce - appropriate build artifacts. -3. [Just `npm`](http://blog.keithcirkel.co.uk/how-to-use-npm-as-a-build-tool/). - Reduces the amount of tooling being used, but may introduce - some complexity (e.g. custom scripts) to the build process, - and may reduce portability. - -## Step 3. Separate repositories - -Refactor existing applications built on Open MCT such that they -are no longer forks, but instead separate projects with a dependency -on the built artifacts from Step 2. - -Note that this is achievable already using `bower` (see `warp-bower` -branch at http://developer.nasa.gov/mct/warp for an example.) -However, changes involved in switching to an imperative API and -introducing a build process may change (and should simplify) the -approach used to utilize Open MCT as a dependency, so these -changes should be introduced first. - -## Step 4. Design registration API - -Design the registration API that will replace declarative extension -categories and extensions (including Angular built-ins and composite -services.) - -This may occur in parallel with implementation steps. - -It will be necessary -to have a decision about the role of Angular at this point; are extensions -registered via provider configuration (Angular), or directly in some -exposed registry? - -Success criteria here should be based on peer review. Scope of peer -review should be based on perceived risk/uncertainty surrounding -proposed changes, to avoid waste; may wish to limit this review to -the internal team. (The extent to which external -feedback is available is limited, but there is an inherent timeliness -to external review; need to balance this.) - -Benefits: - -* Solves the "general case" early, allowing for early validation. - -Note that in specific cases, it may be desirable to refactor some -current "extension category" in a manner that will not appear as -registries, _or_ to locate these in different -namespaces, _or_ to remove/replace certain categories entirely. -This work is deferred intentionally to allow for a solution of the -general case. - -## Step 5. Imperative extension registration - -Register individual extensions imperatively, implementing API changes -from the previous step. At this stage, _usage_ of the API may be confined -to a transitional adapter in the framework layer; bundles may continue -to utilize the transitional API for registering extensions in the -legacy format. - -An important, ongoing sub-task here will be to discover and define dependencies -among bundles. Composite services and extension categories are presently -"implicit"; after the API redesign, these will become "explicit", insofar -as some specific component will be responsible for creating any registries. -As such, "bundles" which _use_ specific registries will need to have an -enforceable dependency (e.g. require) upon those "bundles" which -_declare_ those registries. - -## Step 6. Refactor individual extensions - -Refactor individual extension categories and/or services that have -been identified as needing changes. This includes, but is not -necessarily limited to: - -* Views/Representations/Templates (refactored into "components.") -* Capabilities (refactored into "roles", potentially.) -* Telemetry (from `TelemetrySeries` to `TelemetryService`.) - -Changes should be made one category at a time (either serially -or separately in parallel) and should involve a tight cycle of: - -1. Prioritization/reprioritization; highest-value API improvements - should be done first. -2. Design. -3. Review. Refactoring individual extensions will require significant - effort (likely the most significant effort in the process) so changes - should be validated early to minimize risk/waste. -4. Implementation. These changes will not have a one-to-one relationship - with existing extensions, so changes cannot be centralized; usages - will need to be updated across all "bundles" instead of centralized - in a legacy adapter. If changes are of sufficient complexity, some - planning should be done to spread out the changes incrementally. - -By necessity, these changes may break functionality in applications -built using Open MCT. On a case-by-case basis, should consider -providing temporary "legacy support" to allow downstream updates -to occur as a separate task; the relevant trade here is between -waste/effort required to maintain legacy support, versus the -downtime which may be introduced by making these changes simultaneously -across several repositories. - - -## Step 7. Remove legacy bundle support - -Update bundles to remove any usages of legacy support for bundles -(including that used by dependent projects.) Then, remove legacy -support from Open MCT. - -## Step 8. Release candidacy - -Once API changes are complete, Open MCT should enter a release -candidacy cycle. Important things to look at here: - -* Are changes really complete? - * Are they sufficiently documented? - * Are they sufficiently tested? -* Are changes really sufficient? - * Do reviewers think they are usable? - * Does the development team find them useful in practice? This - will require calendar time to ascertain; should allocate time - for this, particularly in alignment with the sprint/release - cycle. - * Has learning curve been measurably decreased? Comparing a to-do - list tutorial to [other examples(http://todomvc.com/) could - provide an empirical basis to this. How much code is required? - How much explanation is required? How many dependencies must - be installed before initial setup? - * Does the API offer sufficient power to implement the extensions we - anticipate? - * Any open API-related issues which should block a 1.0.0 release? - -Any problems identified during release candidacy will require -subsequent design changes and planning. - -## Step 9. Release - -Once API changes have been verified and validated, proceed -with release, including: - -* Tagging as version 1.0.0 (at an appropriate time in the - sprint/release cycle.) -* Close any open issues which have been resolved (or made obsolete) - by API changes. \ No newline at end of file diff --git a/docs/src/design/proposals/APIRedesign.md b/docs/src/design/proposals/APIRedesign.md deleted file mode 100644 index 93098c3d7d..0000000000 --- a/docs/src/design/proposals/APIRedesign.md +++ /dev/null @@ -1,1282 +0,0 @@ -# Overview - -The purpose of this document is to review feedback on Open MCT's -current API and propose improvements to the API, particularly for a -1.0.0 release. - -Strategically, this is handled by: - -* Identifying broader goals. -* Documenting feedback and related background information. -* Reviewing feedback to identify trends and useful features. - * In particular, pull out "pain points" to attempt to address, - as well as positive attributes to attempt to preserve. -* Proposing a set of API changes to address these "pain points." - * This also takes into account scheduling concerns. -* Once agreed-upon, formalize this set of changes (e.g. as UML - diagrams) and plan to implement them. - -# Goals - -## Characteristics of a good API - -A good API: - -* Is easy to understand. -* Rewards doing things "the right way." -* Saves development effort. -* Is powerful enough to support a broad range of applications. -* Lends itself to good documentation. - -These characteristics can sometimes be at odds with each other, or -with other concerns. These should typically be viewed as participants -in trades. - -## Evaluating APIs - -APIs may be evaluated based on: - -* Number of interfaces. - * How many application-specific interfaces do I need to know to - solve a certain class of problems? -* Size of interfaces. - * How many methods does each interface have? -* Depth of interfaces. - * Specifically, how many methods do I need to call before the return - value is of a form that is not specific to the API? -* Clarity of interfaces. - * How much documentation or learning is required before an interface is - useful? -* Consistency of interfaces. - * How similar is one interface to an analogous interface? -* Utility of interfaces. - * How much development effort is reduced by utilizing these interfaces, - versus accomplishing the same goals with other tools? -* Power of interfaces. - * How much application functionality can I influence with the interfaces - that are available to me? - -In general, prefer to have a small number of simple, shallow, clear, -useful, powerful interfaces. - -# Developer Feedback - -## Developer Intern Feedback - -This feedback comes from interns who worked closely with -Open MCT as their primary task over the Summer of 2015. - -### Developer Intern 1 - -Worked on bug fixes in the platform and a plugin for search. - -* Initially, it was confusing that many things in files that are in - very different locations in the code base refer to each other. - * Perhaps explain more the organization strategy behind the - different main sections, like "commonUI" vs "core". -* This may be just me, but there are often long chains of related - functions calling each other, and when I had to modify the behavior, - I had a hard time remembering to look for the highest level function - in the call chain to change. I also sometimes had a hard time finding - the connections between the functions. But, that is important because - the implementation of the functions along the chain may change later. -* One very helpful thing that you could add might just be documentation - that is not in paragraph format like in the current developer guide. - I would just like a list of all the functions and members of each kind - of object there is, and descriptions of what they are and how they're - used. - * Also, the current developer guide pdf's words that are in 'code font', - rather than the normal text, are not searchable. - (Depending on the pdf viewer.) -* I do appreciate that there is some example code. -* I am still slightly confused about what "domainObject" refers to in - different situations. -* The tutorials are helpful, but only really for designing new views. - It doesn't help much with gaining understanding of how the other parts - of the application work. -* The general idea of 'telemetry' in this context is kind of confusing. - It is hard to figure out what the difference between the various ways of - dealing with telemetry are. e.g., what is the difference between just - "Telemetry" and the "Telemetry Service"? There are many - "Telemetry Things" which seem related, but in an unclear way. - -### Developer Intern 2 - -Worked on platform bug fixes and mobile support. - -* No guide for the UI and front end for the HTML/CSS part of Open MCT. - Not sure if this is applicable or needed for developers, however would - be helpful to any front end development -* Found it difficult to follow the plot controller & subplot - functions/features, such as zooming. -* If the developer guide could have for references to which files or - functions are key for gestures, browse navigation, etc it would be - helpful for future developers as a place to start looking. I found - it occasionally difficult to find which files or functions I wanted - at first. - -## Plugin Developer Feedback - -This feedback comes from developers who have worked on plugins for -Open MCT, but have not worked on the platform. - -### Plugin Developer 1 - -Used Open MCT over the course of several months (on a -less-than-half-time basis) to develop a -spectrum visualization plugin. - -* Not a lot of time to work on this, made it hard to get up the learning - curve. - * Note that this is the norm, particularly for GDS development. -* JavaScript carries its own learning curve. -* The fact that it pulls in other tools whose APIs need to be learned - also makes the learning curve harder to get up. -* Tracking down interconnected parts was a bit difficult. -* Could really use examples. -* Easy to get lost when not immersed in the style. - -### Plugin Developer 2 - -Used Open MCT over the course of several weeks (on a half-time basis) -to develop a tabular visualization plugin. - -* Pain points - * Unable to copy and paste from tutorial pdfs into code - * Wanted to verify my environment was setup properly so that I - could get the final product working in the end without having - to type everything out. Perhaps there could be something in - github that has the final completed tutorial for new users to - checkout? Or a step by step one kind of like the tutorials on - the angular js webpage? - * Typing too long without seeing results of what I was doing - * At some points in the tutorial I ended up typing for the sake - of typing without knowing what I was really typing for. - * If there were break points where we could run the incomplete - code and just see a variable dump or something even that would - be helpful to know that I am on the right track. - * Documentation on features are a bit hard to find. - * I'm not sure what I can do until I search through examples of - existing code and work my way backwards. - * Maybe you can link the features we are using in the tutorial to - their respective parts in the developer guide? Not sure if that - can be done on PDFs, so maybe a webpage instead? -* Positive Attributes - * Unable to copy and paste from tutorial pdfs into code - * I know I also listed this as a pain, but it was kind of helpful - being forced to read and type everything out. - * "Widgets" are self contained in their own directories. I don't have - to be afraid of exploding things. - * All files/config that I care about for a "widget" can be found in - the bundles.json -* Misc - * Coming from a not so strong webdev background and on top of that a - zero strong angular background I think starting off with a simple - "Hello World" webpage tutorial would have been nice. - * Start off with a bare bones bundle json with an empty controller - and static "Hello World" in the view - * Add the variable "Hello World" into the controller for the view - to display - * Add a model property to the bundle.json to take in "Hello World" - as a parameter and pass through to the controller/view - -### Open Source Contributor - - * [Failures are non-graceful when services are missing.]( - https://github.com/nasa/openmctweb/issues/79) - -## Misc. Feedback (mostly verbal) - -* Easy to add things. -* Separation of concerns is unclear (particularly: "where's the MVC?") -* Telemetry API is confusing. In particular, `TelemetrySeries` should - just be an array. - * Came out of design discussions for Limits. -* Capabilities are confusing. - -## Long-term Developer Notes - -The following notes are from original platform developer, with long -term experience using Open MCT. - -* Bundle mechanism allows for grouping related components across concerns, - and adding and removing these easily. (e.g. model and view components of - Edit mode are all grouped together in the Edit bundle.) - -## AngularJS - -Angular 2.0.0 is coming (maybe by end of 2015.) - -It will not be backwards-compatible with Angular 1.x. -The differences are significant enough that switching to -Angular 2 will require only slightly less effort than switching -to an entirely different framework. - -We can expect AngularJS 1.x to reach end-of-life reasonably soon thereafter. - -Our API is currently a superset of Angular's API, so this directly affects -our API. Specifically, API changes should be oriented towards removing -or reducing the Angular dependency. - -### Angular's Role - -Angular is Open MCT's: - -* Dependency injection framework. -* Template rendering. -* DOM interactions. -* Services library. -* Form validator. -* Routing. - -This is the problem with frameworks: They become a single point of -failure for unrelated concerns. - -### Rationale for Adopting Angular - -The rationale for adopting AngularJS as a framework is -documented in https://trunk.arc.nasa.gov/jira/browse/WTD-208. -Summary of the expected benefits: - -* Establishes design patterns that are well-documented and - understood in industry. This can be beneficial in training - new staff, and lowers the documentation burden on the local - development team. If MCT-Web were to stay with its current - architecture, significant developer-oriented documentation - and training materials would need to be produced. -* The maintainability of MCT-Web would be enhanced by using a - framework like Angular. The local team would enjoy the benefits of - maintenance performed by the sponsor, but would not incur any cost - for this. This would include future upgrades, testing, and bug fixes. -* Replaces DOM-manipulation with a declarative data-binding syntax - which automatically updates views when the model data changes. This - pattern has the potential to save the development team from - time-consuming and difficult-to-debug DOM manipulation. -* Provides data binding to backend models. -* Provides patterns for form validation. -* Establishes documented patterns for add-on modules and services. -* Supports unit tests and system tests (tests which simulate user - interactions in the browser) -* Angular software releases can be expected to be tested, which would - allow MCT-Web developers to focus on MCT-specific features, instead - of the maintenance of custom infrastructure. - -### Actual Experience with Angular - -Most of the expected benefits of Angular have been invalidated -by experience: - -* Feedback from new developers is that Angular was a hindrance to - training, not a benefit. ("One more thing to learn.") Significant - documentation remains necessary for Open MCT. -* Expected enhancements to maintainability will be effectively - invalidated by an expected Angular end-of-life. -* Data binding and automatic view updates do save development effort, - but also carry a performance penalty. This can be solved, but requires - resorting to exactly the sort of DOM manipulations we want to avoid. - In some cases this can require more total development (writing a - poorly-performing Angular version, then "optimizing" by rewriting a - non-Angular version.) -* Expected reduction of test scope will also be invalidated by an - expected end-of-life. - -Other problems: - -* Hinders integrating non-Angular components. (Need to wrap with - Angular API, e.g. as directives, which may be non-trivial.) -* Interferes with debugging by swallowing or obscuring exceptions. - -# Feedback Review - -## Problem Summary - -The following attributes of the current API are undesirable: - -- [ ] It is difficult to tell "where things are" in the code base. -- [ ] It is difficult to see how objects are passed around at run-time. -- [ ] It is difficult to trace flow of control generally. -- [ ] Multiple interfaces for related concepts (e.g. telemetry) is confusing. -- [ ] API documentation is missing or not well-formatted for use. -- [ ] High-level separation of concerns is not made clear. -- [ ] Interface depth of telemetry API is excessive (esp. `TelemetrySeries`) -- [ ] Capabilities as a concept lack clarity. -- [ ] Too many interfaces and concepts to learn. -- [ ] Exposing third-party APIs (e.g. Angular's) increases the learning curve. -- [ ] Want more examples, easier-to-use documentation. -- [ ] UI-relevant features (HTML, CSS) under-documented -- [ ] Good MVC for views of domain objects not enforced (e.g. plots) - -## Positive Features - -It is desirable to retain the following features in an API redesign: - -- [ ] Creating new features and implementing them additively is well-supported. -- [ ] Easy to add/remove features which involve multiple concerns. -- [ ] Features can be self-contained. -- [ ] Declarative syntax makes it easy to tell what's in use. - -## Requirements - -The following are considered "must-haves" of any complete API -redesign: - -- [ ] Don't require usage of Angular API. -- [ ] Don't require support for Angular API. - -# Proposals - -## RequireJS as dependency injector - -Use Require.JS for dependency injection. - -Dependencies will then be responsible for being sufficiently -mutable/extensible/customizable. This can be facilitated by -adding platform classes which can facilitate the addition -of reusable components. - -Things that we usefully acquire via dependency injection currently: - -* Services. -* Extensions (by category). -* Configuration constants. - -Services would be defined (by whatever component is responsible -for declaring it) using `define` and the explicit name of the -service. To allow for the power of composite services, the -platform would provide a `CompositeService` class that supports -this process by providing `register`, `decorate`, and `composite` -methods to register providers, decorators, and aggregators -respectively. (Note that nomenclature changes are also implied -here, to map more clearly to the Composite Pattern and to -avoid the use of the word "provider", which has ambiguity with -Angular.) - -```js -define( - "typeService", - ["CompositeService"], - function (CompositeService) { - var typeService = new CompositeService([ - "listTypes", - "getType" - ]); - - // typeService has `listTypes` and `getType` as methods; - // at this point they are stubbed (will return undefined - // or throw or similar) but this will change as - // decorators/compositors/providers are added. - - // You could build in a compositor here, or - // someone could also define one later - typeService.composite(function (typeServices) { - // ... return a TypeService - }); - - // Similarly, you could register a default implementation - // here, or from some other script. - typeService.register(function (typeService) { - // ... return a TypeService - }, { priority: 'default' }); - - return typeService; - } -); -``` - -Other code could then register additional `TypeService` -implementations (or decorators, or even compositors) by -requiring `typeService` and calling those methods; or, it -could use `typeService` directly. Priority ordering could -be utilized by adding a second "options" argument. - -For extension categories, you could simply use registries: - -```js -define( - "typeRegistry", - ["ExtensionRegistry"], - function (ExtensionRegistry) { - return new ExtensionRegistry(); - } -); -``` - -Where `ExtensionRegistry` extends `Array`, and adds a -`register` method which inserts into the array at some -appropriate point (e.g. with an options parameter that -respects priority order.) - -This makes unit testing somewhat more difficult when you -want to mock injected dependencies; there are tools out -there (e.g. [Squire](https://github.com/iammerrick/Squire.js/)) -which can help with this, however. - -### Benefits - -* Clarifies "how objects are passed around at run-time"; - answer is always "via RequireJS." -* Preserves flexibility/power provided by composite services. -* Lends itself fairly naturally to API documentation via JSDoc - (as compared to declaring things in bundles, which does not.) -* Reduces interface complexity for acquiring dependencies; - one interface for both explicit and "implicit" dependencies, - instead of separate approaches for static and substitutable - dependencies. -* Removes need to understand Angular's DI mechanism. -* Improves usability of documentation (`typeService` is an - instance of `CompositeService` and implements `TypeService` - so you can easily traverse links in the JSDoc.) -* Can be used more easily from Web Workers, allowing services - to be used on background threads trivially. - -### Detriments - -* Having services which both implement the service, and - have methods for registering the service, is a little - weird; would be cleaner if these were separate. - (Mixes concerns.) -* Syntax becomes non-declarative, which may make it harder to - understand "what uses what." -* Allows for ordering problems (e.g. you start using a - service before everything has been registered.) - -## Arbitrary HTML Views - -Currently, writing new views requires writing Angular templates. -This must change if we want to reduce our dependence on Angular. - -Instead, propose that: - -* What are currently called "views" we call something different. - (Want the term view to be more like "view" in the MVC sense.) - * For example, call them "applications." -* Consolidate what are currently called "representations" and - "templates", and instead have them be "views". - -For parity with actions, a `View` would be a constructor which -takes an `ActionContext` as a parameter (with similarly-defined -properties) and exposes a method to retrieve the HTML elements -associated with it. - -The platform would then additionally expose an `AngularView` -implementation to improve compatibility with existing -representations, whose usage would something like: - -```js -define( - ["AngularView"], - function (AngularView) { - var template = "Hello world"; - return new AngularView(template); - } -); -``` - -The interface exposed by a view is TBD, but should provide at -least the following: - -* A way to get the HTML elements that are exposed by & managed - by the view. -* A `destroy` method to detach any listeners at the model level. - -Individual views are responsible for managing their resources, -e.g. listening to domain objects for mutation. To keep DRY, the -platform should include one or more view implementations that -can be used/subclassed which handle common behavior(s). - -### Benefits - -* Using Angular API for views is no longer required. -* Views become less-coupled to domain objects. Domain objects - may be present in the `ViewContext`, but this also might - just be a "view" of some totally different thing. -* Helps clarify high-level concerns in the API (a View is now - really more like a View in the MVC sense; although, not - completely, so this gets double-booked as a detriment.) -* Having a `ViewContext` that gets passed in allows views to - be more "contextually aware," which is something that has - been flagged previously as a UX desire. - -### Detriments - -* Becomes less clear how views relate to domain objects. -* Adds another interface. -* Leaves an open problem of how to distinguish views that - a user can choose (Plot, Scrolling List) from views that - are used more internally by the application (tree view.) -* Views are still not Views in the MVC sense (in practice, - the will likely be view-controller pairs.) We could call - them widgets to disambiguate this. -* Related to the above, even if we called these "widgets" - it would still fail to enforce good MVC. - -## Wrap Angular Services - -Wrap Angular's services in a custom interfaces; e.g. -replace `$http` with an `httpService` which exposes a useful -subset of `$http`'s functionality. - -### Benefits - -* Removes a ubiquitous dependency on Angular. -* Allows documentation for these features to be co-located - and consistent with other documentation. -* Facilitates replacing these with non-Angular versions - in the future. - -### Detriments - -* Increases the number of interfaces in Open MCT. (Arguably, - not really, since the same interfaces would exist if exposed - by Angular.) - -## Bundle Declarations in JavaScript - -Replace `bundle.json` files (and bundle syntax generally) with -an imperative form. There would instead be a `Bundle` interface -which scripts can implement (perhaps assisted by a platform -class.) - -The `bundles.json` file would then be replaced with a `bundles.js` -or `Bundles.js` that would look something like: - -```js -define( - [ - 'platform/core/PlatformBundle', - // ... etc ... - 'platform/features/plot/PlotBundle' - ], - function () { - return arguments; - } -); -``` - -Which could in turn be used by an initializer: - -```js -define( - ['./bundles', 'mct'], - function (bundles, mct) { - mct.initialize(bundles); - } -); -``` - -A `Bundle` would have a constructor that took some JSON object -(a `BundleContext`, lets say) and would provide methods for -application life-cycle events. Depending on other choices, -a dependency injector could be passed in at some appropriate -life-cycle call (e.g. initialize.) - -This would also allow for "composite bundles" which serve as -proxies for multiple bundles. The `BundleContext` could contain -(or later be amended to contain) filtering rules to ignore -other bundles and so forth (this has been useful for administering -Open MCT in subtly different configurations in the past.) - -### Benefits - -* Imperative; more explicit, less magic, more clear what is going on. -* Having a hierarchy of "bundles" could make it easier to navigate - (relevant groupings can be nested in a manner which is not - currently well-supported.) -* Lends itself naturally to a compilation step. -* Nudges plugin authors to "do your initialization and registration - in a specific place" instead of mixing in registration of features - with their implementations. - -### Detriments - -* Introduces another interface. -* Loses some of the convenience of having a declarative - summary of components and their dependencies. - -## Pass around a dependency injector - -:warning: Note that this is incompatible with the -[RequireJS as dependency injector](#requirejs-as-dependency-injector) -proposal. - -Via some means (such as in a registration lifecycle event as -described above) pass a dependency injector to plugins to allow -for dependencies to be registered. - -For example: - -```js -MyBundle.prototype.registration = function (architecture) { - architecture.service('typeService').register(MyTypeService); - architecture.extension('actions').register( - [ 'foo' ], - function (foo) { return new MyAction(foo); } - ); -}; -``` - -### Benefits - -* Ensures that registration occurs at an appropriate stage of - application execution, avoiding start-up problems. -* Makes registration explicit (generally easier to understand) - rather than implicit. -* Encapsulates dependency injection nicely. - -### Detriments - -* Increases number of interfaces to learn. -* Syntax likely to be awkward, since in many cases we really - want to be registering constructors. - -## Remove partial constructors - -Remove partial constructors; these are confusing. It is hard to -recognize which constructor arguments are from dependencies, and -which will be provided at run-time. Instead, it is the responsibility -of whoever is introducing a component to manage these things -separately. - -### Benefits - -* More clarity. - -### Detriments - -* Possibly results in redundant effort to manage this difference - (other APIs may need to be adjusted accordingly.) - -## Rename Views to Applications - -Rename (internally to the application, not necessarily in UI or -in user guide) what are currently called `views` to `applications`. - -### Benefits - -* Easier to understand. What is currently called a "view" is, - in the MVC sense, a view-controller pair, usually with its own - internal model for view state. Calling these "applications" - would avoid this ambiguity/inconsistency. -* Also provides an appropriate mindset for building these; - particularly, sets the expectation that you'll want to decompose - this "application" into smaller pieces. This nudges developers - in appropriate directions (in contrast to `views`, which - typically get implemented as templates with over-complicated - "controllers".) - -### Detriments - -* Developer terminology falls out of sync with what is used in - the user guide. - -## Provide Classes for Extensions - -As a general pattern, when introducing extension categories, provide -classes with a standard implementation of these interfaces that -plugin developers can `new` and register. - -For example, instead of declaring a type as: - -```json -{ - "types": [{ - "key": "sometype", - "glyph": "X", - "etc": "..." - }] -} -``` - -You would register one as: - -```js -// Assume we have gotten a reference to a type registry somehow -typeRegistry.register(new Type({ - "key": "sometype", - "glyph": "X", - "etc": "..." -})); -``` - -### Benefits - -* Easier to understand (less "magic"). -* Lends itself naturally to substitution of different implementations - of the same interface. -* Allows for run-time decisions about exactly what gets registered. - -### Detriments - -* Adds some modest boilerplate. -* Provides more opportunity to "do it wrong." - -## Normalize naming conventions - -Adopt and obey the following naming conventions for AMD modules -(and for injectable dependencies, which may end up being modules): - -* Use `UpperCamelCase` for classes. -* Use `lowerCase` names for instances. - * Use `someNoun` for object instances which implement some - interface. The noun should match the implemented interface, - when applicable. - * `useSomeVerb` for functions. -* Use `ALL_CAPS_WITH_UNDERSCORES` for other values, including - "struct-like" objects (that is, where the object conceptually - contains properties rather than methods.) - -### Benefits - -* Once familiar with the conventions, easier to understand what - individual modules are. - -### Detriments - -* A little bit inflexible. - -## Expose no third-party APIs - -As a general practice, expose no third-party APIs as part of the -platform. - -For cases where you do want to access third-party APIs directly -from other scripts, this behavior should be "opt-in" instead of -mandatory. For instance, to allow addition of Angular templates, -an Angular-support bundle could be included which provides an -`AngularView` class, a `controllerRegistry`, et cetera. Importantly, -such a bundle would need to be kept separate from the platform API, -or appropriately marked as non-platform in the API docs (an -`@experimental` tag would be nice here if we feel like extending -JSDoc.) - -### Benefits - -* Simplifies learning curve (only one API to learn.) -* Reduces Angular dependency. -* Avoids the problems of ubiquitous dependencies generally. - -### Detriments - -* Increases documentation burden. - -## Register Extensions as Instances instead of Constructors - -Register extensions as object instances instead of constructors. -This allows for API flexibility w.r.t. constructor signatures -(and avoids the need for partial constructors) and additionally -makes it easier to provide platform implementations of extensions -that can be used, subclassed, etc. - -For instance, instead of taking an `ActionContext` in its -constructor, an `Action` would be instantiated once and would -accept appropriate arguments to its methods: - -```js -function SomeAction { -} -SomeAction.prototype.canHandle = function (actionContext) { - // Check if we can handle this context -}; -SomeAction.prototype.perform = function (actionContext) { - // Perform this action, in this context -}; -``` - -### Benefits - -* Reduces scope of interfaces to understand (don't need to know - what constructor signature to provide for compatibility.) - -### Detriments - -* Requires refactoring of various types; may result in some - awkward APIs or extra factory interfaces. - -## Remove capability delegation - -The `delegation` capability has only been useful for the -`telemetry` capability, but using both together creates -some complexity to manage. In practice, these means that -telemetry views need to go through `telemetryHandler` to -get their telemetry, which in turn has an awkward API. - -This could be resolved by: - -* Removing `delegation` as a capability altogether. -* Reworking `telemetry` capability API to account for - the possibility of multiple telemetry-providing - domain objects. (Perhaps just stick `domainObject` - in as a field in each property of `TelemetryMetadata`?) -* Move the behavior currently found in `telemetryHandler` - into the `telemetry` capability itself (either the - generic version, or a version specific to telemetry - panels - probably want some distinct functionality - for each.) - -### Benefits - -* Reduces number of interfaces. -* Accounting for the possibility of multiple telemetry objects - in the `telemetry` capability API means that views using - this will be more immediately aware of this as a possibility. - -### Detriments - -* Increases complexity of `telemetry` capability's interface - (although this could probably be minimized.) - -## Nomenclature Change - -Instead of presenting Open MCT as a "framework" or -"platform", present it as an "extensible application." - -This is mostly a change for the developer guide. A -"framework" and a "platform" layer would still be useful -architecturally, but the plugin developer's mental model -for this would then be inclined toward looking at defined -extension points. The underlying extension mechanism could -still be exposed to retain the overall expressive power of -the application. - -This may subtly influence other design decisions in order -to match the "extensible application" identity. On a certain -level, this contradicts the proposal to -[rename views to applications](#rename-views-to-applications). - -### Benefits - -* May avoid incurring some of the "framework aversion" that - is common among JavaScript developers. -* More accurately describes the application. - -### Detriments - -* May also be a deterrent to developers who prefer the more - "green field" feel of developing applications on a useful - platform. - -## Capabilities as Mixins - -Change the behavior of capabilities such that they act as -mixins, adding additional methods to domain objects. -Checking if a domain object has a `persistence` capability -would instead be reduced to checking if it has a `persist` -method. - -Mixins would be applied in priority order and filtered for -applicability by policy. - -### Benefits - -* Replaces "capabilities" (which, as a concept, can be hard - to grasp) with a more familiar "mixins" concept, which has - been used more generally across many languages. -* Reduces interface depth. - -### Detriments - -* Requires checking for the interface exposed by a domain - object. Alternately, could use `instanceof`, but would - need to take care to ensure that the prototype chain of - the domain object is sufficient to do this (which may - enforce awkward or non-obvious constraints on the way these - mixins are implemented.) -* May complicate documentation; understanding the interface - of a given domain object requires visiting documentation - for various mixins. - -## Remove Applies-To Methods - -Remove all `appliesTo` static methods and replace them with -appropriate policy categories. - -### Benefits - -* Reduces sizes of interfaces. Handles filtering down sets - of extensions in a single consistent way. - -### Detriments - -* Mixes formal applicability with policy; presently, `appliesTo` - is useful for cases where a given extension cannot, even in - principle, be applied in a given context (e.g. a domain object - model is missing the properties which describe the behavior), - whereas policy is useful for cases where applicability is - being refined for business or usability reasons. Colocating - the former with the extension itself has some benefits - (exhibits better cohesion.) - * This could be mitigated in the proposed approach by locating - `appliesTo`-like policies in the same bundle as the relevant - extension. - -## Revise Telemetry API - -Revise telemetry API such that: - -* `TelemetrySeries` is replaced with arrays of JavaScript objects - with properties. -* It is no longer necessary to use `telemetryHandler` (plays well - with proposal to - [remove capability delegation](#remove-capability delegation)) -* Change `request` call to take a callback, instead of returning - a promise. This allows that callback to be invoked several - times (e.g. for progressive loading, or to reflect changes from - the time conductor.) - -Should also consider: - -* Merge `subscribe` functionality into `request`; that is, handle - real-time data as just another thing that triggers the `request` - callback. -* Add a useful API to telemetry metadata, allowing things like - formats to be retrieved directly from there. - -As a consequence of this, `request` would need to return an object -representing the active request. This would need to be able to -answer the following questions and provide the following behavior: - -* Has the request been fully filled? (For cases like progressive - loading?) -* What data has changed since the previous callback? (To support - performance optimizations in plotting; e.g. append real-time - data.) -* Stop receiving updates for this request. -* Potentially, provide utility methods for dealing with incoming - data from the request. - -Corollary to this, some revision of `TelemetryMetadata` properties -may be necessary to fully and usably describe the contents of -a telemetry series. - -### Benefits - -* Reduces interface depth. -* Reduces interface size (number of methods.) -* Supports a broader range of behaviors (e.g. progressive loading) - within the same interface. - -### Detriments - -* Merging with `subscribe` may lose the clarity/simplicity of the - current API. - -## Allow Composite Services to Fail Gracefully - -Currently, when no providers are available for a composite service -that is depended-upon, dependencies cannot be resolved and the -application fails to initialize, with errors appearing in the -developer console. - -This is acceptable behavior for truly unrecoverable missing -dependencies, but in many cases it would be preferable to allow a -given type of composite service to define some failure behavior -when no service of an appropriate type is available. - -To address this: - -* Provide an interface (preferably - [imperative](#bundle-Declarations-in-javascript)) - for declaring composite services, independent of any implementation - of an aggregator/decorator/provider. This allows the framework - layer to distinguish between unimplemented dependencies (which - could have defined failover strategies) from undefined dependencies - (which cannot.) -* Provide a default strategy for service composition that picks - the highest-priority provider, and logs an error (and fails to - satisfy) if no providers have been defined. -* Allow this aggregation strategy to be overridden, much as one - can declare aggregators currently. However, these aggregators should - get empty arrays when no providers have been registered (instead of - being ignored), at which point they can decide how to handle this - situation (graceful failure when it's possible, noisy errors when - it is not.) - -### Benefits - -* Allows for improved robustness and fault tolerance. -* Makes service declarations explicit, reducing "magic." - -### Detriments - -* Requires the inclusion of software units which define services, - instead of inferring their existence (slight increase in amount - of code that needs to be written.) -* May result in harder-to-understand errors when overridden - composition strategies do not failover well (that is, when they - do need at least implementation, but fail to check for this.) - -## Plugins as Angular Modules - -Do away with the notion of bundles entirely; use Angular modules -instead. Registering extensions or components of composite services -would then be handled by configuring a provider; reusable classes -could be exposed by the platform for these. - -Example (details are flexible, included for illustrative purposes): - -```javascript -var mctEdit = angular.module('mct-edit', ['ng', 'mct']); - -// Expose a new extension category -mctEdit.provider('actionRegistry', ExtensionCategoryProvider); - -// Expose a new extension -mctEdit.config(['actionRegistryProvider', function (arp) { - arp.register(EditPropertiesAction); -}]) - -return mctEdit; -``` - -Incompatible with proposal to -(expose no third-party APIs)[#expose-no-third-party-apis]; Angular -API would be ubiquitously exposed. - -This is a more specific variant of -(Bundle Declarations in JavaScript)[#bundle-declarations-in-javascript]. - -### Benefits - -* Removes a whole category of API (bundle definitions), reducing - learning curve associated with the software. -* Closer to Angular style, reducing disconnect between learning - Angular and learning Open MCT (reducing burden of having - to learn multiple paradigms.) -* Clarifies "what can be found where" (albeit not perfectly) - since you can look to module dependencies and follow back from there. - -### Detriments - -* Hardens dependency on Angular. -* Increases depth of understanding required of Angular. -* Increases amount of boilerplate (since a lot of this has - been short-handed by existing framework layer.) - -## Contextual Injection - -For extensions that depend on certain instance-level run-time -properties (e.g. actions or views which use objects and/or specific -capabilities of those objects), declare these features as dependencies -and expose them via dependency injection. (AngularJS does this for -`$scope` in the context of controllers, for example.) - -A sketch of an implementation for this might look like: - -```js -function ExtensionRegistry($injector, extensions, getLocals) { - this.$injector = $injector; - this.extensions = extensions; - this.getLocals = getLocals; -} -ExtensionRegistry.prototype.get = function () { - var $injector = this.$injector, - locals = this.getLocals.apply(null, arguments); - return this.extensions.filter(function (extension) { - return depsSatisfiable(extension, $injector, locals); - }).map(function (extension) { - return $injector.instantiate(extension, locals); - }); -}; - - -function ExtensionRegistryProvider(getLocals) { - this.getLocals = getLocals || function () { return {}; }; - this.extensions = []; -} -ExtensionRegistryProvider.prototype.register = function (extension) { - this.extensions.push(extension); -}; -ExtensionRegistryProvider.prototype.$get = ['$injector', function ($injector) { - return new ExtensionRegistry($injector, this.extensions, this.getLocals); -}]; -``` - -Extension registries which need to behave context-sensitively could -subclass this to describe how these contextual dependencies are satisfied -(for instance, by returning various capability properties in `getLocals`). - -Specific extensions could then declare dependencies as appropriate to the -registry they are using: - -```js -app.config(['actionRegistryProvider', function (arp) { - arp.register(['contextCapability', 'domainObject', RemoveAction]); -}]); -``` - -### Benefits - -* Allows contextual dependencies to be fulfilled in the same (or similar) - manner as global dependencies, increasing overall consistency of API. -* Clarifies dependencies of individual extensions (currently, extensions - themselves or policies generally need to imperatively describe what - dependencies will be used in order to filter down to applicable - extensions.) -* Factors out some redundant code from relevant extensions; actions, - for instance, no longer need to interpret an `ActionContext` object. - Instead, their constructors take inputs that are more relevant to - their behavior. -* Removes need for partial construction, as any arguments which would - normally be appended after partialization can instead be declared as - dependencies. Constructors in general become much less bound to the - specifics of the platform. - -### Detriments - -* Slightly increases dependency on Angular; other dependency injectors - may not offer comparable ways to specify dependencies non-globally. -* Not clear (or will take effort to make clear) which dependencies are - available for which extensions. Could be mitigated by standardizing - descriptions of context across actions and views, but that may offer - its own difficulties. -* May seem counter-intuitive coming from "vanilla" AngularJS, where - `$scope` is the only commonly-used context-sensitive dependency. - -## Add new abstractions for actions - -Originally suggested in -[this comment](https://github.com/nasa/openmctweb/pull/69#issuecomment-156199991): - -> I think there are some grey areas with actions: are they all directly -tied to user input? If so, why do they have any meaning in the back end? -Maybe we should look at different abstractions for actions: - -> * `actions` - the basic implementation of an action, essentially a - function declaration. for example, `copy` requires arguments of - `object` and a `target` to place the object in. at this level, - it is reusable in a CLI. -> * `context menu actions` - has criteria for what it applies to. - when it is visible, and defines how to get extra > input from a - user to complete that action. UI concern only. -> * `gesture-handler` - allows for mapping a `gesture` to an action, - e.g. drag and drop for link. UI Concern only. - -> We could add context menu actions for domain objects which navigate -to that object, without having to implement an action that has no real -usage on a command line / backend. - -### Benefits - -* Clearly delineates concerns (UI versus model) - -### Detriments - -* Increases number of interfaces. - -## Add gesture handlers - -See [Add new abstractions for actions](#add-new-abstractions-for-actions); -adding an intermediary between gestures and the actions that they -trigger could be useful in separating concerns, and for more easily -changing mappings in a mobile context. - -### Benefits - -* Clearly decouples UI concerns from the underlying model changes - they initiate. -* Simplifies and clarifies mobile support. - -### Detriments - -* Increases number of interfaces. - -# Decisions - -After review on Dec. 8, 2015, team consensus on these proposals is -as follows: - -Proposal | @VWoeltjen | @larkin | @akhenry | Consensus -----|:---:|:---:|:---:|:---: -RequireJS as dependency injector | :-1: | :neutral_face: :question: | [:-1:](https://github.com/nasa/openmctweb/pull/69#discussion_r44349731) | [:question:](https://github.com/nasa/openmctweb/issues/461) -Arbitrary HTML Views | :+1: | :+1: | | [:+1: 1](https://github.com/nasa/openmctweb/issues/463) -Wrap Angular Services | :-1: | [:-1:](https://github.com/nasa/openmctweb/pull/69#discussion_r43801221) | [:-1:](https://github.com/nasa/openmctweb/pull/69#discussion_r44355057) | :no_entry_sign: -Bundle Declarations in JavaScript | :+1: | :neutral_face: :question: | | [:+1:](https://github.com/nasa/openmctweb/issues/450) -Pass around a dependency injector | :-1: | :-1: | | :-1: -Remove partial constructors | :+1: | :+1: | | [:+1:](https://github.com/nasa/openmctweb/issues/462) -Rename Views to ~~Applications~~ | :+1: | :neutral_face: :question: | | [:+1: 2](https://github.com/nasa/openmctweb/issues/463) -Provide Classes for Extensions | :+1: | :+1: | | [:+1:](https://github.com/nasa/openmctweb/issues/462) -Normalize naming conventions | :+1: | :+1: | | :+1: -Expose no third-party APIs | :+1: * | [:-1:](https://github.com/nasa/openmctweb/pull/69#discussion_r43801221) | [:+1:](https://github.com/nasa/openmctweb/pull/69#discussion_r43801221) † | :+1: 3 -Register Extensions as Instances instead of Constructors | :+1: | :-1: | | [:+1:](https://github.com/nasa/openmctweb/issues/462) -Remove capability delegation | :+1: | :+1: | | [:+1:](https://github.com/nasa/openmctweb/issues/463) -Nomenclature Change | :+1: | [:+1:](https://github.com/nasa/openmctweb/issues/229#issuecomment-153453035) | | :white_check_mark: ‡ -Capabilities as Mixins | | :+1: | [:+1:](https://github.com/nasa/openmctweb/pull/69#discussion_r44355473) | [:question: 4](https://github.com/nasa/openmctweb/issues/463) -Remove Applies-To Methods | | :-1: | | :-1: -Revise Telemetry API | :+1: | :+1: | | [:+1: 5](https://github.com/nasa/openmctweb/issues/463) -Allow Composite Services to Fail Gracefully | :+1: | :-1: | | [:+1: 6](https://github.com/nasa/openmctweb/issues/463) -Plugins as Angular Modules | :+1: | :neutral_face: :question: | | [:question:](https://github.com/nasa/openmctweb/issues/461) -Contextual Injection | | :-1: | | [:question:](https://github.com/nasa/openmctweb/issues/461) -Add new abstractions for actions | [:-1:](https://github.com/nasa/openmctweb/pull/69#issuecomment-158172485) :question: | :+1: | | :-1: -Add gesture handlers | :+1: | :+1: :question: | | [:+1:](https://github.com/nasa/openmctweb/issues/463) - -* Excepting Angular APIs. Internally, continue to use code style -where classes are declared separately from their registration, such -that ubiquity of Angular dependency is minimized. - -† "I think we should limit the third party APIs we expose to -one or two, but I worry it might be counterproductive to -completely hide them." - -‡ Some ambiguity about what to call ourselves if not a platform, -but general agreement that "platform" is not a good term. -More Detail on Pete's Opinions Here: -https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign_PeteRichards.md#notes-on-current-api-proposals - -1 Needs to be designed carefully; don't want to do this with -a complicated interface, needs to be significantly simpler than wrapping -with an Angular directive would be. - -2 Agree that we need a new name, but it should not be "application" - -3 Don't want to expose (or require usage of) third-party -APIs generally. Angular may be an exception in the sense that it is an -API we presume to be present. Can use third-party APIs internally, but -don't want to support them or be tied to them. - -4 Want to have a separate spin-off discussion about -capabilities. Want to consider several alternatives here. -At minimum, though, mixins would be an improvement relative -to how these are currently handled. - -5 Agree we want to revise APIs, but this should -be a larger spin-off. - -6 Not necessarily as described, but expected to be a -property of composite services in whatever formulation they -take. Should not be default behavior. - - -[Additional proposals](APIRedesign_PeteRichards.md) considered: - -Proposal | Consensus -------|------ -Imperative component registries | [:+1:](https://github.com/nasa/openmctweb/issues/462) -Get rid of "extension category" concept. | [:+1:](https://github.com/nasa/openmctweb/issues/462) -Reduce number and depth of extension points | :+1: -Composite services should not be the default | [:question:](https://github.com/nasa/openmctweb/issues/463) -Get rid of views, representations, and templates. | [:+1: 1](https://github.com/nasa/openmctweb/issues/463) -More angular: for all services | [:question:](https://github.com/nasa/openmctweb/issues/461) -Less angular: only for views | [:question:](https://github.com/nasa/openmctweb/issues/461) -Use systemjs for module loading | [:+1: 2](https://github.com/nasa/openmctweb/issues/459) -Use gulp or grunt for standard tooling | [:+1:](https://github.com/nasa/openmctweb/issues/459) -Package openmctweb as single versioned file. | [:+1:](https://github.com/nasa/openmctweb/issues/458) -Refresh on navigation | [:+1: 3](https://github.com/nasa/openmctweb/issues/463) -Move persistence adapter to promise rejection. | [:+1:](https://github.com/nasa/openmctweb/issues/463) -Remove bulk requests from providers | [:+1: 4](https://github.com/nasa/openmctweb/issues/463) - -1 Need to agree upon details at design-time, but -basic premise is agreed-upon - want to replace -views/representations/templates with a common abstraction -(and hoist out the non-commonalities to other places as appropriate) - -2 Beneficial but not strictly necessary (may be -lower-effort alternatives); should prioritize accordingly during planning - -3 Some effort will be required to make all of the state -that needs to persist among route changes actually be persistent. -Will want to address this at design-time (will want to look at -libraries to simplify this, for instance) - -4 Maybe not all providers, but anywhere there is not a -strong case for building batching into the API we should prefer -simplicity. (Want to pay specific attention to telemetry here.) diff --git a/docs/src/design/proposals/APIRedesign_PeteRichards.md b/docs/src/design/proposals/APIRedesign_PeteRichards.md deleted file mode 100644 index 6e3a94310f..0000000000 --- a/docs/src/design/proposals/APIRedesign_PeteRichards.md +++ /dev/null @@ -1,251 +0,0 @@ - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [Reducing interface depth (the bundle.json version)](#reducing-interface-depth-the-bundlejson-version) - - [Imperative component registries](#imperative-component-registries) - - [Get rid of "extension category" concept.](#get-rid-of-extension-category-concept) - - [Reduce number and depth of extension points](#reduce-number-and-depth-of-extension-points) - - [Composite services should not be the default](#composite-services-should-not-be-the-default) - - [Get rid of views, representations, and templates.](#get-rid-of-views-representations-and-templates) -- [Reducing interface depth (The angular discussion)](#reducing-interface-depth-the-angular-discussion) - - [More angular: for all services](#more-angular-for-all-services) - - [Less angular: only for views](#less-angular-only-for-views) -- [Standard packaging and build system](#standard-packaging-and-build-system) - - [Use systemjs for module loading](#use-systemjs-for-module-loading) - - [Use gulp or grunt for standard tooling](#use-gulp-or-grunt-for-standard-tooling) - - [Package openmctweb as single versioned file.](#package-openmctweb-as-single-versioned-file) -- [Misc Improvements](#misc-improvements) - - [Refresh on navigation](#refresh-on-navigation) - - [Move persistence adapter to promise rejection.](#move-persistence-adapter-to-promise-rejection) - - [Remove bulk requests from providers](#remove-bulk-requests-from-providers) -- [Notes on current API proposals:](#notes-on-current-api-proposals) -- [[1] Footnote: The angular debacle](#1-footnote-the-angular-debacle) - - ["Do or do not, there is no try"](#do-or-do-not-there-is-no-try) - - [A lack of commitment](#a-lack-of-commitment) - - [Commitment is good!](#commitment-is-good) - - - - -# Reducing interface depth (the bundle.json version) - -## Imperative component registries - -Transition component registries to javascript, get rid of bundle.json and bundles.json. Prescribe a method for application configuration, but allow flexibility in how application configuration is defined. - -Register components in an imperative fashion, see angularApp.factory, angularApp.controller, etc. Alternatively, implement our own application object with new registries and it's own form of registering objects. - -## Get rid of "extension category" concept. - -The concept of an "extension category" is itself an extraneous concept-- an extra layer of interface depth, an extra thing to learn before you can say "hello world". Extension points should be clearly supported and documented with whatever interfaces make sense. Developers who wish to add something that is conceptually equivalent to an extension category can do so directly, in the manner that suites their needs, without us forcing a common method on them. - -## Reduce number and depth of extension points - -Clearly specify supported extension points (e.g. persistence, model providers, telemetry providers, routes, time systems), but don't claim that the system has a clear and perfect repeatable solution for unknown extension types. New extension categories can be implemented in whatever way makes sense, without prescribing "the one and only system for managing extensions". - -The underlying problem here is we are predicting needs for extension points where none exist-- if we try and design the extension system before we know how it is used, we design the wrong thing and have to rewrite it later. - -## Composite services should not be the default - -Understanding composite services, and describing services as composite services can confuse developers. Aggregators are implemented once and forgotten, while decorators tend to be hacky, brittle solutions that are generally needed to avoid circular imports. While composite services are a useful construct, it reduces interface depth to implement them as registries + typed providers. - -You can write a provider (provides "thing x" for "inputs y") with a simple interface. A provider has two or more methods: -* a method which takes "inputs y" and returns True if it knows how to provide "thing x", false otherwise. -* one or more methods which provide "thing x" for objects of "inputs y". - -Actually checking whether a provider can respond to a request before asking it to do work allows for faster failure and clearer errors when no providers match the request. - -## Get rid of views, representations, and templates. - -Templates are an implementation detail that should be handled by module loaders. Views and representations become "components," and a new concept, "routes", is used to exposing specific views to end users. - -`components` - building blocks for views, have clear inputs and outputs, and can be coupled to other components when it makes sense. (e.g. parent-child components such as menu and menu item), but should have ZERO knowledge of our data models or telemetry apis. They should define data models that enable them to do their job well while still being easy to test. - -`routes` - a view type for a given domain object, e.g. a plot, table, display layout, etc. Can be described as "whatever shows in the main screen when you are viewing an object." Handle loading of data from a domain object and passing that data to the view components. Routes should support editing as it makes sense in their own context. - -To facilitate testing: - -* routes should be testable without having to test the actual view. -* components should be independently testable with zero knowledge of our data models or telemetry APIs. - -Component code should be organized side by side, such as: - -``` -app -|- components - |- productDetail - | |- productDetail.js - | |- productDetail.css - | |- productDetail.html - | |- productDetailSpec.js - |- productList - |- checkout - |- wishlist -``` - -Components are not always reusable, and we shouldn't be overly concerned with making them so. If components are heavily reused, they should either be moved to a platform feature (e.g. notifications, indicators), or broken off as an external dependency (e.g. publish mct-plot as mct-plot.js). - - -# Reducing interface depth (The angular discussion) - -Two options here: use more angular, use less angular. Wrapping angular methods does not reduce interface depth and must be avoided. - -The primary issue with angular is duplications of concerns-- both angular and the openmctweb platform implement the same tools side by side and it can be hard to comprehend-- it increases interface depth. For other concerns, see footnotes[1]. - -Wrapping angular methods for non-view related code is confusing to developers because of the random constraints angular places on these items-- developers ultimately have to understand both angular DI and our framework. For example, it's not possible to name the topic service "topicService" because angular expects Services to be implemented by Providers, which is different than our expectation. - -To reduce interface depth, we can replace our own provider and registry patterns with angular patterns, or we can only utilize angular view logic, and only use our own DI patterns. - -## More angular: for all services - -Increasing our commitment to angular would mean using more of the angular factories, services, etc, and less of our home grown tools. We'd implement our services and extension points as angular providers, and make them configurable via app.config. - -As an example, registering a specific type of model provider in angular would look like: - -```javascript -mct.provider('model', modelProvider() { /* implementation */}); - -mct.config(['modelProvider', function (modelProvider) { - modelProvider.providers.push(RootModelProvider); -}]); -``` - -## Less angular: only for views - -If we wish to use less angular, I would recommend discontinuing use of all angular components that are not view related-- services, factories, $http, etc, and implementing them in our own paradigm. Otherwise, we end up with layered interfaces-- one of the goals we would like to avoid. - - -# Standard packaging and build system - -Standardize the packaging and build system, and completely separate the core platform from deployments. Prescribe a starting point for deployments, but allow flexibility. - -## Use systemjs for module loading - -Allow developers to use whatever module loading system they'd like to use, while still supporting all standard cases. We should also use this system for loading assets (css, scss, html templates), which makes it easier to implement a single file deployment using standard build tooling. - -## Use gulp or grunt for standard tooling - -Using gulp or grunt as a task runner would bring us in line with standard web developer workflows and help standardize rendering, deployment, and packaging. Additional tools can be added to the workflow at low cost, simplifying the setup of developer environments. - -Gulp and grunt provide useful developer tooling such as live reload, automatic scss/less/etc compilation, and ease of extensibility for standard production build processes. They're key in decoupling code. - -## Package openmctweb as single versioned file. - -Deployments should depend on a specific version of openmctweb, but otherwise be allowed to have their own deployment and development toolsets. - -Customizations and deployments of openmctweb should not use the same build tooling as the core platform; instead they should be free to use their own build tools as they wish. (We would provide a template for an application, based on our experience with warp-for-rp and vista) - -Installation and utilization of openmctweb should be as simple as downloading the js file, including it in your own html page, and then initializing an app and running it. If a developer would prefer, they could use bower or npm to handle installation. - -Then, if we're using imperative methods for extending the application we can use the following for basic customization: - -```html - - -``` - -This packaging reduces the complexity of managing multiple deployed versions, and also allows us to provide users with incredibly simple tutorials-- they can use whatever tooling they like. For instance, a hello world tutorial may take the option of "exposing a new object in the tree". - -```javascript -var myApp = new OpenMCTWeb(); -myApp.roots.addRoot({ - id: 'myRoot', - name: 'Hello World!', -}); -myApp.routes.setDefault('myRoot'); -myApp.run(); -``` - -# Misc Improvements - -## Refresh on navigation -In cases where navigation events change the entire screen, we should be using routes and location changes to navigate between objects. We should be using href for all navigation events. - -At the same time, navigating should refresh state of every visible object. A properly configured persistence store will handle caching with standard cache headers and 304 not modified responses, which will provide good performance of object reloads, while helping us ensure that objects are always in sync between clients. - -View state (say, the expanded tree nodes) should not be tied to caching of data-- it should be something we intentionally persist and restore with each navigation. Data (such as object definitions) should be reloaded from server as necessary to restore state. - -## Move persistence adapter to promise rejection. -Simple: reject on fail, resolve on success. - -## Remove bulk requests from providers - -Aggregators can request multiple things at once, but individual providers should only have to implement handling at the level of a single request. Each provider can implement it's own internal batching, but it should support making requests at a finer level of detail. - -Excessive wrapping of code with $q.all causes additional digest cycles and decreased performance. - -For example, instead of every telemetry provider responding to a given telemetry request, aggregators should route each request to the first provider that can fulfill that request. - - -# Notes on current API proposals: - -* [RequireJS for Dependency Injection](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#requirejs-as-dependency-injector): requires other topics to be discussed first. -* [Arbitrary HTML Views](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#arbitrary-html-views): think there is a place for it, requires other topics to be discussed first. -* [Wrap Angular Services](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#wrap-angular-services): No, this is bad. -* [Bundle definitions in Javascript](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#bundle-declarations-in-javascript): Points to a solution, but ultimately requires more discussion. -* [pass around a dependency injector](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#pass-around-a-dependency-injector): No. -* [remove partial constructors](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#remove-partial-constructors): Yes, this should be superseded by another proposal though. The entire concept was a messy solution to dependency injection issues caused by declarative syntax. -* [Rename views to applications](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#rename-views-to-applications): Points to a problem that needs to be solved but I think the name is bad. -* [Provide classes for extensions](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#provide-classes-for-extensions): Yes, in specific places -* [Normalize naming conventions](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#normalize-naming-conventions): Yes. -* [Expose no third-party APIs](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#expose-no-third-party-apis): Completely disagree, points to a real problem with poor angular integration. -* [Register Extensions as Instances instead of Constructors](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#register-extensions-as-instances-instead-of-constructors): Superseded by the fact that we should not hope to implement a generic construct. -* [Remove capability delegation](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#remove-capability-delegation): Yes. -* [Nomenclature Change](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#nomenclature-change): Yes, hope to discuss the implications of this more clearly in other proposals. -* [Capabilities as mixins](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#capabilities-as-mixins): Yes. -* [Remove appliesTo methods](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#remove-applies-to-methods): No-- I think some level of this is necessary. I think a more holistic approach to policy is needed. it's a rather complicated system. -* [Revise telemetry API](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#revise-telemetry-api): If we can rough out and agree to the specifics, then Yes. Needs discussion. -* [Allow composite services to fail gracefully](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#allow-composite-services-to-fail-gracefully): No. As mentioned above, I think composite services themselves should be eliminated for a more purpose bound tool. -* [Plugins as angular modules](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#plugins-as-angular-modules): Should we decide to embrace Angular completely, I would support this. Otherwise, no. -* [Contextual Injection](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#contextual-injection): No, don't see a need. -* [Add New Abstractions for Actions](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#add-new-abstractions-for-actions): Worth a discussion. -* [Add gesture handlers](https://github.com/nasa/openmctweb/blob/api-redesign/docs/src/design/proposals/APIRedesign.md#add-gesture-handlers): Yes if we can agree on details. We need a platform implementation that is easy to use, but we should not reinvent the wheel. - - - -# [1] Footnote: The angular debacle - -## "Do or do not, there is no try" - -A commonly voiced concern of embracing angular is the possibility of becoming dependent on a third party framework. This concern is itself detrimental-- if we're afraid of becoming dependent on a third party framework, then we will do a bad job of using the framework, and inevitably will want to stop using it. - -If we're using a framework, we need to use it fully, or not use it at all. - -## A lack of commitment - -A number of the concerns we heard from developers and interns can be attributed to the tenuous relationship between the OpenMCTWeb platform and angular. We claimed to be angular, but we weren't really angular. Instead, we are caught between our incomplete framework paradigm and the angular paradigm. In many cases we reinvented the wheel or worked around functionality that angular provides, and ended up in a more confusing state. - -## Commitment is good! - -We could just be an application that is built with angular. - -An application that is modular and extensible not because it reinvents tools for providing modularity and extensibility, but because it reuses existing tools for modularity and extensibility. - -There are benefits to buying into the angular paradigm: shift documentation burden to external project, engage a larger talent pool available both as voluntary open source contributors and as experienced developers for hire, and gain access to an ecosystem of tools that we can use to increase the speed of development. - -There are negatives too: Angular is a monolith, it has performance concerns, and an unclear future. If we can't live with it, we should look at alternatives. - diff --git a/docs/src/design/proposals/ImperativePlugins.md b/docs/src/design/proposals/ImperativePlugins.md deleted file mode 100644 index 1fbb83b1bc..0000000000 --- a/docs/src/design/proposals/ImperativePlugins.md +++ /dev/null @@ -1,164 +0,0 @@ -# Imperative Plugins - -This is a design proposal for handling -[bundle declarations in JavaScript]( -APIRedesign.md#bundle-declarations-in-javascript). - -## Developer Use Cases - -Developers will want to use bundles/plugins to (in rough order -of occurrence): - -1. Add new extension instances. -2. Use existing services -3. Add new service implementations. -4. Decorate service implementations. -5. Decorate extension instances. -6. Add new types of services. -7. Add new extension categories. - -Notably, bullets 4 and 5 above are currently handled implicitly, -which has been cited as a source of confusion. - -## Interfaces - -Two base classes may be used to satisfy these use cases: - - * The `CompositeServiceFactory` provides composite service instances. - Decorators may be added; the approach used for compositing may be - modified; and individual services may be registered to support compositing. - * The `ExtensionRegistry` allows for the simpler case where what is desired - is an array of all instances of some kind of thing within the system. - -Note that additional developer use cases may be supported by using the -more general-purpose `Registry` - -```nomnoml -[Factory. - | - - factoryFn : function (V) : T - | - + decorate(decoratorFn : function (T, V) : T, options? : RegistrationOptions) -]-:>[function (V) : T] - -[RegistrationOptions | - + priority : number or string -] - -[Registry. - | - - compositorFn : function (Array.) : V - | - + register(item : T, options? : RegistrationOptions) - + composite(compositorFn : function (Array.) : V, options? : RegistrationOptions) -]-:>[Factory.] -[Factory.]-:>[Factory.] - -[ExtensionRegistry.]-:>[Registry.>] -[Registry.>]-:>[Registry.] - -[CompositeServiceFactory.]-:>[Registry.] -[Registry.]-:>[Registry.] -``` - -## Examples - -### 1. Add new extension instances. - -```js -// Instance-style registration -mct.types.register(new mct.Type({ - key: "timeline", - name: "Timeline", - description: "A container for activities ordered in time." -}); - -// Factory-style registration -mct.actions.register(function (domainObject) { - return new RemoveAction(domainObject); -}, { priority: 200 }); -``` - -### 2. Use existing services - -```js -mct.actions.register(function (domainObject) { - var dialogService = mct.ui.dialogServiceFactory(); - return new PropertiesAction(dialogService, domainObject); -}); -``` - -### 3. Add new service implementations - -```js -// Instance-style registration -mct.persistenceServiceFactory.register(new LocalPersistenceService()); - -// Factory-style registration -mct.persistenceServiceFactory.register(function () { - var $http = angular.injector(['ng']).get('$http'); - return new LocalPersistenceService($http); -}); -``` - -### 4. Decorate service implementations - -```js -mct.modelServiceFactory.decorate(function (modelService) { - return new CachingModelDecorator(modelService); -}, { priority: 100 }); -``` - -### 5. Decorate extension instances - -```js -mct.capabilities.decorate(function (capabilities) { - return capabilities.map(decorateIfApplicable); -}); -``` - -This use case is not well-supported by these API changes. The most -common case for decoration is capabilities, which are under reconsideration; -should consider handling decoration of capabilities in a different way. - -### 6. Add new types of services - -```js -myModule.myServiceFactory = new mct.CompositeServiceFactory(); - -// In cases where a custom composition strategy is desired -myModule.myServiceFactory.composite(function (services) { - return new MyServiceCompositor(services); -}); -``` - -### 7. Add new extension categories. - -```js -myModule.hamburgers = new mct.ExtensionRegistry(); -``` - -## Evaluation - -### Benefits - -* Encourages separation of registration from declaration (individual - components are decoupled from the manner in which they are added - to the architecture.) -* Minimizes "magic." Dependencies are acquired, managed, and exposed - using plain-old-JavaScript without any dependency injector present - to obfuscate what is happening. -* Offers comparable expressive power to existing APIs; can still - extend the behavior of platform components in a variety of ways. -* Does not force or limit formalisms to use; - -### Detriments - -* Does not encourage separation of dependency acquisition from - declaration; that is, it would be quite natural using this API - to acquire references to services during the constructor call - to an extension or service. But, passing these in as constructor - arguments is preferred (to separate implementation from architecture.) -* Adds (negligible?) boilerplate relative to declarative syntax. -* Relies on factories, increasing number of interfaces to be concerned - with. \ No newline at end of file diff --git a/docs/src/design/proposals/Roles.md b/docs/src/design/proposals/Roles.md deleted file mode 100644 index 6148d47566..0000000000 --- a/docs/src/design/proposals/Roles.md +++ /dev/null @@ -1,138 +0,0 @@ -# Roles - -Roles are presented as an alternative formulation to capabilities -(dynamic behavior associated with individual domain objects.) - -Specific goals here: - -* Dependencies of individual scripts should be clear. -* Domain objects should be able to selectively exhibit a wide - variety of behaviors. - -## Developer Use Cases - -1. Checking for the existence of behavior. -2. Using behavior. -3. Augmenting existing behaviors. -4. Overriding existing behaviors. -5. Adding new behaviors. - -## Overview of Proposed Solution - -Remove `getCapability` from domain objects; add roles as external -services which can be applied to domain objects. - -## Interfaces - -```nomnoml -[Factory. - | - - factoryFn : function (V) : T - | - + decorate(decoratorFn : function (T, V) : T, options? : RegistrationOptions) -]-:>[function (V) : T] - -[RegistrationOptions | - + priority : number or string -]<:-[RoleOptions | - + validate : function (DomainObject) : boolean -] - -[Role. | - + validate(domainObject : DomainObject) : boolean - + decorate(decoratorFn : function (T, V) : T, options? : RoleOptions) -]-:>[Factory.] -[Factory.]-:>[Factory.] -``` - -## Examples - -### 1. Checking for the existence of behavior - -```js -function PlotViewPolicy(telemetryRole) { - this.telemetryRole = telemetryRole; -} -PlotViewPolicy.prototype.allow = function (view, domainObject) { - return this.telemetryRole.validate(domainObject); -}; -``` - -### 2. Using behavior - -```js -PropertiesAction.prototype.perform = function () { - var mutation = this.mutationRole(this.domainObject); - return this.showDialog.then(function (newModel) { - return mutation.mutate(function () { - return newModel; - }); - }); -}; -``` - -### 3. Augmenting existing behaviors - -```js -// Non-Angular style -mct.roles.persistenceRole.decorate(function (persistence) { - return new DecoratedPersistence(persistence); -}); - -// Angular style -myModule.decorate('persistenceRole', ['$delegate', function ($delegate) { - return new DecoratedPersistence(persistence); -}]); -``` - -### 4. Overriding existing behaviors - -```js -// Non-Angular style -mct.roles.persistenceRole.decorate(function (persistence, domainObject) { - return domainObject.getModel().type === 'someType' ? - new DifferentPersistence(domainObject) : - persistence; -}, { - validate: function (domainObject, next) { - return domainObject.getModel().type === 'someType' || next(); - } -}); -``` - -### 5. Adding new behaviors - -```js -function FooRole() { - mct.Role.apply(this, [function (domainObject) { - return new Foo(domainObject); - }]); -} - -FooRole.prototype = Object.create(mct.Role.prototype); - -FooRole.prototype.validate = function (domainObject) { - return domainObject.getModel().type === 'some-type'; -}; - -// -myModule.roles.fooRole = new FooRole(); -``` - - -## Evaluation - -### Benefits - -* Simplifies/standardizes augmentation or replacement of behavior associated - with specific domain objects. -* Minimizes number of abstractions; roles are just factories. -* Clarifies dependencies; roles used must be declared/acquired in the - same manner as services. - -### Detriments - -* Externalizes functionality which is conceptually associated with a - domain object. -* Relies on factories, increasing number of interfaces to be concerned - with. \ No newline at end of file diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md deleted file mode 100644 index a74c55e016..0000000000 --- a/docs/src/guide/index.md +++ /dev/null @@ -1,2456 +0,0 @@ -# Open MCT Developer Guide -Victor Woeltjen - -[victor.woeltjen@nasa.gov](mailto:victor.woeltjen@nasa.gov) - -September 23, 2015 -Document Version 1.1 - -Date | Version | Summary of Changes | Author -------------------- | --------- | ------------------------- | --------------- -April 29, 2015 | 0 | Initial Draft | Victor Woeltjen -May 12, 2015 | 0.1 | | Victor Woeltjen -June 4, 2015 | 1.0 | Name Changes | Victor Woeltjen -October 4, 2015 | 1.1 | Conversion to MarkDown | Andrew Henry -April 5, 2016 | 1.2 | Added Mct-table directive | Andrew Henry - -# Introduction -The purpose of this guide is to familiarize software developers with the Open -MCT Web platform. - -## What is Open MCT -Open MCT is a platform for building user interface and display tools, -developed at the NASA Ames Research Center in collaboration with teams at the -Jet Propulsion Laboratory. It is written in HTML5, CSS3, and JavaScript, using -[AngularJS](http://www.angularjs.org) as a framework. Its intended use is to -create single-page web applications which integrate data and behavior from a -variety of sources and domains. - -Open MCT has been developed to support the remote operation of space -vehicles, so some of its features are specific to that task; however, it is -flexible enough to be adapted to a variety of other application domains where a -display tool oriented toward browsing, composing, and visualizing would be -useful. - -Open MCT provides: - -* A common user interface paradigm which can be applied to a variety of domains -and tasks. Open MCT is more than a widget toolkit - it provides a standard -tree-on-the-left, view-on-the-right browsing environment which you customize by -adding new browsable object types, visualizations, and back-end adapters. -* A plugin framework and an extensible API for introducing new application -features of a variety of types. -* A set of general-purpose object types and visualizations, as well as some -visualizations and infrastructure specific to telemetry display. - -## Client-Server Relationship -Open MCT is client software - it runs entirely in the user's web browser. As -such, it is largely 'server agnostic'; any web server capable of serving files -from paths is capable of providing Open MCT. - -While Open MCT can be configured to run as a standalone client, this is -rarely very useful. Instead, it is intended to be used as a display and -interaction layer for information obtained from a variety of back-end services. -Doing so requires authoring or utilizing adapter plugins which allow Open MCT -Web to interact with these services. - -Typically, the pattern here is to provide a known interface that Open MCT -can utilize, and implement it such that it interacts with whatever back-end -provides the relevant information. Examples of back-ends that can be utilized in -this fashion include databases for the persistence of user-created objects, or -sources of telemetry data. - -See the [Architecture Guide](../architecture/index.md#Overview) for information -on the client-server relationship. - -## Developing with Open MCT -Building applications with Open MCT typically means authoring and utilizing -a set of plugins which provide application-specific details about how Open MCT -Web should behave. - -### Technologies - -Open MCT sources are written in JavaScript, with a number of configuration -files written in JSON. Displayable components are written in HTML5 and CSS3. -Open MCT is built using [AngularJS](http://www.angularjs.org) from Google. A -good understanding of Angular is recommended for developers working with Open -MCT Web. - -### Forking -Open MCT does not currently have a single stand-alone artifact that can be -used as a library. Instead, the recommended approach for creating a new -application is to start by forking/branching Open MCT, and then adding new -features from there. Put another way, Open MCT's source structure is built -to serve as a template for specific applications. - -Forking in this manner should not require that you edit Open MCT's sources. -The preferred approach is to create a new directory (peer to `index.html`) for -the new application, then add new bundles (as described in the Framework -chapter) within that directory. - -To initially clone the Open MCT repository: -`git clone -b open-master` - -To create a fork to begin working on a new application using Open MCT: - - cd - git checkout open-master - git checkout -b - -As a convention used internally, applications built using Open MCT have -master branch names with an identifying prefix. For instance, if building an -application called 'Foo', the last statement above would look like: - - git checkout -b foo-master - -This convention is not enforced or understood by Open MCT in any way; it is -mentioned here as a more general recommendation. - -# Overview - -Open MCT is implemented as a framework component which manages a set of -other components. These components, called _bundles_, act as containers to group -sets of related functionality; individual units of functionality are expressed -within these bundles as _extensions_. - -Extensions declare dependencies on other extensions (either individually or -categorically), and the framework provides actual extension instances at -run-time to satisfy these declared dependency. This dependency injection -approach allows software components which have been authored separately (e.g. as -plugins) but to collaborate at run-time. - -Open MCT's framework layer is implemented on top of AngularJS's [dependency -injection mechanism](https://docs.angularjs.org/guide/di) and is modelled after -[OSGi](hhttp://www.osgi.org/) and its [Declarative Services component model](http://wiki.osgi.org/wiki/Declarative_Services). -In particular, this is where the term _bundle_ comes from. - -## Framework Overview - -The framework's role in the application is to manage connections between -bundles. All application-specific behavior is provided by individual bundles, or -as the result of their collaboration. - -The framework is described in more detail in the [Framework Overview](../architecture/framework.md#overview) of the -architecture guide. - -### Tiers -While all bundles in a running Open MCT instance are effectively peers, it -is useful to think of them as a tiered architecture, where each tier adds more -specificity to the application. -```nomnoml -#direction: down -[Plugins (Features external to OpenMCTWeb) *Bundle]->[OpenMCTWeb | -[Application (Plots, layouts, ElasticSearch wrapper) *Bundle]->[Platform (Core API, common UI, infrastructure) *Bundle] -[Platform (Core API, common UI, infrastructure) *Bundle]->[Framework (RequireJS, AngularJS, bundle loader)]] -``` - -* __Framework__ : This tier is responsible for wiring together the set of -configured components (called _bundles_) together to instantiate the running -application. It is responsible for mediating between AngularJS (in particular, -its dependency injection mechanism) and RequireJS (to load scripts at run-time.) -It additionally interprets bundle definitions (see explanation below, as well as -further detail in the Framework chapter.) At this tier, we are at our most -general: We know only that we are a plugin-based application. -* __Platform__: Components in the Platform tier describe both the general user -interface and corresponding developer-facing interfaces of Open MCT. This -tier provides the general infrastructure for applications. It is less general -than the framework tier, insofar as this tier introduces a specific user -interface paradigm, but it is still non-specific as to what useful features -will be provided. Although they can be removed or replaced easily, bundles -provided by the Platform tier generally should not be thought of as optional. -* __Application__: The application tier consists of components which utilize the -infrastructure provided by the Platform to provide functionality which will (or -could) be useful to specific applications built using Open MCT. These -include adapters to specific persistence back-ends (such as ElasticSearch or -CouchDB) as well as bundles which describe more user-facing features (such as -_Plot_ views for visualizing time series data, or _Layout_ objects for -display-building.) Bundles from this tier can be added or removed without -compromising basic application functionality, with the caveat that at least one -persistence adapter needs to be present. -* __Plugins__: Conceptually, this tier is not so different from the application -tier; it consists of bundles describing new features, back-end adapters, that -are specific to the application being built on Open MCT. It is described as -a separate tier here because it has one important distinction from the -application tier: It consists of bundles that are not included with the platform -(either authored anew for the specific application, or obtained from elsewhere.) - -Note that bundles in any tier can go off and consult back-end services. In -practice, this responsibility is handled at the Application and/or Plugin tiers; -Open MCT is built to be server-agnostic, so any back-end is considered an -application-specific detail. - -## Platform Overview - -The "tiered" architecture described in the preceding text describes a way of -thinking of and categorizing software components of a Open MCT application, -as well as the framework layer's role in mediating between these components. -Once the framework layer has wired these software components together, however, -the application's logical architecture emerges. - -An overview of the logical architecture of the platform is given in the -[Platform Architecture](../architecture/platform.md#platform-architecture) -section of the Platform guide - -### Web Services - -As mentioned in the Introduction, Open MCT is a platform single-page -applications which runs entirely in the browser. Most applications will want to -additionally interact with server-side resources, to (for example) read -telemetry data or store user-created objects. This interaction is handled by -individual bundles using APIs which are supported in browser (such as -`XMLHttpRequest`, typically wrapped by Angular's `$http`.) - -```nomnoml -#direction: right -[Web Service #1] <- [Web Browser] -[Web Service #2] <- [Web Browser] -[Web Service #3] <- [Web Browser] -[ Web Browser | - [ Open MCT | - [Plugin Bundle #1]-->[Core API] - [Core API]<--[Plugin Bundle #2] - [Platform Bundle #1]-->[Core API] - [Platform Bundle #2]-->[Core API] - [Platform Bundle #3]-->[Core API] - [Core API]<--[Platform Bundle #4] - [Core API]<--[Platform Bundle #5] - [Core API]<--[Plugin Bundle #3] - ] - [Open MCT] ->[Browser APIs] -] -``` - -This architectural approach ensures a loose coupling between applications built -using Open MCT and the backends which support them. - -### Glossary - -Certain terms are used throughout Open MCT with consistent meanings or -conventions. Other developer documentation, particularly in-line documentation, -may presume an understanding of these terms. - -* __bundle__: A bundle is a removable, reusable grouping of software elements. -The application is composed of bundles. Plug-ins are bundles. -* __capability__: A JavaScript object which exposes dynamic behavior or -non-persistent state associated with a domain object. -* __category__: A machine-readable identifier for a group that something may -belong to. -* __composition__: In the context of a domain object, this refers to the set of -other domain objects that compose or are contained by that object. A domain -object's composition is the set of domain objects that should appear immediately - beneath it in a tree hierarchy. A domain object's composition is described in -its model as an array of identifiers; its composition capability provides a -means to retrieve the actual domain object instances associated with these -identifiers asynchronously. -* __description__: When used as an object property, this refers to the human- -readable description of a thing; usually a single sentence or short paragraph. -(Most often used in the context of extensions, domain object models, or other -similar application-specific objects.) -* __domain object__: A meaningful object to the user; a distinct thing in the -work support by Open MCT. Anything that appears in the left-hand tree is a -domain object. -* __extension__: An extension is a unit of functionality exposed to the platform -in a declarative fashion by a bundle. The term 'extension category' is used to -distinguish types of extensions from specific extension instances. -* __id__: A string which uniquely identifies a domain object. -* __key__: When used as an object property, this refers to the machine-readable -identifier for a specific thing in a set of things. (Most often used in the -context of extensions or other similar application-specific object sets.) This -term is chosen to avoid attaching ambiguous meanings to 'id'. -* __model__: The persistent state associated with a domain object. A domain -object's model is a JavaScript object which can be converted to JSON without -losing information (that is, it contains no methods.) -* __name__: When used as an object property, this refers to the human-readable -name for a thing. (Most often used in the context of extensions, domain object -models, or other similar application-specific objects.) -* __navigation__: Refers to the current state of the application with respect to -the user's expressed interest in a specific domain object; e.g. when a user -clicks on a domain object in the tree, they are navigating to it, and it is -thereafter considered the navigated object (until the user makes another such -choice.) This term is used to distinguish navigation from selection, which -occurs in an editing context. -* __space__: A machine-readable name used to identify a persistence store. -Interactions with persistence with generally involve a space parameter in some -form, to distinguish multiple persistence stores from one another (for cases -where there are multiple valid persistence locations available.) -* __source__: A machine-readable name used to identify a source of telemetry -data. Similar to "space", this allows multiple telemetry sources to operate -side-by-side without conflicting. - -# Framework - -Open MCT is built on the [AngularJS framework]( http://www.angularjs.org ). A -good understanding of that framework is recommended. - -Open MCT adds an extra layer on top of AngularJS to (a) generalize its -dependency injection mechanism slightly, particularly to handle many-to-one -relationships; and (b) handle script loading. Combined, these features become a -plugin mechanism. - -This framework layer operates on two key concepts: - -* __Bundle:__ A bundle is a collection of related functionality that can be -added to the application as a group. More concretely, a bundle is a directory -containing a JSON file declaring its contents, as well as JavaScript sources, -HTML templates, and other resources used to support that functionality. (The -term bundle is borrowed from [OSGi](http://www.osgi.org/) - which has also -inspired many of the concepts used in the framework layer. A familiarity with -OSGi, particularly Declarative Services, may be useful when working with Open -MCT Web.) -* __Extension:__ An extension is an individual unit of functionality. Extensions -are collected together in bundles, and may interact with other extensions. - -The framework layer, loaded and initiated from `index.html`, is the main point -of entry for an application built on Open MCT. It is responsible for wiring -together the application at run time (much of this responsibility is actually -delegated to Angular); at a high-level, the framework does this by proceeding -through four stages: - -1. __Loading definitions:__ JSON declarations are loaded for all bundles which -will constitute the application, and wrapped in a useful API for subsequent -stages. -2. __Resolving extensions:__ Any scripts which provide implementations for -extensions exposed by bundles are loaded, using Require. -3. __Registering extensions__ Resolved extensions are registered with Angular, -such that they can be used by the application at run-time. This stage includes -both registration of Angular built-ins (directives, controllers, routes, -constants, and services) as well as registration of non-Angular extensions. -4. __Bootstrapping__ The Angular application is bootstrapped; at that point, -Angular takes over and populates the body of the page using the extensions that -have been registered. - -## Bundles - -The basic configurable unit of Open MCT is the _bundle_. This term has been -used a bit already; now we'll get to a more formal definition. - -A bundle is a directory which contains: - -* A bundle definition; a file named `bundle.json`. -* Subdirectories for sources, resources, and tests. -* Optionally, a `README.md` Markdown file describing its contents (this is not -used by Open MCT in any way, but it's a helpful convention to follow.) - -The bundle definition is the main point of entry for the bundle. The framework -looks at this to determine which components need to be loaded and how they -interact. - -A plugin in Open MCT is a bundle. The platform itself is also decomposed -into bundles, each of which provides some category of functionality. The -difference between a _bundle_ and a _plugin_ is purely a matter of the intended -use; a plugin is just a bundle that is meant to be easily added or removed. When -developing, it is typically more useful to think in terms of bundles. - -### Configuring Active Bundles - -To decide which bundles should be loaded, the framework loads a file named -`bundles.json` (peer to the `index.html` file which serves the application) to -determine which bundles should be loaded. This file should contain a single JSON -array of strings, where each is the path to a bundle. These paths should not -include bundle.json (this is implicit) or a trailing slash. - -For instance, if `bundles.json` contained: - - [ - "example/builtins", - "example/extensions" - ] - -...then the Open MCT framework would look for bundle definitions at -`example/builtins/bundle.json` and `example/extensions/bundle.json`, relative -to the path of `index.html`. No other bundles would be loaded. - -### Bundle Definition - -A bundle definition (the `bundle.json` file located within a bundle) contains a -description of the bundle itself, as well as the information exposed by the -bundle. - -This definition is expressed as a single JSON object with the following -properties (all of which are optional, falling back to reasonable defaults): - -* `key`: A machine-readable name for the bundle. (Currently used only in -logging.) -* `name`: A human-readable name for the bundle. (Also only used in logging.) -* `sources`: Names a directory in which source scripts (which will implement -extensions) are located. Defaults to 'src' -* `resources`: Names a directory in which resource files (such as HTML templates, -images, CS files, and other non-JavaScript files needed by this bundle) are -located. Defaults to 'res' -* `libraries`: Names a directory in which third-party libraries are located. -Defaults to 'lib' -* `configuration`: A bundle's configuration object, which should be formatted as -would be passed to require.config (see [RequireJS documentation](http://requirejs.org/docs/api.html ) ); -note that only paths and shim have been tested. -* `extensions`: An object containing key-value pairs, where keys are extension -categories, and values are extension definitions. See the section on Extensions -for more information. - -For example, the bundle definition for example/policy looks like: - - { - "name": "Example Policy", - "description": "Provides an example of using policies.", - "sources": "src", - "extensions": { - "policies": [ - { - "implementation": "ExamplePolicy.js", - "category": "action" - } - ] - } - } - -### Bundle Directory Structure - -In addition to the directories defined in the bundle definition, a bundle will -typically contain other directories not used at run-time. Additionally, some -useful development scripts (such as the command line build and the test suite) -expect this directory structure to be in use, and may ignore options chosen by -`bundle.json`. It is recommended that the directory structure described below be -used for new bundles. - -* `src`: Contains JavaScript sources for this bundle. May contain additional -subdirectories to organize these sources; typically, these subdirectories are -named to correspond to the extension categories they contain and/or support, but -this is only a convention. -* `res`: Contains other files needed by this bundle, such as HTML templates. May -contain additional subdirectories to organize these sources. -* `lib`: Contains JavaScript sources from third-party libraries. These are -separated from bundle sources in order to ignore them during code style checking -from the command line build. -* `test`: Contains JavaScript sources implementing [Jasmine](http://jasmine.github.io/) -tests, as well as a file named `suite.json` describing which files to test. -Should have the same folder structure as the `src` directory; see the section on -automated testing for more information. - -For example, the directory structure for bundle `platform/commonUI/dialog` looks -like: - - Platform - | - |-commonUI - | - +-dialog - | - |-res - | - |-src - | - |-test - | - |-bundle.json - | - +-README.md - -## Extensions - -While bundles provide groupings of related behaviors, the individual units of -behavior are called extensions. - -Extensions belong to categories; an extension category is the machine-readable -identifier used to identify groups of extensions. In the `extensions` property -of a bundle definition, the keys are extension categories and the values are -arrays of extension definitions. - -### General Extensions - -Extensions are intended as a general-purpose mechanism for adding new types of -functionality to Open MCT. - -An extension category is registered with Angular under the name of the -extension, plus a suffix of two square brackets; so, an Angular service (or, -generally, any other extension) can access the full set of registered -extensions, from all bundles, by including this string (e.g. `types[]` to get -all type definitions) in a dependency declaration. - -As a convention, extension categories are given single-word, plural nouns for -names within Open MCT (e.g. `types`.) This convention is not enforced by the -platform in any way. For extension categories introduced by external plugins, it -is recommended to prefix the extension category with a vendor identifier (or -similar) followed by a dot, to avoid collisions. - -### Extension Definitions - -The properties used in extension definitions are typically unique to each -category of extension; a few properties have standard interpretations by the -platform. - -* `implementation`: Identifies a JavaScript source file (in the sources -folder) which implements this extension. This JavaScript file is expected to -contain an AMD module (see http://requirejs.org/docs/whyamd.html#amd ) which -gives as its result a single constructor function. -* `depends`: An array of dependencies needed by this extension; these will be -passed on to Angular's [dependency injector](https://docs.angularjs.org/guide/di ) . -By default, this is treated as an empty array. Note that depends does not make -sense without `implementation` (since these dependencies will be passed to the -implementation when it is instantiated.) -* `priority`: A number or string indicating the priority order (see below) of -this extension instance. Before an extension category is registered with -AngularJS, the extensions of this category from all bundles will be concatenated -into a single array, and then sorted by priority. - -Extensions do not need to have an implementation. If no implementation is -provided, consumers of the extension category will receive the extension -definition as a plain JavaScript object. Otherwise, they will receive the -partialized (see below) constructor for that implementation, which will -additionally have all properties from the extension definition attached. - -#### Partial Construction - -In general, extensions are intended to be implemented as constructor functions, -which will be used elsewhere to instantiate new objects of that type. However, -the Angular-supported method for dependency injection is (effectively) -constructor-style injection; so, both declared dependencies and run-time -arguments are competing for space in a constructor's arguments. - -To resolve this, the Open MCT framework registers extension instances in a -partially constructed form. That is, the constructor exposed by the extension's -implementation is effectively decomposed into two calls; the first takes the -dependencies, and returns the constructor in its second form, which takes the -remaining arguments. - -This means that, when writing implementations, the constructor function should -be written to include all declared dependencies, followed by all run-time -arguments. When using extensions, only the run-time arguments need to be -provided. - -#### Priority - -Within each extension category, registration occurs in priority order. An -extension's priority may be specified as a `priority` property in its extension -definition; this may be a number, or a symbolic string. Extensions are -registered in reverse order (highest-priority first), and symbolic strings are -mapped to the numeric values as follows: - -* `fallback`: Negative infinity. Used for extensions that are not intended for -use (that is, they are meant to be overridden) but are present as an option of -last resort. -* `default`: `-100`. Used for extensions that are expected to be overridden, but -need a useful default. -* `none`: `0`. Also used if no priority is specified, or if an unknown or -malformed priority is specified. -* `optional`: `100`. Used for extensions that are meant to be used, but may be -overridden. -* `preferred`: `1000`. Used for extensions that are specifically intended to be -used, but still may be overridden in principle. -* `mandatory`: Positive infinity. Used when an extension should definitely not -be overridden. - -These symbolic names are chosen to support usage where many extensions may -satisfy a given need, but only one may be used; in this case, as a convention it -should be the lowest-ordered (highest-priority) extensions available. In other -cases, a full set (or multi-element subset) of extensions may be desired, with a -specific ordering; in these cases, it is preferable to specify priority -numerically when declaring extensions, and to understand that extensions will be -sorted according to these conventions when using them. - -### Angular Built-ins - -Several entities supported Angular are expressed and managed as extensions in -Open MCT. Specifically, these extension categories are _directives_, -_controllers_, _services_, _constants_, _runs_, and _routes_. - -#### Angular Directives - -New [directives]( https://docs.angularjs.org/guide/directive ) may be -registered as extensions of the directives category. Implementations of -directives in this category should take only dependencies as arguments, and -should return a directive definition object. - -The directive's name should be provided as a key property of its extension -definition, in camel-case format. - -#### Angular Controllers - -New [controllers]( https://docs.angularjs.org/guide/controller ) may be registered -as extensions of the controllers category. The implementation is registered -directly as the controller; its only constructor arguments are its declared -dependencies. - -The directive's identifier should be provided as a key property of its extension -definition. - - -#### Angular Services - -New [services](https://docs.angularjs.org/guide/services ) may be registered as -extensions of the services category. The implementation is registered via a -[service call]( https://docs.angularjs.org/api/auto/service/$provide#service ), so -it will be instantiated with the new operator. - -#### Angular Constants - -Constant values may be registered as extensions of the [ constants category](https://docs.angularjs.org/api/ng/type/angular.Module#constant ). -These extensions have no implementation; instead, they should contain a property - key , which is the name under which the constant will be registered, and a -property value , which is the constant value that will be registered. - -#### Angular Runs - -In some cases, you want to register code to run as soon as the application -starts; these can be registered as extensions of the [ runs category](https://docs.angularjs.org/api/ng/type/angular.Module#run ). -Implementations registered in this category will be invoked (with their declared -dependencies) when the Open MCT application first starts. (Note that, in -this case, the implementation is better thought of as just a function, as -opposed to a constructor function.) - -#### Angular Routes - -Extensions of category `routes` will be registered with Angular's [route provider](https://docs.angularjs.org/api/ngRoute/provider/$routeProvider ). -Extensions of this category have no implementations, and need only two -properties in their definition: - -* `when`: The value that will be passed as the path argument to `$routeProvider.when`; -specifically, the string that will appear in the trailing -part of the URL corresponding to this route. This property may be omitted, in -which case this extension instance will be treated as the default route. -* `templateUrl`: A path to the template to render for this route. Specified as a -path relative to the bundle's resource directory (`res` by default.) - -### Composite Services - -Composite services are described in the [Composite Services](../architecture/framework.md#composite-services) -section of the framework guide. - -A component should include the following properties in its extension definition: - -* `provides`: The symbolic identifier for the service that will be composed. The - fully-composed service will be registered with Angular under this name. -* `type`: One of `provider`, `aggregator` or `decorator` (as above) - -In addition to any declared dependencies, _aggregators_ and _decorators_ both -receive one more argument (immediately following declared dependencies) that is -provided by the framework. For an aggregator, this will be an array of all -providers of the same service (that is, with matching `provides` properties); -for a decorator, this will be whichever provider, decorator, or aggregator is -next in the sequence of decorators. - -Services exposed by the Open MCT platform are often declared as composite -services, as this form is open for a variety of common modifications. - -# Core API - -Most of Open MCT's relevant API is provided and/or mediated by the -framework; that is, much of developing for Open MCT is a matter of adding -extensions which access other parts of the platform by means of dependency -injection. - -The core bundle (`platform/core`) introduces a few additional object types meant -to be passed along by other services. - -## Domain Objects - -Domain objects are the most fundamental component of Open MCT's information -model. A domain object is some distinct thing relevant to a user's workflow, -such as a telemetry channel, display, or similar. Open MCT is a tool for -viewing, browsing, manipulating, and otherwise interacting with a graph of -domain objects. - -A domain object should be conceived of as the union of the following: - -* __Identifier__: A machine-readable string that uniquely identifies the domain -object within this application instance. -* __Model__: The persistent state of the domain object. A domain object's model -is a JavaScript object that can be losslessly converted to JSON. -* __Capabilities__: Dynamic behavior associated with the domain object. -Capabilities are JavaScript objects which provide additional methods for -interacting with the domain objects which expose those capabilities. Not all -domain objects expose all capabilities. - -At run-time, a domain object has the following interface: - -* `getId()`: Get the identifier for this domain object. -* `getModel()`: Get the plain state associated with this domain object. This -will return a JavaScript object that can be losslessly converted to JSON. Note -that the model returned here can be modified directly but should not be; -instead, use the mutation capability. -* `getCapability(key)`: Get the specified capability associated with this domain -object. This will return a JavaScript object whose interface is specific to the -type of capability being requested. If the requested capability is not exposed -by this domain object, this will return undefined . -* `hasCapability(key)`: Shorthand for checking if a domain object exposes the -requested capability. -* `useCapability(key, arguments )`: Shorthand for -`getCapability(key).invoke(arguments)`, with additional checking between calls. -If the provided capability has no invoke method, the return value here functions -as `getCapability` including returning `undefined` if the capability is not -exposed. - -### Identifier Syntax - -For most purposes, a domain object identifier can be treated as a purely -symbolic string; these are typically generated by Open MCT and plug-ins -should rarely be concerned with its internal structure. - -A domain object identifier has one or two parts, separated by a colon. - -* If two parts are present, the part before the colon refers to the space - in which the domain object resides. This may be a persistence space or - a purely symbolic space recognized by a specific model provider. The - part after the colon is the key to use when looking up the domain object - model within that space. -* If only one part is present, the domain object has no space specified, - and may presume to reside in the application-configured default space - defined by the `PERSISTENCE_SPACE` constant. -* Both the key and the space identifier may consist of any combination - of alphanumeric characters, underscores, dashes, and periods. - -Some examples: - -* A domain object with the identifier `foo:xyz` would have its model - loaded using key `xyz` from persistence space `foo`. -* A domain object with the identifier `bar` would have its model loaded - using key `bar` from the space identified by the `PERSISTENCE_SPACE` - constant. - -```bnf - ::= ":" | - ::= + - ::= + - ::= | | "-" | "." | "_" -``` - -## Domain Object Actions - -An `Action` is behavior that can be performed upon/using a `DomainObject`. An -Action has the following interface: - -* `perform()`: Do this action. For example, if one had an instance of a -`RemoveAction` invoking its perform method would cause the domain object which -exposed it to be removed from its container. -* `getMetadata()`: Get metadata associated with this action. Returns an object -containing: - * `name`: Human-readable name. - * `description`: Human-readable summary of this action. - * `glyph`: Single character to be displayed in Open MCT's icon font set. - * `context`: The context in which this action is being performed (see below) - -Action instances are typically obtained via a domain object's `action` -capability. - -### Action Contexts - -An action context is a JavaScript object with the following properties: - -* `domainObject`: The domain object being acted upon. -* `selectedObject`: Optional; the selection at the time of action (e.g. the -dragged object in a drag-and-drop operation.) - -## Telemetry - -Telemetry series data in Open MCT is represented by a common interface, and -packaged in a consistent manner to facilitate passing telemetry updates around -multiple visualizations. - -### Telemetry Requests - -A telemetry request is a JavaScript object containing the following properties: - -* `source`: A machine-readable identifier for the source of this telemetry. This -is useful when multiple distinct data sources are in use side-by-side. -* `key`: A machine-readable identifier for a unique series of telemetry within -that source. -* _Note: This API is still under development; additional properties, such as -start and end time, should be present in future versions of Open MCT._ - -Additional properties may be included in telemetry requests which have specific -interpretations for specific sources. - -### Telemetry Responses - -When returned from the `telemetryService` (see [Telemetry Services](#telemetry-service) -section), telemetry series data will be packaged in a `source -> key -> TelemetrySeries` -fashion. That is, telemetry is passed in an object containing key-value pairs. -Keys identify telemetry sources; values are objects containing additional -key-value pairs. In this object, keys identify individual telemetry series (and -match they `key` property from corresponding requests) and values are -`TelemetrySeries` objects (see below.) - -### Telemetry Series - -A telemetry series is a specific sequence of data, typically associated with a -specific instrument. Telemetry is modeled as an ordered sequence of domain and -range values, where domain values must be non-decreasing but range values do -not. (Typically, domain values are interpreted as UTC timestamps in milliseconds -relative to the UNIX epoch.) A series must have at least one domain and one -range, and may have more than one. - -Telemetry series data in Open MCT is expressed via the following -`TelemetrySeries` interface: - -* `getPointCount()`: Returns the number of unique points/samples in this series. -* `getDomainValue(index, [domain])`: Get the domain value at the specified index . -If a second domain argument is provided, this is taken as a string identifier -indicating which domain option (of, presumably, multiple) should be returned. -* `getRangeValue(index, [range])`: Get the domain value at the specified index . -If a second range argument is provided, this is taken as a string identifier -indicating which range option (of, presumably, multiple) should be returned. - -### Telemetry Metadata - -Domain objects which have associated telemetry also expose metadata about that -telemetry; this is retrievable via the `getMetadata()` of the telemetry -capability. This will return a single JavaScript object containing the following -properties: - -* `source`: The machine-readable identifier for the source of telemetry data for -this object. -* `key`: The machine-readable identifier for the individual telemetry series. -* `domains`: An array of supported domains (see TelemetrySeries above.) Each -domain should be expressed as an object which includes: - * `key`: Machine-readable identifier for this domain, as will be passed into - a getDomainValue(index, domain) call. - * `name`: Human-readable name for this domain. -* `ranges`: An array of supported ranges; same format as domains . - -Note that this metadata is also used as the prototype for telemetry requests -made using this capability. - -## Types -A domain object's type is represented as a Type object, which has the following -interface: - -* `getKey()`: Get the machine-readable identifier for this type. -* `getName()`: Get the human-readable name for this type. -* `getDescription()`: Get a human-readable summary of this type. -* `getGlyph()`: Get the single character to be rendered as an icon for this type -in Open MCT's custom font set. -* `getInitialModel()`: Get a domain object model that represents the initial -state (before user specification of properties) for domain objects of this type. -* `getDefinition()`: Get the extension definition for this type, as a JavaScript -object. -* `instanceOf(type)`: Check if this type is (or inherits from) a specified type . -This type can be either a string, in which case it is taken to be that type's - key , or it may be a `Type` instance. -* `hasFeature(feature)`: Returns a boolean value indicating whether or not this -type supports the specified feature, which is a symbolic string. -* `getProperties()`: Get all properties associated with this type, expressed as -an array of `TypeProperty` instances. - -### Type Features - -Features of a domain object type are expressed as symbolic string identifiers. -They are defined in practice by usage; currently, the Open MCT platform only -uses the creation feature to determine which domain object types should appear -in the Create menu. - -### Type Properties - -Types declare the user-editable properties of their domain object instances in -order to allow the forms which appear in the __Create__ and __Edit Properties__ -dialogs to be generated by the platform. A `TypeProperty` has the following interface: - -* `getValue(model)`: Get the current value for this property, as it appears in -the provided domain object model. -* `setValue(model, value)`: Set a new value for this property in the provided -domain object model . -* `getDefinition()`: Get the raw definition for this property as a JavaScript -object (as it was declared in this type's extension definition.) - -# Extension Categories - -The information in this section is focused on registering new extensions of -specific types; it does not contain a catalog of the extension instances of -these categories provided by the platform. Relevant summaries there are provided -in subsequent sections. - -## Actions Category - -An action is a thing that can be done to or using a domain object, typically as -initiated by the user. - -An action's implementation: - -* Should take a single `context` argument in its constructor. (See Action -Contexts, under Core API.) -* Should provide a method `perform` which causes the behavior associated with -the action to occur. -* May provide a method `getMetadata` which provides metadata associated with -the action. If omitted, one will be provided by the platform which includes -metadata from the action's extension definition. -* May provide a static method `appliesTo(context)` (that is, a function -available as a property of the implementation's constructor itself), which will -be used by the platform to filter out actions from contexts in which they are -inherently inapplicable. - -An action's bundle definition (and/or `getMetadata()` return value) may include: - -* `category`: A string or array of strings identifying which category or -categories an action falls into; used to determine when an action is displayed. -Categories supported by the platform include: - * `contextual`: Actions in a context menu. - * `view-control`: Actions triggered by buttons in the top-right of Browse - view. -* `key`: A machine-readable identifier for this action. -* `name`: A human-readable name for this action (e.g. to show in a menu) -* `description`: A human-readable summary of the behavior of this action. -* `glyph`: A single character which will be rendered in Open MCT's custom -font set as an icon for this action. - -## Capabilities Category - -Capabilities are exposed by domain objects (e.g. via the `getCapability` method) -but most commonly originate as extensions of this category. - -Extension definitions for capabilities should include both an implementation, -and a property named key whose value should be a string used as a -machine-readable identifier for that capability, e.g. when passed as the -argument to a domain object's `getCapability(key)` call. - -A capability's implementation should have methods specific to that capability; -that is, there is no common format for capability implementations, aside from -support for invocation via the `useCapability` shorthand. - -A capability's implementation will take a single argument (in addition to any -declared dependencies), which is the domain object that will expose that -capability. - -A capability's implementation may also expose a static method `appliesTo(model)` -which should return a boolean value, and will be used by the platform to filter -down capabilities to those which should be exposed by specific domain objects, -based on their domain object models. - -## Containers Category - -Containers provide options for the `mct-container` directive. - -The definition for an extension in the `containers` category should include: - -* `key`: An identifier for the container. -* `template`: An Angular template for the container, including an - `ng-transclude` where contained content should go. -* `attributes`: An array of attribute names. The values associated with - these attributes will be exposed in the template's scope under the - name provided by the `alias` property. -* `alias`: The property name in scope under which attributes will be - exposed. Optional; defaults to "container". - -Note that `templateUrl` is not supported for `containers`. - -## Controls Category - -Controls provide options for the `mct-control` directive. - -These standard control types are included in the forms bundle: - -* `textfield`: A text input to enter plain text. -* `numberfield`: A text input to enter numbers. -* `select`: A drop-down list of options. -* `checkbox`: A box which may be checked/unchecked. -* `color`: A color picker. -* `button`: A button. -* `datetime`: An input for UTC date/time entry; gives result as a UNIX -timestamp, in milliseconds since start of 1970, UTC. -* `composite`: A control parenting an array of other controls. -* `menu-button`: A drop-down list of items supporting custom behavior -on click. -* `dialog-button`: A button which opens a dialog allowing a single property -to be edited. -* `radio`: A radio button. - -New controls may be added as extensions of the controls category. Extensions of -this category have two properties: - -* `key`: The symbolic name for this control (matched against the control field -in rows of the form structure). -* `templateUrl`: The URL to the control's Angular template, relative to the -resources directory of the bundle which exposes the extension. - -Within the template for a control, the following variables will be included in -scope: - -* `ngModel`: The model where form input will be stored. Notably we also need to -look at field (see below) to determine which field in the model should be -modified. -* `ngRequired`: True if input is required. -* `ngPattern`: The pattern to match against (for text entry) -* `ngBlur`: A function that may be invoked to evaluate the expression - associated with the `ng-blur` attribute associated with the control. - * This should be called when the control has lost focus; for controls - which simply wrap or augment `input` elements, this should be fired - on `blur` events associated with those elements, while more complex - custom controls may fire this at the end of more specific interactions. -* `options`: The options for this control, as passed from the `options` property -of an individual row definition. -* `field`: Name of the field in `ngModel` which will hold the value for this -control. - -## Gestures Category - -A _gesture_ is a user action which can be taken upon a representation of a -domain object. - -Examples of gestures included in the platform are: - -* `drag`: For representations that can be used to initiate drag-and-drop -composition. -* `drop`: For representations that can be drop targets for drag-and-drop -composition. -* `menu`: For representations that can be used to popup a context menu. - -Gesture definitions have a property `key` which is used as a machine-readable -identifier for the gesture (e.g. `drag`, `drop`, `menu` above.) - -A gesture's implementation is instantiated once per representation that uses the -gesture. This class will receive the jqLite-wrapped `mct-representation` element -and the domain object being represented as arguments, and should do any -necessary "wiring" (e.g. listening for events) during its constructor call. The -gesture's implementation may also expose an optional `destroy()` method which -will be called when the gesture should be removed, to avoid memory leaks by way -of unremoved listeners. - -## Indicators Category - -An indicator is an element that should appear in the status area at the bottom -of a running Open MCT client instance. - -### Standard Indicators - -Indicators which wish to appear in the common form of an icon-text pair should -provide implementations with the following methods: - -* `getText()`: Provides the human-readable text that will be displayed for this -indicator. -* `getGlyph()`: Provides a single-character string that will be displayed as an -icon in Open MCT's custom font set. -* `getDescription()`: Provides a human-readable summary of the current state of -this indicator; will be displayed in a tooltip on hover. -* `getClass()`: Get a CSS class that will be applied to this indicator. -* `getTextClass()`: Get a CSS class that will be applied to this indicator's -text portion. -* `getGlyphClass()`: Get a CSS class that will be applied to this indicator's -icon portion. -* `configure()`: If present, a configuration icon will appear to the right of -this indicator, and clicking it will invoke this method. - -Note that all methods are optional, and are called directly from an Angular -template, so they should be appropriate to run during digest cycles. - -### Custom Indicators - -Indicators which wish to have an arbitrary appearance (instead of following the -icon-text convention commonly used) may specify a `template` property in their -extension definition. The value of this property will be used as the `key` for -an `mct-include` directive (so should refer to an extension of category - templates .) This template will be rendered to the status area. Indicators of -this variety do not need to provide an implementation. - -## Licenses Category - -The extension category `licenses` can be used to add entries into the 'Licensing -information' page, reachable from Open MCT's About dialog. - -Licenses may have the following properties, all of which are strings: - -* `name`: Human-readable name of the licensed component. (e.g. 'AngularJS'.) -* `version`: Human-readable version of the licensed component. (e.g. '1.2.26'.) -* `description`: Human-readable summary of the component. -* `author`: Name or names of entities to which authorship should be attributed. -* `copyright`: Copyright text to display for this component. -* `link`: URL to full license text. - -## Policies Category - -Policies are used to handle decisions made using Open MCT's `policyService`; -examples of these decisions are determining the applicability of certain -actions, or checking whether or not a domain object of one type can contain a -domain object of a different type. See the section on the Policies for an -overview of Open MCT's policy model. - -A policy's extension definition should include: - -* `category`: The machine-readable identifier for the type of policy decision -being supported here. For a list of categories supported by the platform, see -the section on Policies. Plugins may introduce and utilize additional policy -categories not in that list. -* `message`: Optional; a human-readable message describing the policy, intended -for display in situations where this specific policy has disallowed something. - -A policy's implementation should include a single method, `allow(candidate, -context)`. The specific types used for `candidate` and `context` vary by policy -category; in general, what is being asked is 'is this candidate allowed in this -context?' This method should return a boolean value. - -Open MCT's policy model requires consensus; a policy decision is allowed -when and only when all policies choose to allow it. As such, policies should -generally be written to reject a certain case, and allow (by returning `true`) -anything else. - -## Representations Category - -A representation is an Angular template used to display a domain object. The -`representations` extension category is used to add options for the -`mct-representation` directive. - -A representation definition should include the following properties: - -* `key`: The machine-readable name which identifies the representation. -* `templateUrl`: The path to the representation's Angular template. This path is -relative to the bundle's resources directory. -* `uses`: Optional; an array of capability names. Indicates that this -representation intends to use those capabilities of a domain object (via a -`useCapability` call), and expects to find the latest results of that -`useCapability` call in the scope of the presented template (under the same name -as the capability itself.) Note that, if `useCapability` returns a promise, this -will be resolved before being placed in the representation's scope. -* `gestures`: An array of keys identifying gestures (see the `gestures` -extension category) which should be available upon this representation. Examples -of gestures include `drag` (for representations that should act as draggable -sources for drag-drop operations) and `menu` (for representations which should -show a domain-object-specific context menu on right-click.) - -### Representation Scope - -While _representations_ do not have implementations, per se, they do refer to -Angular templates which need to interact with information (e.g. the domain -object being represented) provided by the platform. This information is passed -in through the template's scope, such that simple representations may be created -by providing only templates. (More complex representations will need controllers -which are referenced from templates. See https://docs.angularjs.org/guide/controller -for more information on controllers in Angular.) - -A representation's scope will contain: - -* `domainObject`: The represented domain object. -* `model`: The domain object's model. -* `configuration`: An object containing configuration information for this -representation (an empty object if there is no saved configuration.) The -contents of this object are managed entirely by the view/representation which -receives it. -* `representation`: An empty object, useful as a 'scratch pad' for -representation state. -* `ngModel`: An object passed through the ng-model attribute of the -`mct-representation` , if any. -* `parameters`: An object passed through the parameters attribute of the -`mct-representation`, if any. -* Any capabilities requested by the uses property of the representation -definition. - -## Representers Category - -The `representers` extension category is used to add additional behavior to the -`mct-representation` directive. This extension category is intended primarily -for use internal to the platform. - -Unlike _representations_, which describe specific ways to represent domain -objects, _representers_ are used to modify or augment the process of -representing domain objects in general. For example, support for the _gestures_ -extension category is added by a _representer_. - -A representer needs only provide an implementation. When an `mct-representation` -is linked (see https://docs.angularjs.org/guide/directive ) or when the -domain object being represented changes, a new _representer_ of each declared -type is instantiated. The constructor arguments for a _representer_ are the same -as the arguments to the link function in an Angular directive: `scope` the -Angular scope for this representation; `element` the jqLite-wrapped -`mct-representation` element, and `attrs` a set of key-value pairs of that -element's attributes. _Representers_ may wish to populate the scope, attach -event listeners to the element, etc. - -This implementation must provide a single method, `destroy()`, which will be -invoked when the representer is no longer needed. - -## Roots Category - -The extension category `roots` is used to provide root-level domain object -models. Root-level domain objects appear at the top-level of the tree hierarchy. -For example, the _My Items_ folder is added as an extension of this category. - -Extensions of this category should have the following properties: - -* `id`: The machine-readable identifier for the domain object being exposed. -* `model`: The model, as a JSON object, for the domain object being exposed. - -## Stylesheets Category - -The stylesheets extension category is used to add CSS files to style the -application. Extension definitions for this category should include one -property: - -* `stylesheetUrl`: Path and filename, including extension, for the stylesheet to -include. This path is relative to the bundle's resources folder (by default, -`res`) -* `theme`: Optional; if present, this stylesheet will only be included if this -value matches the `THEME` constant. - -To control the order of CSS files, use priority (see the section on Extension -Definitions above.) - -## Templates Category - -The `templates` extension category is used to expose Angular templates under -symbolic identifiers. These can then be utilized using the `mct-include` -directive, which behaves similarly to `ng-include` except that it uses these -symbolic identifiers instead of paths. - -A template's extension definition should include the following properties: - -* `key`: The machine-readable name which identifies this template, matched -against the value given to the key attribute of the `mct-include` directive. -* `templateUrl`: The path to the relevant Angular template. This path is -relative to the bundle's resources directory. - -Note that, when multiple templates are present with the same key , the one with -the highest priority will be used from `mct-include`. This behavior can be used -to override templates exposed by the platform (to change the logo which appears -in the bottom right, for instance.) - -Templates do not have implementations. - -## Types Category - -The types extension category describes types of domain objects which may -appear within Open MCT. - -A type's extension definition should have the following properties: - -* `key`: The machine-readable identifier for this domain object type. Will be -stored to and matched against the type property of domain object models. -* `name`: The human-readable name for this domain object type. -* `description`: A human-readable summary of this domain object type. -* `glyph`: A single character to be rendered as an icon in Open MCT's custom -font set. -* `model`: A domain object model, used as the initial state for created domain -objects of this type (before any properties are specified.) -* `features`: Optional; an array of strings describing features of this domain -object type. Currently, only creation is recognized by the platform; this is -used to determine that this type should appear in the Create menu. More -generally, this is used to support the `hasFeature(...)` method of the type -capability. -* `properties`: An array describing individual properties of this domain object -(as should appear in the _Create_ or the _Edit Properties_ dialog.) Each -property is described by an object containing the following properties: - * `control`: The key of the control (see `mct-control` and the `controls` - [extension category](#controls-category)) to use for editing this property. - * `property`: A string which will be used as the name of the property in the - domain object's model that the value for this property should be stored - under. If this value should be stored in an object nested within the domain - object model, then property should be specified as an array of strings - identifying these nested objects and, finally, the property itself. - * other properties as appropriate for a control of this type (each - property's definition will also be passed in as the structure for its - control.) See documentation of mct-form for more detail on these - properties. - -Types do not have implementations. - -## Versions Category -The versions extension category is used to introduce line items in Open MCT -Web's About dialog. These should have the following properties: - -* `name`: The name of this line item, as should appear in the left-hand side of -the list of version information in the About dialog. -* `value`: The value which should appear to the right of the name in the About -dialog. - -To control the ordering of line items within the About dialog, use `priority`. -(See section on [Extensions](#extensions) above.) - -This extension category does not have implementations. - -## Views Category - -The views extension category is used to determine which options appear to the -user as available views of domain objects of specific types. A view's extension -definition has the same properties as a representation (and views can be -utilized via `mct-representation`); additionally: - -* `name`: The human-readable name for this view type. -* description : A human-readable summary of this view type. -* `glyph`: A single character to be rendered as an icon in Open MCT's custom -font set. -* `type`: Optional; if present, this representation is only applicable for -domain object's of this type. -* `needs`: Optional array of strings; if present, this representation is only -applicable for domain objects which have the capabilities identified by these -strings. -* `delegation`: Optional boolean, intended to be used in conjunction with -`needs`; if present, allow required capabilities to be satisfied by means of -capability delegation. (See [Delegation](#delegation-capability)) -* `toolbar`: Optional; a definition for the toolbar which may appear in a -toolbar when using this view in Edit mode. This should be specified as a -structure for mct-toolbar , with additional properties available for each item in -that toolbar: - * `property`: A property name. This will refer to a property in the view's - current selection; that property on the selected object will be modifiable - as the `ng-model` of the displayed control in the toolbar. If the value of - the property is a function, it will be used as a getter-setter (called with - no arguments to use as a getter, called with a value to use as a setter.) - * `method`: A method to invoke (again, on the selected object) from the - toolbar control. Useful particularly for buttons (which don't edit a single - property, necessarily.) - -### View Scope - -Views do not have implementations, but do get the same properties in scope that -are provided for `representations`. - -When a view is in Edit mode, this scope will additionally contain: - -* `commit()`: A function which can be invoked to mark any changes to the view's - configuration as ready to persist. -* `selection`: An object representing the current selection state. - -#### Selection State - -A view's selection state is, conceptually, a set of JavaScript objects. The -presence of methods/properties on these objects determine which toolbar controls -are visible, and what state they manage and/or behavior they invoke. - -This set may contain up to two different objects: The _view proxy_, which is -used to make changes to the view as a whole, and the _selected object_, which is -used to represent some state within the view. (Future versions of Open MCT -may support multiple selected objects.) - -The `selection` object made available during Edit mode has the following -methods: - -* `proxy([object])`: Get (or set, if called with an argument) the current view -proxy. -* `select(object)`: Make this object the selected object. -* `deselect()`: Clear the currently selected object. -* `get()`: Get the currently selected object. Returns undefined if there is no -currently selected object. -* `selected(object)`: Check if the JavaScript object is currently in the -selection set. Returns true if the object is either the currently selected -object, or the current view proxy. -* `all()`: Get an array of all objects in the selection state. Will include -either or both of the view proxy and selected object. - -## Workers Category - -The `workers` extension category allows scripts to be run as web workers -using the `workerService`. - -An extension of this category has no implementation. The following properties -are supported: - -* `key`: A symbolic string used to identify this worker. -* `workerUrl`: The path, relative to this bundle's `src` folder, where - this worker's source code resides. -* `shared`: Optional; a boolean flag which, if true, indicates that this - worker should be instantiated as a - [`SharedWorker`](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker/SharedWorker). - Default value is `false`. - -# Directives - -Open MCT defines several Angular directives that are intended for use both -internally within the platform, and by plugins. - -## Container - -The `mct-container` is similar to the `mct-include` directive insofar as it allows -templates to be referenced by symbolic keys instead of by URL. Unlike -`mct-include` it supports transclusion. - -Unlike `mct-include` `mct-container` accepts a key as a plain string attribute, -instead of as an Angular expression. - -## Control - -The `mct-control` directive is used to display user input elements. Several -controls are included with the platform to wrap default input types. This -directive is primarily intended for internal use by the `mct-form` and -`mct-toolbar` directives. - -When using `mct-control` the attributes `ng-model` `ng-disabled` -`ng-required` and `ng-pattern` may also be used. These have the usual meaning -(as they would for an input element) except for `ng-model`; when used, it will -actually be `ngModel[field]` (see below) that is two-way bound by this control. -This allows `mct-control` elements to more easily delegate to other -`mct-control` instances, and also facilitates usage for generated forms. - -This directive supports the following additional attributes, all specified as -Angular expressions: - -* `key`: A machine-readable identifier for the specific type of control to -display. -* `options`: A set of options to display in this control. -* `structure`: In practice, contains the definition object which describes this -form row or toolbar item. Used to pass additional control-specific parameters. -* `field`: The field in the `ngModel` under which to read/store the property -associated with this control. - -## Drag - -The `mct-drag` directive is used to support drag-based gestures on HTML -elements. Note that this is not 'drag' in the 'drag-and-drop' sense, but 'drag' -in the more general 'mouse down, mouse move, mouse up' sense. - -This takes the form of three attributes: - -* `mct-drag`: An Angular expression to evaluate during drag movement. -* `mct-drag-down`: An Angular expression to evaluate when the drag starts. -* `mct-drag-up`: An Angular expression to evaluate when the drag ends. - -In each case, a variable `delta` will be provided to the expression; this is a -two-element array or the horizontal and vertical pixel offset of the current -mouse position relative to the mouse position where dragging began. - -## Form - -The `mct-form` directive is used to generate forms using a declarative structure, -and to gather back user input. It is applicable at the element level and -supports the following attributes: - -* `ng-model`: The object which should contain the full form input. Individual -fields in this model are bound to individual controls; the names used for these -fields are provided in the form structure (see below). -* `structure`: The structure of the form; e.g. sections, rows, their names, and -so forth. The value of this attribute should be an Angular expression. -* `name`: The name in the containing scope under which to publish form -"meta-state", e.g. `$valid` `$dirty` etc. This is as the behavior of `ng-form`. -Passed as plain text in the attribute. - -### Form Structure - -Forms in Open MCT have a common structure to permit consistent display. A -form is broken down into sections, which will be displayed in groups; each -section is broken down into rows, each of which provides a control for a single -property. Input from this form is two-way bound to the object passed via -`ng-model`. - -A form's structure is represented by a JavaScript object in the following form: - - { - "name": ... title to display for the form, as a string ..., - "sections": [ - { - "name": ... title to display for the section ..., - "rows": [ - { - "name": ... title to display for this row ..., - "control": ... symbolic key for the control ..., - "key": ... field name in ng-model ... - "pattern": ... optional, reg exp to match against ... - "required": ... optional boolean ... - "options": [ - "name": ... name to display (e.g. in a select) ..., - "value": ... value to store in the model ... - ] - }, - ... and other rows ... - ] - }, - ... and other sections ... - ] - } - -Note that `pattern` may be specified as a string, to simplify storing for -structures as JSON when necessary. The string should be given in a form -appropriate to pass to a `RegExp` constructor. - -### Form Controls - -A few standard control types are included in the platform/forms bundle: - -* `textfield`: An area to enter plain text. -* `select`: A drop-down list of options. -* `checkbox`: A box which may be checked/unchecked. -* `color`: A color picker. -* `button`: A button. -* `datetime`: An input for UTC date/time entry; gives result as a UNIX -timestamp, in milliseconds since start of 1970, UTC. - -## Include - -The `mct-include` directive is similar to ng-include , except that it takes a -symbolic identifier for a template instead of a URL. Additionally, templates -included via mct-include will have an isolated scope. - -The directive should be used at the element level and supports the following -attributes, all of which are specified as Angular expressions: - -* `key`: Machine-readable identifier for the template (of extension category -templates ) to be displayed. -* `ng-model`: _Optional_; will be passed into the template's scope as `ngModel`. -Intended usage is for two-way bound user input. -* `parameters`: _Optional_; will be passed into the template's scope as -parameters. Intended usage is for template-specific display parameters. - -## Representation - -The `mct-representation` directive is used to include templates which -specifically represent domain objects. Usage is similar to `mct-include`. - -The directive should be used at the element level and supports the following -attributes, all of which are specified as Angular expressions: - -* `key`: Machine-readable identifier for the representation (of extension -category _representations_ or _views_ ) to be displayed. -* `mct-object`: The domain object being represented. -* `ng-model`: Optional; will be passed into the template's scope as `ngModel`. -Intended usage is for two-way bound user input. -* `parameters`: Optional; will be passed into the template's scope as -parameters . Intended usage is for template-specific display parameters. - -## Resize - -The `mct-resize` directive is used to monitor the size of an HTML element. It is -specified as an attribute whose value is an Angular expression that will be -evaluated when the size of the HTML element changes. This expression will be -provided a single variable, `bounds` which is an object containing two -properties, `width` and `height` describing the size in pixels of the element. - -When using this directive, an attribute `mct-resize-interval` may optionally be -provided. Its value is an Angular expression describing the number of -milliseconds to wait before next checking the size of the HTML element; this -expression is evaluated when the directive is linked and reevaluated whenever -the size is checked. - -## Scroll - -The `mct-scroll-x` and `mct-scroll-y` directives are used to both monitor and -control the horizontal and vertical scroll bar state of an element, -respectively. They are intended to be used as attributes whose values are -assignable Angular expressions which two-way bind to the scroll bar state. - -## Toolbar - -The `mct-toolbar` directive is used to generate toolbars using a declarative -structure, and to gather back user input. It is applicable at the element level -and supports the following attributes: - -* `ng-model`: The object which should contain the full toolbar input. Individual -fields in this model are bound to individual controls; the names used for these -fields are provided in the form structure (see below). -* `structure`: The structure of the toolbar; e.g. sections, rows, their names, and -so forth. The value of this attribute should be an Angular expression. -* `name`: The name in the containing scope under which to publish form -"meta-state", e.g. `$valid`, `$dirty` etc. This is as the behavior of -`ng-form`. Passed as plain text in the attribute. - -Toolbars support the same control options as forms. - -### Toolbar Structure - -A toolbar's structure is defined similarly to forms, except instead of rows -there are items . - - { - "name": ... title to display for the form, as a string ..., - "sections": [ - { - "name": ... title to display for the section ..., - "items": [ - { - "name": ... title to display for this row ..., - "control": ... symbolic key for the control ..., - "key": ... field name in ng-model ... - "pattern": ... optional, reg exp to match against ... - "required": ... optional boolean ... - "options": [ - "name": ... name to display (e.g. in a select) ..., - "value": ... value to store in the model ... - ], - "disabled": ... true if control should be disabled ... - "size": ... size of the control (for textfields) ... - "click": ... function to invoke (for buttons) ... - "glyph": ... glyph to display (for buttons) ... - "text": ... text within control (for buttons) ... - }, - ... and other rows ... - ] - }, - ... and other sections ... - ] - } - -## Table - -The `mct-table` directive provides a generic table component, with optional -sorting and filtering capabilities. The table can be pre-populated with data -by setting the `rows` parameter, and it can be updated in real-time using the -`add:row` and `remove:row` broadcast events. The table will expand to occupy -100% of the size of its containing element. The table is highly optimized for -very large data sets. - -### Events - -The table supports two events for notifying that the rows have changed. For -performance reasons, the table does not monitor the content of `rows` -constantly. - -* `add:row`: A `$broadcast` event that will notify the table that a new row -has been added to the table. - -eg. The code below adds a new row, and alerts the table using the `add:row` -event. Sorting and filtering will be applied automatically by the table component. - -``` -$scope.rows.push(newRow); -$scope.$broadcast('add:row', $scope.rows.length-1); -``` - -* `remove:row`: A `$broadcast` event that will notify the table that a row -should be removed from the table. - -eg. The code below removes a row from the rows array, and then alerts the table -to its removal. - -``` -$scope.rows.slice(5, 1); -$scope.$broadcast('remove:row', 5); -``` - -### Parameters - -* `headers`: An array of string values which will constitute the column titles - that appear at the top of the table. Corresponding values are specified in - the rows using the header title provided here. -* `rows`: An array of objects containing row values. Each element in the -array must be an associative array, where the key corresponds to a column header. -* `enableFilter`: A boolean that if true, will enable searching and result -filtering. When enabled, each column will have a text input field that can be -used to filter the table rows in real time. -* `enableSort`: A boolean determining whether rows can be sorted. If true, -sorting will be enabled allowing sorting by clicking on column headers. Only -one column may be sorted at a time. -* `autoScroll`: A boolean value that if true, will cause the table to automatically -scroll to the bottom as new data arrives. Auto-scroll can be disengaged manually -by scrolling away from the bottom of the table, and can also be enabled manually -by scrolling to the bottom of the table rows. - -# Services - -The Open MCT platform provides a variety of services which can be retrieved -and utilized via dependency injection. These services fall into two categories: - -* _Composite Services_ are defined by a set of components extensions; plugins may -introduce additional components with matching interfaces to extend or augment -the functionality of the composed service. (See the Framework section on -Composite Services.) -* _Other services_ which are defined as standalone service objects; these can be -utilized by plugins but are not intended to be modified or augmented. - -## Composite Type Services - -This section describes the composite services exposed by Open MCT, -specifically focusing on their interface and contract. - -In many cases, the platform will include a provider for a service which consumes -a specific extension category; for instance, the `actionService` depends on -`actions[]` and will expose available actions based on the rules defined for -that extension category. - -In these cases, it will usually be simpler to add a new extension of a given -category (e.g. of category `actions`) even when the same behavior could be -introduced by a service component (e.g. an extension of category `components` -where `provides` is `actionService` and `type` is `provider`.) - -Occasionally, the extension category does not provide enough expressive power to -achieve a desired result. For instance, the Create menu is populated with -`create` actions, where one such action exists for each creatable type. Since -the framework does not provide a declarative means to introduce a new action per -type declaratively, the platform implements this explicitly in an `actionService` -component of type `provider`. Plugins may use a similar approach when the normal -extension mechanism is insufficient to achieve a desired result. - -### Action Service - -The [Action Service](../architecture/platform.md#action-service) -(`actionService`) -provides `Action` instances which are applicable in specific contexts. See Core -API for additional notes on the interface for actions. The `actionService` has -the following interface: - -* `getActions(context)`: Returns an array of Action objects which are applicable -in the specified action context. - -### Capability Service - -The [Capability Service](../architecture/platform.md#capability-service) -(`capabilityService`) -provides constructors for capabilities which will be exposed for a given domain -object. - -The capabilityService has the following interface: - -* `getCapabilities(model)`: Returns a an object containing key-value pairs, -representing capabilities which should be exposed by the domain object with this -model. Keys in this object are the capability keys (as used in a -`getCapability(...)` call) and values are either: - * Functions, in which case they will be used as constructors, which will - receive the domain object instance to which the capability applies as their - sole argument.The resulting object will be provided as the result of a - domain object's `getCapability(...)` call. Note that these instances are cached - by each object, but may be recreated when an object is mutated. - * Other objects, which will be used directly as the result of a domain - object's `getCapability(...)` call. - -### Dialog Service - -The `dialogService` provides a means for requesting user input via a modal -dialog. It has the following interface: - -* `getUserInput(formStructure, formState)`: Prompt the user to fill out a form. -The first argument describes the form's structure (as will be passed to - mct-form ) while the second argument contains the initial state of that form. -This returns a Promise for the state of the form after the user has filled it -in; this promise will be rejected if the user cancels input. -* `getUserChoice(dialogStructure)`: Prompt the user to make a single choice from -a set of options, which (in the platform implementation) will be expressed as -buttons in the displayed dialog. Returns a Promise for the user's choice, which -will be rejected if the user cancels input. - -### Dialog Structure - -The object passed as the `dialogStructure` to `getUserChoice` should have the -following properties: - -* `title`: The title to display at the top of the dialog. -* `hint`: Short message to display below the title. -* `template`: Identifying key (as will be passed to mct-include ) for the -template which will be used to populate the inner area of the dialog. -* `model`: Model to pass in the ng-model attribute of mct-include . -* `parameters`: Parameters to pass in the parameters attribute of mct-include . -* `options`: An array of options describing each button at the bottom. Each -option may have the following properties: - * `name`: Human-readable name to display in the button. - * `key`: Machine-readable key, to pass as the result of the resolved promise - when clicked. - * `description`: Description to show in tooltip on hover. - -### Domain Object Service - -The [Object Service](../architecture/platform.md#object-service) (`objectService`) -provides domain object instances. It has the following interface: - -* `getObjects(ids)`: For the provided array of domain object identifiers, -returns a Promise for an object containing key-value pairs, where keys are -domain object identifiers and values are corresponding DomainObject instances. -Note that the result may contain a superset or subset of the objects requested. - -### Gesture Service - -The `gestureService` is used to attach gestures (see extension category gestures) -to representations. It has the following interface: - -* `attachGestures(element, domainObject, keys)`: Attach gestures specified by -the provided gesture keys (an array of strings) to this jqLite-wrapped HTML -element , which represents the specified domainObject . Returns an object with a -single method `destroy()`, to be invoked when it is time to detach these -gestures. - -### Model Service - -The [Model Service](../architecture/platform.md#model-service) (`modelService`) -provides domain object models. It has the following interface: - -* `getModels(ids)`: For the provided array of domain object identifiers, returns -a Promise for an object containing key-value pairs, where keys are domain object -identifiers and values are corresponding domain object models. Note that the -result may contain a superset or subset of the models requested. - -### Persistence Service - -The [Persistence Service](../architecture/platform.md#persistence-service) (`persistenceService`) -provides the ability to load/store JavaScript objects -(presumably serializing/deserializing to JSON in the process.) This is used -primarily to store domain object models. It has the following interface: - -* `listSpaces()`: Returns a Promise for an array of strings identifying the -different persistence spaces this service supports. Spaces are intended to be -used to distinguish between different underlying persistence stores, to allow -these to live side by side. -* `listObjects()`: Returns a Promise for an array of strings identifying all -documents stored in this persistence service. -* `createObject(space, key, value)`: Create a new document in the specified -persistence space , identified by the specified key , the contents of which shall -match the specified value . Returns a promise that will be rejected if creation -fails. -* `readObject(space, key)`: Read an existing document in the specified -persistence space , identified by the specified key . Returns a promise for the -specified document; this promise will resolve to undefined if the document does -not exist. -* `updateObject(space, key, value)`: Update an existing document in the -specified persistence space , identified by the specified key , such that its -contents match the specified value . Returns a promise that will be rejected if -the update fails. -* `deleteObject(space, key)`: Delete an existing document from the specified -persistence space , identified by the specified key . Returns a promise which will -be rejected if deletion fails. - -### Policy Service - -The [Policy Service](../architecture/platform.md#policy-service) (`policyService`) -may be used to determine whether or not certain behaviors are -allowed within the application. It has the following interface: - -* `allow(category, candidate, context, [callback])`: Check if this decision -should be allowed. Returns a boolean. Its arguments are interpreted as: - * `category`: A string identifying which kind of decision is being made. See - the [section on Categories](#policy-categories) for categories supported by - the platform; plugins may define and utilize policies of additional - categories, as well. - * `candidate`: An object representing the thing which shall or shall not be - allowed. Usually, this will be an instance of an extension of the category - defined above. This does need to be the case; additional policies which are - not specific to any extension may also be defined and consulted using unique - category identifiers. In this case, the type of the object delivered for the - candidate may be unique to the policy type. - * `context`: An object representing the context in which the decision is - occurring. Its contents are specific to each policy category. - * `callback`: Optional; a function to call if the policy decision is rejected. - This function will be called with the message string (which may be - undefined) of whichever individual policy caused the operation to fail. - -### Telemetry Service - -The [Telemetry Service](../architecture/platform.md#telemetry-service) (`telemetryService`) -is used to acquire telemetry data. See the section on -Telemetry in Core API for more information on how both the arguments and -responses of this service are structured. - -When acquiring telemetry for display, it is recommended that the -`telemetryHandler` service be used instead of this service. The -`telemetryHandler` has additional support for subscribing to and requesting -telemetry data associated with domain objects or groups of domain objects. See -the [Other Services](#other-services) section for more information. - -The `telemetryService` has the following interface: - -* `requestTelemetry(requests)`: Issue a request for telemetry, matching the -specified telemetry requests . Returns a _ Promise _ for a telemetry response -object. -* `subscribe(callback, requests)`: Subscribe to real-time updates for telemetry, -matching the specified `requests`. The specified `callback` will be invoked with -telemetry response objects as they become available. This method returns a -function which can be invoked to terminate the subscription. - -### Type Service - -The [Type Service](../architecture/platform.md#type-service) (`typeService`) exposes -domain object types. It has the following interface: - -* `listTypes()`: Returns all domain object types supported in the application, -as an array of `Type` instances. -* `getType(key)`: Returns the `Type` instance identified by the provided key, or -undefined if no such type exists. - -### View Service - -The [View Service](../architecture/platform.md#view-service) (`viewService`) exposes -definitions for views of domain objects. It has the following interface: - -* `getViews(domainObject)`: Get an array of extension definitions of category -`views` which are valid and applicable to the specified `domainObject`. - -## Other Services - -### Drag and Drop - -The `dndService` provides information about the content of an active -drag-and-drop gesture within the application. It is intended to complement the -`DataTransfer` API of HTML5 drag-and-drop, by providing access to non-serialized -JavaScript objects being dragged, as well as by permitting inspection during -drag (which is normally prohibited by browsers for security reasons.) - -The `dndService` has the following methods: - -* `setData(key, value)`: Set drag data associated with a given type, specified -by the `key` argument. -* `getData(key)`: Get drag data associated with a given type, specified by the -`key` argument. -* `removeData(key)`: Clear drag data associated with a given type, specified by -the `key` argument. - -### Navigation - -The _Navigation_ service provides information about the current navigation state -of the application; that is, which object is the user currently viewing? This -service merely tracks this state and notifies listeners; it does not take -immediate action when navigation changes, although its listeners might. - -The `navigationService` has the following methods: - -* `getNavigation()`: Get the current navigation state. Returns a `DomainObject`. -* `setNavigation(domainObject)`: Set the current navigation state. Returns a -`DomainObject`. -* `addListener(callback)`: Listen for changes in navigation state. The provided -`callback` should be a `Function` which takes a single `DomainObject` as an -argument. -* `removeListener(callback)`: Stop listening for changes in navigation state. -The provided `callback` should be a `Function` which has previously been passed -to addListener . - -### Now - -The service now is a function which acts as a simple wrapper for `Date.now()`. -It is present mainly so that this functionality may be more easily mocked in -tests for scripts which use the current time. - -### Telemetry Formatter - -The _Telemetry Formatter_ is a utility for formatting domain and range values -read from a telemetry series. - -`telemetryFormatter` has the following methods: - -* `formatDomainValue(value)`: Format the provided domain value (which will be -assumed to be a timestamp) for display; returns a string. -* `formatRangeValue(value)`: Format the provided range value (a number) for -display; returns a string. - -### Telemetry Handler - -The _Telemetry Handler_ is a utility for retrieving telemetry data associated -with domain objects; it is particularly useful for dealing with cases where the -telemetry capability is delegated to contained objects (as occurs -in _Telemetry Panels_.) - -The `telemetryHandler` has the following methods: - -* `handle(domainObject, callback, [lossless])`: Subscribe to and issue future -requests for telemetry associated with the provided `domainObject`, invoking the -provided callback function when streaming data becomes available. Returns a -`TelemetryHandle` (see below.) - -#### Telemetry Handle - -A TelemetryHandle has the following methods: - -* `getTelemetryObjects()`: Get the domain objects (as a `DomainObject[]`) that -have a telemetry capability and are being handled here. Note that these are -looked up asynchronously, so this method may return an empty array if the -initial lookup is not yet completed. -* `promiseTelemetryObjects()`: As `getTelemetryObjects()`, but returns a Promise -that will be fulfilled when the lookup is complete. -* `unsubscribe()`: Unsubscribe to streaming telemetry updates associated with -this handle. -* `getDomainValue(domainObject)`: Get the most recent domain value received via -a streaming update for the specified `domainObject`. -* `getRangeValue(domainObject)`: Get the most recent range value received via a -streaming update for the specified `domainObject`. -* `getMetadata()`: Get metadata (as reported by the `getMetadata()` method of a -telemetry capability) associated with telemetry-providing domain objects. -Returns an array, which is in the same order as getTelemetryObjects() . -* `request(request, callback)`: Issue a new request for historical telemetry -data. The provided callback will be invoked when new data becomes available, -which may occur multiple times (e.g. if there are multiple domain objects.) It -will be invoked with the DomainObject for which a new series is available, and -the TelemetrySeries itself, in that order. -* `getSeries(domainObject)`: Get the latest `TelemetrySeries` (as resulted from -a previous `request(...)` call) available for this domain object. - -### Worker Service - -The `workerService` may be used to run web workers defined via the -`workers` extension category. It has the following method: - -* `run(key)`: Run the worker identified by the provided `key`. Returns - a `Worker` (or `SharedWorker`, if the specified worker is defined - as a shared worker); if the `key` is unknown, returns `undefined`. - -# Models -Domain object models in Open MCT are JavaScript objects describing the -persistent state of the domain objects they describe. Their contents include a -mix of commonly understood metadata attributes; attributes which are recognized -by and/or determine the applicability of specific extensions; and properties -specific to given types. - -## General Metadata - -Some properties of domain object models have a ubiquitous meaning through Open -MCT Web and can be utilized directly: - -* `name`: The human-readable name of the domain object. - -## Extension-specific Properties - -Other properties of domain object models have specific meaning imposed by other -extensions within the Open MCT platform. - -### Capability-specific Properties - -Some properties either trigger the presence/absence of certain capabilities, or -are managed by specific capabilities: - -* `composition`: An array of domain object identifiers that represents the -contents of this domain object (e.g. as will appear in the tree hierarchy.) -Understood by the composition capability; the presence or absence of this -property determines the presence or absence of that capability. -* `modified`: The timestamp (in milliseconds since the UNIX epoch) of the last -modification made to this domain object. Managed by the mutation capability. -* `persisted`: The timestamp (in milliseconds since the UNIX epoch) of the last -time when changes to this domain object were persisted. Managed by the - persistence capability. -* `relationships`: An object containing key-value pairs, where keys are symbolic -identifiers for relationship types, and values are arrays of domain object -identifiers. Used by the relationship capability; the presence or absence of -this property determines the presence or absence of that capability. -* `telemetry`: An object which serves as a template for telemetry requests -associated with this domain object (e.g. specifying `source` and `key`; see -Telemetry Requests under Core API.) Used by the telemetry capability; the -presence or absence of this property determines the presence or absence of that -capability. -* `type`: A string identifying the type of this domain object. Used by the `type` -capability. - -### View Configurations - -Persistent configurations for specific views of domain objects are stored in the -domain object model under the property configurations . This is an object -containing key-value pairs, where keys identify the view, and values are objects -containing view-specific (and view-managed) configuration properties. - -## Modifying Models -When interacting with a domain object's model, it is possible to make -modifications to it directly. __Don't!__ These changes may not be properly detected -by the platform, meaning that other representations of the domain object may not -be updated, changes may not be saved at the expected times, and generally, that -unexpected behavior may occur. Instead, use the `mutation` capability. - -# Capabilities - -Dynamic behavior associated with a domain object is expressed as capabilities. A -capability is a JavaScript object with an interface that is specific to the type -of capability in use. - -Often, there is a relationship between capabilities and services. For instance, -there is an action capability and an actionService , and there is a telemetry -capability as well as a `telemetryService`. Typically, the pattern here is that -the capability will utilize the service for the specific domain object. - -When interacting with domain objects, it is generally preferable to use a -capability instead of a service when the option is available. Capability -interfaces are typically easier to use and/or more powerful in these situations. -Additionally, this usage provides a more robust substitutability mechanism; for -instance, one could configure a plugin such that it provided a totally new -implementation of a given capability which might not invoke the underlying -service, while user code which interacts with capabilities remains indifferent -to this detail. - -## Action Capability - -The `action` capability is present for all domain objects. It allows applicable -`Action` instances to be retrieved and performed for specific domain objects. - -For example: - `domainObject.getCapability("action").perform("navigate"); ` - ...will initiate a navigate action upon the domain object, if an action with - key "navigate" is defined. - -This capability has the following interface: -* `getActions(context)`: Get the actions that are applicable in the specified -action `context`; the capability will fill in the `domainObject` field of this -context if necessary. If context is specified as a string, they will instead be -used as the `key` of the action context. Returns an array of `Action` instances. -* `perform(context)`: Perform an action. This will find and perform the first -matching action available for the specified action context , filling in the -`domainObject` field as necessary. If `context` is specified as a string, they -will instead be used as the `key` of the action context. Returns a `Promise` for -the result of the action that was performed, or `undefined` if no matching action -was found. - -## Composition Capability - -The `composition` capability provides access to domain objects that are -contained by this domain object. While the `composition` property of a domain -object's model describes these contents (by their identifiers), the -`composition` capability provides a means to load the corresponding -`DomainObject` instances in the same order. The absence of this property in the -model will result in the absence of this capability in the domain object. - -This capability has the following interface: - -* `invoke()`: Returns a `Promise` for an array of `DomainObject` instances. - -## Delegation Capability - -The delegation capability is used to communicate the intent of a domain object -to delegate responsibilities, which would normally handled by other -capabilities, to the domain objects in its composition. - -This capability has the following interface: - -* `getDelegates(key)`: Returns a Promise for an array of DomainObject instances, -to which this domain object wishes to delegate the capability with the specified -key . -* `invoke(key)`: Alias of getDelegates(key) . -* `doesDelegate(key)`: Returns true if the domain object does delegate the -capability with the specified key . - -The platform implementation of the delegation capability inspects the domain -object's type definition for a property delegates , whose value is an array of -strings describing which capabilities domain objects of that type wish to -delegate. If this property is not present, the delegation capability will not be -present in domain objects of that type. - -## Editor Capability - -The editor capability is meant primarily for internal use by Edit mode, and -helps to manage the behavior associated with exiting _Edit_ mode via _Save_ or -_Cancel_. Its interface is not intended for general use. However, -`domainObject.hasCapability(editor)` is a useful way of determining whether or -not we are looking at an object in _Edit_ mode. - -## Mutation Capability - -The `mutation` capability provides a means by which the contents of a domain -object's model can be modified. This capability is provided by the platform for -all domain objects, and has the following interface: - -* `mutate(mutator, [timestamp])`: Modify the domain object's model using the -specified `mutator` function. After changes are made, the `modified` property of -the model will be updated with the specified `timestamp` if one was provided, -or with the current system time. -* `invoke(...)`: Alias of `mutate`. - -Changes to domain object models should only be made via the `mutation` -capability; other platform behavior is likely to break (either by exhibiting -undesired behavior, or failing to exhibit desired behavior) if models are -modified by other means. - -### Mutator Function - -The mutator argument above is a function which will receive a cloned copy of the -domain object's model as a single argument. It may return: - -* A `Promise` in which case the resolved value of the promise will be used to -determine which of the following forms is used. -* Boolean `false` in which case the mutation is cancelled. -* A JavaScript object, in which case this object will be used as the new model -for this domain object. -* No value (or, equivalently, `undefined`), in which case the cloned copy -(including any changes made in place by the mutator function) will be used as -the new domain object model. - -## Persistence Capability - -The persistence capability provides a mean for interacting with the underlying -persistence service which stores this domain object's model. It has the -following interface: - -* `persist()`: Store the local version of this domain object, including any -changes, to the persistence store. Returns a Promise for a boolean value, which -will be true when the object was successfully persisted. -* `refresh()`: Replace this domain object's model with the most recent version -from persistence. Returns a Promise which will resolve when the change has -completed. -* `getSpace()`: Return the string which identifies the persistence space which -stores this domain object. - -## Relationship Capability - -The relationship capability provides a means for accessing other domain objects -with which this domain object has some typed relationship. It has the following -interface: - -* `listRelationships()`: List all types of relationships exposed by this object. -Returns an array of strings identifying the types of relationships. -* `getRelatedObjects(relationship)`: Get all domain objects to which this domain -object has the specified type of relationship, which is a string identifier -(as above.) Returns a `Promise` for an array of `DomainObject` instances. - -The platform implementation of the `relationship` capability is present for domain -objects which has a `relationships` property in their model, whose value is an -object containing key-value pairs, where keys are strings identifying -relationship types, and values are arrays of domain object identifiers. - -## Status Capability - -The `status` capability provides a way to flag domain objects as possessing -certain states, represented as simple strings. These states, in turn, are -reflected on `mct-representation` elements as classes (prefixed with -`s-status-`.) The `status` capability has the following interface: - -* `get()`: Returns an array of all status strings that currently apply - to this object. -* `set(status, state)`: Adds or removes a status flag to this domain object. - The `status` argument is the string to set; `state` is a boolean - indicating whether this status should be included (true) or removed (false). -* `listen(callback)`: Listen for changes in status. The provided `callback` - will be invoked with an array of all current status strings whenever status - changes. - -Plug-ins may add and/or recognize arbitrary status flags. Flags defined -and/or supported by the platform are: - - Status | CSS Class | Meaning ------------|--------------------|----------------------------------- -`editing` | `s-status-editing` | Domain object is being edited. -`pending` | `s-status-pending` | Domain object is partially loaded. - - -## Telemetry Capability - -The telemetry capability provides a means for accessing telemetry data -associated with a domain object. It has the following interface: - -* `requestData([request])`: Request telemetry data for this specific domain -object, using telemetry request parameters from the specified request if -provided. This capability will fill in telemetry request properties as-needed -for this domain object. Returns a `Promise` for a `TelemetrySeries`. -* `subscribe(callback, [request])`: Subscribe to telemetry data updates for -this specific domain object, using telemetry request parameters from the -specified request if provided. This capability will fill in telemetry request -properties as-needed for this domain object. The specified callback will be -invoked with TelemetrySeries instances as they arrive. Returns a function which -can be invoked to terminate the subscription, or undefined if no subscription -could be obtained. -* `getMetadata()`: Get metadata associated with this domain object's telemetry. - -The platform implementation of the `telemetry` capability is present for domain -objects which has a `telemetry` property in their model and/or type definition; -this object will serve as a template for telemetry requests made using this -object, and will also be returned by `getMetadata()` above. - -## Type Capability -The `type` capability exposes information about the domain object's type. It has -the same interface as `Type`; see Core API. - -## View Capability - -The `view` capability exposes views which are applicable to a given domain -object. It has the following interface: - -* `invoke()`: Returns an array of extension definitions for views which are -applicable for this domain object. - -# Actions - -Actions are reusable processes/behaviors performed by users within the system, -typically upon domain objects. - -## Action Categories - -The platform understands the following action categories (specifiable as the -`category` parameter of an action's extension definition.) - -* `contextual`: Appears in context menus. -* `view-control`: Appears in top-right area of view (as buttons) in Browse mode - -## Platform Actions -The platform defines certain actions which can be utilized by way of a domain -object's `action` capability. Unless otherwise specified, these act upon (and -modify) the object described by the `domainObject` property of the action's -context. - -* `cancel`: Cancel the current editing action (invoked from Edit mode.) -* `compose`: Place an object in another object's composition. The object to be -added should be provided as the `selectedObject` of the action context. -* `edit`: Start editing an object (enter Edit mode.) -* `fullscreen`: Enter full screen mode. -* `navigate`: Make this object the focus of navigation (e.g. highlight it within -the tree, display a view of it to the right.) -* `properties`: Show the 'Edit Properties' dialog. -* `remove`: Remove this domain object from its parent's composition. (The -parent, in this case, is whichever other domain object exposed this object by -way of its `composition` capability.) -* `save`: Save changes (invoked from Edit mode.) -* `window`: Open this object in a new window. - -# Policies - -Policies are consulted to determine when certain behavior in Open MCT is -allowed. Policy questions are assigned to certain categories, which broadly -describe the type of decision being made; within each category, policies have a -candidate (the thing which may or may not be allowed) and, optionally, a context -(describing, generally, the context in which the decision is occurring.) - -The types of objects passed for 'candidate' and 'context' vary by category; -these types are documented below. - -## Policy Categories - -The platform understands the following policy categories (specifiable as the -`category` parameter of an policy's extension definition.) - -* `action`: Determines whether or not a given action is allowable. The candidate -argument here is an Action; the context is its action context object. -* `composition`: Determines whether or not a given domain object(first argument, `parent`) can contain a candidate child object (second argument, `child`). -* `view`: Determines whether or not a view is applicable for a domain object. -The candidate argument is the view's extension definition; the context argument -is the `DomainObject` to be viewed. - -# Build-Test-Deploy -Open MCT is designed to support a broad variety of build and deployment -options. The sources can be deployed in the same directory structure used during -development. A few utilities are included to support development processes. - -## Command-line Build - -Open MCT is built using [`npm`](http://npmjs.com/) -and [`gulp`](http://gulpjs.com/). - -To install build dependencies (only needs to be run once): - -`npm install` - -To build: - -`npm run prepare` - -This will compile and minify JavaScript sources, as well as copy over assets. -The contents of the `dist` folder will contain a runnable Open MCT -instance (e.g. by starting an HTTP server in that directory), including: - -* A `main.js` file containing Open MCT source code. -* Various assets in the `example` and `platform` directories. -* An `index.html` that runs Open MCT in its default configuration. - -Additional `gulp` tasks are defined in [the gulpfile](gulpfile.js). - -Note that an internet connection is required to run this build, in order to -download build dependencies. - -## Test Suite - -Open MCT uses [Jasmine 1.3](http://jasmine.github.io/) and -[Karma](http://karma-runner.github.io) for automated testing. - -The test suite is configured to load any scripts ending with `Spec.js` found -in the `src` hierarchy. Full configuration details are found in -`karma.conf.js`. By convention, unit test scripts should be located -alongside the units that they test; for example, `src/foo/Bar.js` would be -tested by `src/foo/BarSpec.js`. (For legacy reasons, some existing tests may -be located in separate `test` folders near the units they test, but the -naming convention is otherwise the same.) - -Tests are written as AMD modules which depend (at minimum) upon the -unit under test. For example, `src/foo/BarSpec.js` could look like: - - /*global define,Promise,describe,it,expect,beforeEach*/ - - define( - ["./Bar"], - function (Bar) { - "use strict"; - - describe("Bar", function () { - it("does something", function () { - var bar = new Bar(); - expect(controller.baz()).toEqual("foo"); - }); - }); - } - ); - - -## Code Coverage - -In addition to running tests, the test runner will also capture code coverage -information using [Blanket.JS](http://blanketjs.org/) and display this at the -bottom of the screen. Currently, only statement coverage is displayed. - -## Deployment -Open MCT is built to be flexible in terms of the deployment strategies it -supports. In order to run in the browser, Open MCT needs: - -1. HTTP access to sources/resources for the framework, platform, and all active -bundles. -2. Access to any external services utilized by active bundles. (This means that -external services need to support HTTP or some other web-accessible interface, -like WebSockets.) - -Any HTTP server capable of serving flat files is sufficient for the first point. -The command-line build also packages Open MCT into a `.war` file for easier -deployment on containers such as Apache Tomcat. - -The second point may be less flexible, as it depends upon the specific services -to be utilized by Open MCT. Because of this, it is often the set of external -services (and the manner in which they are exposed) that determine how to deploy -Open MCT. - -One important constraint to consider in this context is the browser's same -origin policy. If external services are not on the same apparent host and port -as the client (from the perspective of the browser) then access may be -disallowed. There are two workarounds if this occurs: - -* Make the external service appear to be on the same host/port, either by -actually deploying it there, or by proxying requests to it. -* Enable CORS (cross-origin resource sharing) on the external service. This is -only possible if the external service can be configured to support CORS. Care -should be exercised if choosing this option to ensure that the chosen -configuration does not create a security vulnerability. - -Examples of deployment strategies (and the conditions under which they make the -most sense) include: - -* If the external services that Open MCT will utilize are all running on -[Apache Tomcat](https://tomcat.apache.org/), then it makes sense to run Open -MCT Web from the same Tomcat instance as a separate web application. The -`.war` artifact produced by the command line build facilitates this deployment -option. (See https://tomcat.apache.org/tomcat-8.0-doc/deployer-howto.html for -general information on deploying in Tomcat.) -* If a variety of external services will be running from a variety of -hosts/ports, then it may make sense to use a web server that supports proxying, -such as the [Apache HTTP Server](http://httpd.apache.org/). In this -configuration, the HTTP server would be configured to proxy (or reverse proxy) -requests at specific paths to the various external services, while providing -Open MCT as flat files from a different path. -* If a single server component is being developed to handle all server-side -needs of an Open MCT instance, it can make sense to serve Open MCT (as -flat files) from the same component using an embedded HTTP server such as -[Nancy](http://nancyfx.org/). -* If no external services are needed (or if the 'external services' will just -be generating flat files to read) it makes sense to utilize a lightweight flat -file HTTP server such as [Lighttpd](http://www.lighttpd.net/). In this -configuration, Open MCT sources/resources would be placed at one path, while -the files generated by the external service are placed at another path. -* If all external services support CORS, it may make sense to have an HTTP -server that is solely responsible for making Open MCT sources/resources -available, and to have Open MCT contact these external services directly. -Again, lightweight HTTP servers such as [Lighttpd](http://www.lighttpd.net/) -are useful in this circumstance. The downside of this option is that additional -configuration effort is required, both to enable CORS on the external services, -and to ensure that Open MCT can correctly locate these services. - -Another important consideration is authentication. By design, Open MCT does -not handle user authentication. Instead, this should typically be treated as a -deployment-time concern, where authentication is handled by the HTTP server -which provides Open MCT, or an external access management system. - -### Configuration -In most of the deployment options above, some level of configuration is likely -to be needed or desirable to make sure that bundles can reach the external -services they need to reach. Most commonly this means providing the path or URL -to an external service. - -Configurable parameters within Open MCT are specified via constants -(literally, as extensions of the `constants` category) and accessed via -dependency injection by the scripts which need them. Reasonable defaults for -these constants are provided in the bundle where they are used. Plugins are -encouraged to follow the same pattern. - -Constants may be specified in any bundle; if multiple constants are specified -with the same `key` the highest-priority one will be used. This allows default -values to be overridden by specifying constants with higher priority. - -This permits at least three configuration approaches: - -* Modify the constants defined in their original bundles when deploying. This is -generally undesirable due to the amount of manual work required and potential -for error, but is viable if there are a small number of constants to change. -* Add a separate configuration bundle which overrides the values of these -constants. This is particularly appropriate when multiple configurations (e.g. -development, test, production) need to be managed easily; these can be swapped -quickly by changing the set of active bundles in bundles.json. -* Deploy Open MCT and its external services in such a fashion that the -default paths to reach external services are all correct. - -### Configuration Constants - -The following constants have global significance: -* `PERSISTENCE_SPACE`: The space in which domain objects should be persisted - (or read from) when not otherwise specified. Typically this will not need - to be overridden by other bundles, but persistence adapters may wish to - consume this constant in order to provide persistence for that space. - -The following configuration constants are recognized by Open MCT bundles: -* Common UI elements - `platform/commonUI/general` - * `THEME`: A string identifying the current theme symbolically. Individual - stylesheets (the `stylesheets` extension category) may specify an optional - `theme` property which will be matched against this before inclusion. -* CouchDB adapter - `platform/persistence/couch` - * `COUCHDB_PATH`: URL or path to the CouchDB database to be used for domain - object persistence. Should not include a trailing slash. -* ElasticSearch adapter - `platform/persistence/elastic` - * `ELASTIC_ROOT`: URL or path to the ElasticSearch instance to be used for - domain object persistence. Should not include a trailing slash. - * `ELASTIC_PATH`: Path relative to the ElasticSearch instance where domain - object models should be persisted. Should take the form `/`. diff --git a/docs/src/index.md b/docs/src/index.md index a09c2f8232..3166ae6019 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -22,16 +22,5 @@ * The [Development Process](process/) document describes the Open MCT software development cycle. -## Legacy Documentation - -As we transition to a new API, the following documentation for the old API -(which is supported during the transition) may be useful as well: - - * The [Architecture Overview](architecture/) describes the concepts used - throughout Open MCT, and gives a high level overview of the platform's design. - - * The [Developer's Guide](guide/) goes into more detail about how to use the - platform and the functionality that it provides. - * The [Tutorials](https://github.com/nasa/openmct-tutorial) give examples of extending the platform to add functionality, and integrate with data sources. diff --git a/docs/src/process/index.md b/docs/src/process/index.md index fc8f7e6e91..ae781b0de8 100644 --- a/docs/src/process/index.md +++ b/docs/src/process/index.md @@ -7,9 +7,5 @@ documents: process points are repeated during development. * The [Version Guide](version.md) describes version numbering for Open MCT (both semantics and process.) -* Testing is described in two documents: - * The [Test Plan](testing/plan.md) summarizes the approaches used - to test Open MCT. - * The [Test Procedures](testing/procedures.md) document what - specific tests are performed to verify correctness, and how - they should be carried out. +* The [Test Plan](testing/plan.md) summarizes the approaches used + to test Open MCT. diff --git a/docs/src/process/testing/procedures.md b/docs/src/process/testing/procedures.md deleted file mode 100644 index 5ccf0def5b..0000000000 --- a/docs/src/process/testing/procedures.md +++ /dev/null @@ -1,169 +0,0 @@ -# Test Procedures - -## Introduction - -This document is intended to be used: - -* By testers, to verify that Open MCT behaves as specified. -* By the development team, to document new test cases and to provide - guidance on how to author these. - -## Writing Procedures - -### Template - -Procedures for individual tests should use the following template, -adapted from [https://swehb.nasa.gov/display/7150/SWE-114](). - -Property | Value ----------------|--------------------------------------------------------------- -Test ID | -Relevant reqs. | -Prerequisites | -Test input | -Instructions | -Expectation | -Eval. criteria | - -For multi-line descriptions, use an asterisk or similar indicator to refer -to a longer-form description below. - -#### Example Procedure - Edit a Layout - -Property | Value ----------------|--------------------------------------------------------------- -Test ID | MCT-TEST-000X - Edit a layout -Relevant reqs. | MCT-EDIT-000Y -Prerequisites | Create a layout, as in MCT-TEST-000Z -Test input | Domain object database XYZ -Instructions | See below * -Expectation | Change to editing context † -Eval. criteria | Visual inspection - -* Follow the following steps: - -1. Verify that the created layout is currently navigated-to, - as in MCT-TEST-00ZZ. -2. Click the Edit button, identified by a pencil icon and the text "Edit" - displayed on hover. - -† Right-hand viewing area should be surrounded by a dashed -blue border when a domain object is being edited. - -### Guidelines - -Test procedures should be written assuming minimal prior knowledge of the -application: Non-standard terms should only be used when they are documented -in [the glossary](#glossary), and shorthands used for user actions should -be accompanied by useful references to test procedures describing those -actions (when available) or descriptions in user documentation. - -Test cases should be narrow in scope; if a list of steps is excessively -long (or must be written vaguely to be kept short) it should be broken -down into multiple tests which reference one another. - -All requirements satisfied by Open MCT should be verifiable using -one or more test procedures. - -## Glossary - -This section will contain terms used in test procedures. This may link to -a common glossary, to avoid replication of content. - -## Procedures - -This section will contain specific test procedures. Presently, procedures -are placeholders describing general patterns for setting up and conducting -testing. - -### User Testing Setup - -These procedures describes a general pattern for setting up for user -testing. Specific deployments should customize this pattern with -relevant data and any additional steps necessary. - -Property | Value ----------------|--------------------------------------------------------------- -Test ID | MCT-TEST-SETUP0 - User Testing Setup -Relevant reqs. | TBD -Prerequisites | Build of relevant components -Test input | Exemplary database; exemplary telemetry data set -Instructions | See below -Expectation | Able to load application in a web browser (Google Chrome) -Eval. criteria | Visual inspection - -Instructions: - -1. Start telemetry server. -2. Start ElasticSearch. -3. Restore database snapshot to ElasticSearch. -4. Start telemetry playback. -5. Start HTTP server for client sources. - -### User Test Procedures - -Specific user test cases have not yet been authored. In their absence, -user testing is conducted by: - -* Reviewing the text of issues from the issue tracker to understand the - desired behavior, and exercising this behavior in the running application. - (For instance, by following steps to reproduce from the original issue.) - * Issues which appear to be resolved should be marked as such with comments - on the original issue (e.g. "verified during user testing MM/DD/YYYY".) - * Issues which appear not to have been resolved should be reopened with an - explanation of what unexpected behavior has been observed. - * In cases where an issue appears resolved as-worded but other related - undesirable behavior is observed during testing, a new issue should be - opened, and linked to from a comment in the original issues. -* General usage of new features and/or existing features which have undergone - recent changes. Defects or problems with usability should be documented - by filing issues in the issue tracker. -* Open-ended testing to discover defects, identify usability issues, and - generate feature requests. - -### Long-Duration Testing - -The purpose of long-duration testing is to identify performance issues -and/or other defects which are sensitive to the amount of time the -application is kept running. (Memory leaks, for instance.) - -Property | Value ----------------|--------------------------------------------------------------- -Test ID | MCT-TEST-LDT0 - Long-duration Testing -Relevant reqs. | TBD -Prerequisites | MCT-TEST-SETUP0 -Test input | (As for test setup.) -Instructions | See "Instructions" below * -Expectation | See "Expectations" below † -Eval. criteria | Visual inspection - -* Instructions: - -1. Start `top` or a similar tool to measure CPU usage and memory utilization. -2. Open several user-created displays (as many as would be realistically - opened during actual usage in a stressing case) in some combination of - separate tabs and windows (approximately as many tabs-per-window as - total windows.) -3. Ensure that playback data is set to run continuously for at least 24 hours - (e.g. on a loop.) -4. Record CPU usage and memory utilization. -5. In at least one tab, try some general user interface gestures and make - notes about the subjective experience of using the application. (Particularly, - the degree of responsiveness.) -6. Leave client displays open for 24 hours. -7. Record CPU usage and memory utilization again. -8. Make additional notes about the subjective experience of using the - application (again, particularly responsiveness.) -9. Check logs for any unexpected warnings or errors. - -† Expectations: - -* At the end of the test, CPU usage and memory usage should both be similar - to their levels at the start of the test. -* At the end of the test, subjective usage of the application should not - be observably different from the way it was at the start of the test. - (In particular, responsiveness should not decrease.) -* Logs should not contain any unexpected warnings or errors ("expected" - warnings or errors are those that have been documented and prioritized - as known issues, or those that are explained by transient conditions - external to the software, such as network outages.) diff --git a/e2e/.eslintrc.js b/e2e/.eslintrc.js index 78d37c29a8..3388deb35f 100644 --- a/e2e/.eslintrc.js +++ b/e2e/.eslintrc.js @@ -1,4 +1,12 @@ /* eslint-disable no-undef */ module.exports = { - "extends": ["plugin:playwright/playwright-test"] + "extends": ["plugin:playwright/playwright-test"], + "overrides": [ + { + "files": ["tests/visual/*.spec.js"], + "rules": { + "playwright/no-wait-for-timeout": "off" + } + } + ] }; diff --git a/e2e/.percy.yml b/e2e/.percy.yml new file mode 100644 index 0000000000..fc3ff095de --- /dev/null +++ b/e2e/.percy.yml @@ -0,0 +1,5 @@ +version: 2 +snapshot: + widths: [1024, 2000] + min-height: 1440 # px + diff --git a/e2e/fixtures.js b/e2e/fixtures.js new file mode 100644 index 0000000000..ce46de5bbf --- /dev/null +++ b/e2e/fixtures.js @@ -0,0 +1,69 @@ +/* This file extends the base functionality of the playwright test framework to enable + * code coverage instrumentation, console log error detection and working with a 3rd + * party Chrome-as-a-service extension called Browserless. + */ + +const base = require('@playwright/test'); +const { expect } = require('@playwright/test'); +const fs = require('fs'); +const path = require('path'); +const { v4: uuid } = require('uuid'); + +/** + * Takes a `ConsoleMessage` and returns a formatted string + * @param {import('@playwright/test').ConsoleMessage} msg + * @returns {String} formatted string with message type, text, url, and line and column numbers + */ +function consoleMessageToString(msg) { + const { url, lineNumber, columnNumber } = msg.location(); + + return `[${msg.type()}] ${msg.text()} + at (${url} ${lineNumber}:${columnNumber})`; +} + +//The following is based on https://github.com/mxschmitt/playwright-test-coverage +// eslint-disable-next-line no-undef +const istanbulCLIOutput = path.join(process.cwd(), '.nyc_output'); + +// eslint-disable-next-line no-undef +exports.test = base.test.extend({ + //The following is based on https://github.com/mxschmitt/playwright-test-coverage + context: async ({ context }, use) => { + await context.addInitScript(() => + window.addEventListener('beforeunload', () => + (window).collectIstanbulCoverage(JSON.stringify((window).__coverage__)) + ) + ); + await fs.promises.mkdir(istanbulCLIOutput, { recursive: true }); + await context.exposeFunction('collectIstanbulCoverage', (coverageJSON) => { + if (coverageJSON) { + fs.writeFileSync(path.join(istanbulCLIOutput, `playwright_coverage_${uuid()}.json`), coverageJSON); + } + }); + await use(context); + for (const page of context.pages()) { + await page.evaluate(() => (window).collectIstanbulCoverage(JSON.stringify((window).__coverage__))); + } + }, + page: async ({ baseURL, page }, use) => { + const messages = []; + page.on('console', (msg) => messages.push(msg)); + await use(page); + messages.forEach( + msg => expect.soft(msg.type(), `Console error detected: ${consoleMessageToString(msg)}`).not.toEqual('error') + ); + }, + browser: async ({ playwright, browser }, use, workerInfo) => { + // Use browserless if configured + if (workerInfo.project.name.match(/browserless/)) { + const vBrowser = await playwright.chromium.connectOverCDP({ + endpointURL: 'ws://localhost:3003' + }); + await use(vBrowser); + } else { + // Use Local Browser for testing. + await use(browser); + } + } +}); + diff --git a/e2e/playwright-ci.config.js b/e2e/playwright-ci.config.js index a6d0d62a31..a0139f56bf 100644 --- a/e2e/playwright-ci.config.js +++ b/e2e/playwright-ci.config.js @@ -2,30 +2,78 @@ // playwright.config.js // @ts-check +// eslint-disable-next-line no-unused-vars +const { devices } = require('@playwright/test'); +const MAX_FAILURES = 5; +const NUM_WORKERS = 2; + /** @type {import('@playwright/test').PlaywrightTestConfig} */ const config = { - retries: 2, + retries: 3, //Retries 3 times for a total of 4. When running sharded and with maxFailures = 5, this should ensure that flake is managed without failing the full suite testDir: 'tests', - timeout: 90 * 1000, + testIgnore: '**/*.perf.spec.js', //Ignore performance tests and define in playwright-perfromance.config.js + timeout: 60 * 1000, webServer: { - command: 'npm run start', - port: 8080, + command: 'cross-env NODE_ENV=test npm run start', + url: 'http://localhost:8080/#', timeout: 200 * 1000, - reuseExistingServer: !process.env.CI + reuseExistingServer: false }, + maxFailures: MAX_FAILURES, //Limits failures to 5 to reduce CI Waste + workers: NUM_WORKERS, //Limit to 2 for CircleCI Agent use: { - browserName: "chromium", baseURL: 'http://localhost:8080/', headless: true, ignoreHTTPSErrors: true, - screenshot: 'on', - trace: 'on', - video: 'on' + screenshot: 'only-on-failure', + trace: 'on-first-retry', + video: 'off' }, + projects: [ + { + name: 'chrome', + use: { + browserName: 'chromium' + } + }, + { + name: 'MMOC', + testMatch: '**/*.e2e.spec.js', // only run e2e tests + grepInvert: /@snapshot/, + use: { + browserName: 'chromium', + viewport: { + width: 2560, + height: 1440 + } + } + }, + { + name: 'firefox', + testMatch: '**/*.e2e.spec.js', // only run e2e tests + grepInvert: /@snapshot/, + use: { + browserName: 'firefox' + } + }, + { + name: 'chrome-beta', //Only Chrome Beta is available on ubuntu -- not chrome canary + testMatch: '**/*.e2e.spec.js', // only run e2e tests + grepInvert: /@snapshot/, + use: { + browserName: 'chromium', + channel: 'chrome-beta' + } + } + ], reporter: [ ['list'], + ['html', { + open: 'never', + outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840 + }], ['junit', { outputFile: 'test-results/results.xml' }], - ['allure-playwright'] + ['github'] ] }; diff --git a/e2e/playwright-local.config.js b/e2e/playwright-local.config.js index 82b7231a1e..d79c702b19 100644 --- a/e2e/playwright-local.config.js +++ b/e2e/playwright-local.config.js @@ -2,29 +2,102 @@ // playwright.config.js // @ts-check +// eslint-disable-next-line no-unused-vars +const { devices } = require('@playwright/test'); + /** @type {import('@playwright/test').PlaywrightTestConfig} */ const config = { retries: 0, testDir: 'tests', + testIgnore: '**/*.perf.spec.js', timeout: 30 * 1000, webServer: { - command: 'npm run start', - port: 8080, + command: 'cross-env NODE_ENV=test npm run start', + url: 'http://localhost:8080/#', timeout: 120 * 1000, - reuseExistingServer: !process.env.CI + reuseExistingServer: true }, + workers: 1, use: { browserName: "chromium", baseURL: 'http://localhost:8080/', headless: false, ignoreHTTPSErrors: true, - screenshot: 'on', - trace: 'on', - video: 'on' + screenshot: 'only-on-failure', + trace: 'retain-on-failure', + video: 'off' }, + projects: [ + { + name: 'chrome', + use: { + browserName: 'chromium' + } + }, + { + name: 'MMOC', + testMatch: '**/*.e2e.spec.js', // only run e2e tests + grepInvert: /@snapshot/, + use: { + browserName: 'chromium', + viewport: { + width: 2560, + height: 1440 + } + } + }, + { + name: 'safari', + testMatch: '**/*.e2e.spec.js', // only run e2e tests + grep: /@ipad/, // only run ipad tests due to this bug https://github.com/microsoft/playwright/issues/8340 + grepInvert: /@snapshot/, + use: { + browserName: 'webkit' + } + }, + { + name: 'firefox', + testMatch: '**/*.e2e.spec.js', // only run e2e tests + grepInvert: /@snapshot/, + use: { + browserName: 'firefox' + } + }, + { + name: 'canary', + testMatch: '**/*.e2e.spec.js', // only run e2e tests + grepInvert: /@snapshot/, + use: { + browserName: 'chromium', + channel: 'chrome-canary' //Note this is not available in ubuntu/CircleCI + } + }, + { + name: 'chrome-beta', + testMatch: '**/*.e2e.spec.js', // only run e2e tests + grepInvert: /@snapshot/, + use: { + browserName: 'chromium', + channel: 'chrome-beta' + } + }, + { + name: 'ipad', + testMatch: '**/*.e2e.spec.js', // only run e2e tests + grep: /@ipad/, + grepInvert: /@snapshot/, + use: { + browserName: 'webkit', + ...devices['iPad (gen 7) landscape'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json + } + } + ], reporter: [ ['list'], - ['allure-playwright'] + ['html', { + open: 'on-failure', + outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840 + }] ] }; diff --git a/e2e/playwright-performance.config.js b/e2e/playwright-performance.config.js new file mode 100644 index 0000000000..de79304f11 --- /dev/null +++ b/e2e/playwright-performance.config.js @@ -0,0 +1,43 @@ +/* eslint-disable no-undef */ +// playwright.config.js +// @ts-check + +const CI = process.env.CI === 'true'; + +/** @type {import('@playwright/test').PlaywrightTestConfig} */ +const config = { + retries: 1, //Only for debugging purposes because trace is enabled only on first retry + testDir: 'tests/performance/', + timeout: 60 * 1000, + workers: 1, //Only run in serial with 1 worker + webServer: { + command: 'cross-env NODE_ENV=test npm run start', + url: 'http://localhost:8080/#', + timeout: 200 * 1000, + reuseExistingServer: !CI + }, + use: { + browserName: "chromium", + baseURL: 'http://localhost:8080/', + headless: CI, //Only if running locally + ignoreHTTPSErrors: true, + screenshot: 'off', + trace: 'on-first-retry', + video: 'off' + }, + projects: [ + { + name: 'chrome', + use: { + browserName: 'chromium' + } + } + ], + reporter: [ + ['list'], + ['junit', { outputFile: 'test-results/results.xml' }], + ['json', { outputFile: 'test-results/results.json' }] + ] +}; + +module.exports = config; diff --git a/e2e/playwright-visual.config.js b/e2e/playwright-visual.config.js index 7f6df513fc..55570b0493 100644 --- a/e2e/playwright-visual.config.js +++ b/e2e/playwright-visual.config.js @@ -4,29 +4,28 @@ /** @type {import('@playwright/test').PlaywrightTestConfig} */ const config = { - retries: 0, - testDir: 'tests', + retries: 0, // visual tests should never retry due to snapshot comparison errors + testDir: 'tests/visual', timeout: 90 * 1000, - workers: 1, + workers: 1, // visual tests should never run in parallel due to test pollution webServer: { - command: 'npm run start', - port: 8080, + command: 'cross-env NODE_ENV=test npm run start', + url: 'http://localhost:8080/#', timeout: 200 * 1000, reuseExistingServer: !process.env.CI }, use: { browserName: "chromium", baseURL: 'http://localhost:8080/', - headless: true, + headless: true, // this needs to remain headless to avoid visual changes due to GPU ignoreHTTPSErrors: true, screenshot: 'on', trace: 'off', - video: 'on' + video: 'off' }, reporter: [ ['list'], - ['junit', { outputFile: 'test-results/results.xml' }], - ['allure-playwright'] + ['junit', { outputFile: 'test-results/results.xml' }] ] }; diff --git a/e2e/test-data/PerformanceDisplayLayout.json b/e2e/test-data/PerformanceDisplayLayout.json new file mode 100644 index 0000000000..de81d7b4ca --- /dev/null +++ b/e2e/test-data/PerformanceDisplayLayout.json @@ -0,0 +1 @@ +{"openmct":{"b3cee102-86dd-4c0a-8eec-4d5d276f8691":{"identifier":{"key":"b3cee102-86dd-4c0a-8eec-4d5d276f8691","namespace":""},"name":"Performance Display Layout","type":"layout","composition":[{"key":"9666e7b4-be0c-47a5-94b8-99accad7155e","namespace":""}],"configuration":{"items":[{"width":32,"height":18,"x":12,"y":9,"identifier":{"key":"9666e7b4-be0c-47a5-94b8-99accad7155e","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"23ca351d-a67d-46aa-a762-290eb742d2f1"}],"layoutGrid":[10,10]},"modified":1654299875432,"location":"mine","persisted":1654299878751},"9666e7b4-be0c-47a5-94b8-99accad7155e":{"identifier":{"key":"9666e7b4-be0c-47a5-94b8-99accad7155e","namespace":""},"name":"Performance Example Imagery","type":"example.imagery","configuration":{"imageLocation":"","imageLoadDelayInMilliSeconds":20000,"imageSamples":[],"layers":[{"source":"dist/imagery/example-imagery-layer-16x9.png","name":"16:9","visible":false},{"source":"dist/imagery/example-imagery-layer-safe.png","name":"Safe","visible":false},{"source":"dist/imagery/example-imagery-layer-scale.png","name":"Scale","visible":false}]},"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},"layers":[{"source":"dist/imagery/example-imagery-layer-16x9.png","name":"16:9"},{"source":"dist/imagery/example-imagery-layer-safe.png","name":"Safe"},{"source":"dist/imagery/example-imagery-layer-scale.png","name":"Scale"}]},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1}}]},"modified":1654299840077,"location":"b3cee102-86dd-4c0a-8eec-4d5d276f8691","persisted":1654299840078}},"rootId":"b3cee102-86dd-4c0a-8eec-4d5d276f8691"} \ No newline at end of file diff --git a/e2e/test-data/PerformanceNotebook.json b/e2e/test-data/PerformanceNotebook.json new file mode 100644 index 0000000000..ae08431874 --- /dev/null +++ b/e2e/test-data/PerformanceNotebook.json @@ -0,0 +1 @@ +{"openmct":{"6d2fa9fd-f2aa-461a-a1e1-164ac44bec9d":{"identifier":{"key":"6d2fa9fd-f2aa-461a-a1e1-164ac44bec9d","namespace":""},"name":"Performance Notebook","type":"notebook","configuration":{"defaultSort":"oldest","entries":{"3e31c412-33ba-4757-8ade-e9821f6ba321":{"8c8f6035-631c-45af-8c24-786c60295335":[{"id":"entry-1652815305457","createdOn":1652815305457,"createdBy":"","text":"Existing Entry 1","embeds":[]},{"id":"entry-1652815313465","createdOn":1652815313465,"createdBy":"","text":"Existing Entry 2","embeds":[]},{"id":"entry-1652815399955","createdOn":1652815399955,"createdBy":"","text":"Existing Entry 3","embeds":[]}]}},"imageMigrationVer":"v1","pageTitle":"Page","sections":[{"id":"3e31c412-33ba-4757-8ade-e9821f6ba321","isDefault":false,"isSelected":false,"name":"Section1","pages":[{"id":"8c8f6035-631c-45af-8c24-786c60295335","isDefault":false,"isSelected":false,"name":"Page1","pageTitle":"Page"},{"id":"36555942-c9aa-439c-bbdb-0aaf50db50f5","isDefault":false,"isSelected":false,"name":"Page2","pageTitle":"Page"}],"sectionTitle":"Section"},{"id":"dab0bd1d-2c5a-405c-987f-107123d6189a","isDefault":false,"isSelected":true,"name":"Section2","pages":[{"id":"f625a86a-cb99-4898-8082-80543c8de534","isDefault":false,"isSelected":false,"name":"Page1","pageTitle":"Page"},{"id":"e77ef810-f785-42a7-942e-07e999b79c59","isDefault":false,"isSelected":true,"name":"Page2","pageTitle":"Page"}],"sectionTitle":"Section"}],"sectionTitle":"Section","type":"General","showTime":"0"},"modified":1652815915219,"location":"mine","persisted":1652815915222}},"rootId":"6d2fa9fd-f2aa-461a-a1e1-164ac44bec9d"} \ No newline at end of file diff --git a/e2e/test-data/VisualTestData_storage.json b/e2e/test-data/VisualTestData_storage.json new file mode 100644 index 0000000000..d059303264 --- /dev/null +++ b/e2e/test-data/VisualTestData_storage.json @@ -0,0 +1,22 @@ +{ + "cookies": [], + "origins": [ + { + "origin": "http://localhost:8080", + "localStorage": [ + { + "name": "tcHistory", + "value": "{\"utc\":[{\"start\":1654548551471,\"end\":1654550351471}]}" + }, + { + "name": "mct", + "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"527856c0-cced-4b64-bb19-f943432326d0\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1654550352296,\"modified\":1654550352296},\"527856c0-cced-4b64-bb19-f943432326d0\":{\"identifier\":{\"key\":\"527856c0-cced-4b64-bb19-f943432326d0\",\"namespace\":\"\"},\"name\":\"Unnamed Overlay Plot\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"ce88ce37-8bb9-45e1-a85b-bb7e3c8453b9\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"ce88ce37-8bb9-45e1-a85b-bb7e3c8453b9\",\"namespace\":\"\"}}],\"yAxis\":{},\"xAxis\":{}},\"modified\":1654550353356,\"location\":\"mine\",\"persisted\":1654550353357},\"ce88ce37-8bb9-45e1-a85b-bb7e3c8453b9\":{\"name\":\"Unnamed Sine Wave Generator\",\"type\":\"generator\",\"identifier\":{\"key\":\"ce88ce37-8bb9-45e1-a85b-bb7e3c8453b9\",\"namespace\":\"\"},\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":\"5000\"},\"modified\":1654550353350,\"location\":\"527856c0-cced-4b64-bb19-f943432326d0\",\"persisted\":1654550353350}}" + }, + { + "name": "mct-tree-expanded", + "value": "[\"/browse/mine\"]" + } + ] + } + ] +} \ No newline at end of file diff --git a/e2e/test-data/recycled_local_storage.json b/e2e/test-data/recycled_local_storage.json new file mode 100644 index 0000000000..5026bca3bb --- /dev/null +++ b/e2e/test-data/recycled_local_storage.json @@ -0,0 +1,22 @@ +{ + "cookies": [], + "origins": [ + { + "origin": "http://localhost:8080", + "localStorage": [ + { + "name": "tcHistory", + "value": "{\"utc\":[{\"start\":1654537164464,\"end\":1654538964464},{\"start\":1652301954635,\"end\":1652303754635}]}" + }, + { + "name": "mct", + "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1654538965703,\"modified\":1654538965703},\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"73f2d9ae-d1f3-4561-b7fc-ecd5df557249\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1652303755999,\"location\":\"mine\",\"persisted\":1652303756002},\"2d02a680-eb7e-4645-bba2-dd298f76efb8\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"4291d80c-303c-4d8d-85e1-10f012b864fb\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1654538965702,\"location\":\"mine\",\"persisted\":1654538965702}}" + }, + { + "name": "mct-tree-expanded", + "value": "[]" + } + ] + } + ] +} \ No newline at end of file diff --git a/e2e/tests/api/forms/forms.e2e.spec.js b/e2e/tests/api/forms/forms.e2e.spec.js new file mode 100644 index 0000000000..0e5be59c23 --- /dev/null +++ b/e2e/tests/api/forms/forms.e2e.spec.js @@ -0,0 +1,77 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to tests which verify form functionality. +*/ + +const { test, expect } = require('@playwright/test'); + +const TEST_FOLDER = 'test folder'; + +test.describe('forms set', () => { + test('New folder form has title as required field', async ({ page }) => { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Click button:has-text("Create") + await page.click('button:has-text("Create")'); + // Click :nth-match(:text("Folder"), 2) + await page.click(':nth-match(:text("Folder"), 2)'); + // Click text=Properties Title Notes >> input[type="text"] + await page.click('text=Properties Title Notes >> input[type="text"]'); + // Fill text=Properties Title Notes >> input[type="text"] + await page.fill('text=Properties Title Notes >> input[type="text"]', ''); + // Press Tab + await page.press('text=Properties Title Notes >> input[type="text"]', 'Tab'); + + const okButton = page.locator('text=OK'); + + await expect(okButton).toBeDisabled(); + await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/invalid/); + + // Click text=Properties Title Notes >> input[type="text"] + await page.click('text=Properties Title Notes >> input[type="text"]'); + // Fill text=Properties Title Notes >> input[type="text"] + await page.fill('text=Properties Title Notes >> input[type="text"]', TEST_FOLDER); + // Press Tab + await page.press('text=Properties Title Notes >> input[type="text"]', 'Tab'); + + await expect(page.locator('.c-form-row__state-indicator').first()).not.toHaveClass(/invalid/); + + // Click text=OK + await Promise.all([ + page.waitForNavigation(), + page.click('text=OK') + ]); + + await expect(page.locator('.l-browse-bar__object-name')).toContainText(TEST_FOLDER); + }); + test.fixme('Create all object types and verify correctness', async ({ page }) => { + //Create the following Domain Objects with their unique Object Types + // Sine Wave Generator (number object) + // Timer Object + // Plan View Object + // Clock Object + // Hyperlink + }); +}); diff --git a/e2e/tests/branding.e2e.spec.js b/e2e/tests/branding.e2e.spec.js new file mode 100644 index 0000000000..b543abf864 --- /dev/null +++ b/e2e/tests/branding.e2e.spec.js @@ -0,0 +1,64 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to tests which verify branding related components. +*/ + +const { test } = require('../fixtures.js'); +const { expect } = require('@playwright/test'); + +test.describe('Branding tests', () => { + test('About Modal launches with basic branding properties', async ({ page }) => { + // Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Click About button + await page.click('.l-shell__app-logo'); + + // Verify that the NASA Logo Appears + await expect(page.locator('.c-about__image')).toBeVisible(); + + // Modify the Build information in 'about' Modal + const versionInformationLocator = page.locator('ul.t-info.l-info.s-info'); + await expect(versionInformationLocator).toBeEnabled(); + await expect.soft(versionInformationLocator).toContainText(/Version: \d/); + await expect.soft(versionInformationLocator).toContainText(/Build Date: ((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun))/); + await expect.soft(versionInformationLocator).toContainText(/Revision: \b[0-9a-f]{5,40}\b/); + await expect.soft(versionInformationLocator).toContainText(/Branch: ./); + }); + test('Verify Links in About Modal', async ({ page }) => { + // Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Click About button + await page.click('.l-shell__app-logo'); + + // Verify that clicking on the third party licenses information opens up another tab on licenses url + const [page2] = await Promise.all([ + page.waitForEvent('popup'), + page.locator('text=click here for third party licensing information').click() + ]); + await page2.waitForLoadState('networkidle'); //Avoids timing issues with juggler/firefox + expect(page2.waitForURL('**/licenses**')).toBeTruthy(); + }); +}); diff --git a/e2e/tests/example/eventGenerator.e2e.spec.js b/e2e/tests/example/eventGenerator.e2e.spec.js new file mode 100644 index 0000000000..c94d652af4 --- /dev/null +++ b/e2e/tests/example/eventGenerator.e2e.spec.js @@ -0,0 +1,63 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to tests which verify the basic operations surrounding the example event generator. +*/ + +const { test } = require('../../fixtures.js'); +const { expect } = require('@playwright/test'); + +test.describe('Example Event Generator Operations', () => { + test('Can create example event generator with a name', async ({ page }) => { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + // let's make an event generator + await page.locator('button:has-text("Create")').click(); + // Click li:has-text("Event Message Generator") + await page.locator('li:has-text("Event Message Generator")').click(); + // Click text=Properties Title Notes >> input[type="text"] + await page.locator('text=Properties Title Notes >> input[type="text"]').click(); + // Fill text=Properties Title Notes >> input[type="text"] + await page.locator('text=Properties Title Notes >> input[type="text"]').fill('Test Event Generator'); + // Press Enter + await page.locator('text=Properties Title Notes >> input[type="text"]').press('Enter'); + // Click text=OK + await Promise.all([ + page.waitForNavigation({ url: /.*&view=table/ }), + page.locator('text=OK').click() + ]); + + await expect(page.locator('.l-browse-bar__object-name')).toContainText('Test Event Generator'); + // Click button:has-text("Fixed Timespan") + await page.locator('button:has-text("Fixed Timespan")').click(); + }); + + test.fixme('telemetry is coming in for test event', async ({ page }) => { + // Go to object created in step one + // Verify the telemetry table is filled with > 1 row + }); + test.fixme('telemetry is sorted by time ascending', async ({ page }) => { + // Go to object created in step one + // Verify the telemetry table has a class with "is-sorting asc" + }); +}); diff --git a/e2e/tests/example/generator/SinewaveLimitProvider.e2e.spec.js b/e2e/tests/example/generator/SinewaveLimitProvider.e2e.spec.js new file mode 100644 index 0000000000..972e410dfc --- /dev/null +++ b/e2e/tests/example/generator/SinewaveLimitProvider.e2e.spec.js @@ -0,0 +1,119 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to tests which verify the basic operations surrounding conditionSets. +*/ + +const { test } = require('../../../fixtures.js'); +const { expect } = require('@playwright/test'); + +test.describe('Sine Wave Generator', () => { + test('Create new Sine Wave Generator Object and validate create Form Logic', async ({ page, browserName }) => { + test.fixme(browserName === 'firefox', 'This test needs to be updated to work with firefox'); + + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + //Click the Create button + await page.click('button:has-text("Create")'); + + // Click Sine Wave Generator + await page.click('text=Sine Wave Generator'); + + // Verify that the each required field has required indicator + // Title + await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/req/); + + // Verify that the Notes row does not have a required indicator + await expect(page.locator('.c-form__section div:nth-child(3) .form-row .c-form-row__state-indicator')).not.toContain('.req'); + await page.locator('textarea[type="text"]').fill('Optional Note Text'); + + // Period + await expect(page.locator('div:nth-child(4) .c-form-row__state-indicator')).toHaveClass(/req/); + + // Amplitude + await expect(page.locator('div:nth-child(5) .c-form-row__state-indicator')).toHaveClass(/req/); + + // Offset + await expect(page.locator('div:nth-child(6) .c-form-row__state-indicator')).toHaveClass(/req/); + + // Data Rate + await expect(page.locator('div:nth-child(7) .c-form-row__state-indicator')).toHaveClass(/req/); + + // Phase + await expect(page.locator('div:nth-child(8) .c-form-row__state-indicator')).toHaveClass(/req/); + + // Randomness + await expect(page.locator('div:nth-child(9) .c-form-row__state-indicator')).toHaveClass(/req/); + + // Verify that by removing value from required text field shows invalid indicator + await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').fill(''); + await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/invalid/); + + // Verify that by adding value to empty required text field changes invalid to valid indicator + await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').fill('New Sine Wave Generator'); + await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/valid/); + + // Verify that by removing value from required number field shows invalid indicator + await page.locator('.field.control.l-input-sm input').first().fill(''); + await expect(page.locator('div:nth-child(4) .c-form-row__state-indicator')).toHaveClass(/invalid/); + + // Verify that by adding value to empty required number field changes invalid to valid indicator + await page.locator('.field.control.l-input-sm input').first().fill('3'); + await expect(page.locator('div:nth-child(4) .c-form-row__state-indicator')).toHaveClass(/valid/); + + // Verify that can change value of number field by up/down arrows keys + // Click .field.control.l-input-sm input >> nth=0 + await page.locator('.field.control.l-input-sm input').first().click(); + // Press ArrowUp 3 times to change value from 3 to 6 + await page.locator('.field.control.l-input-sm input').first().press('ArrowUp'); + await page.locator('.field.control.l-input-sm input').first().press('ArrowUp'); + await page.locator('.field.control.l-input-sm input').first().press('ArrowUp'); + + const value = await page.locator('.field.control.l-input-sm input').first().inputValue(); + await expect(value).toBe('6'); + + //Click text=OK + await Promise.all([ + page.waitForNavigation(), + page.click('text=OK') + ]); + + // Verify that the Sine Wave Generator is displayed and correct + // Verify object properties + await expect(page.locator('.l-browse-bar__object-name')).toContainText('New Sine Wave Generator'); + + // Verify canvas rendered and can be interacted with + await page.locator('canvas').nth(1).click({ + position: { + x: 341, + y: 28 + } + }); + + // Verify that where we click on canvas shows the number we clicked on + // Note that any number will do, we just care that a number exists + await expect(page.locator('.value-to-display-nearestValue')).toContainText(/[+-]?([0-9]*[.])?[0-9]+/); + + }); +}); diff --git a/e2e/tests/framework.e2e.spec.js b/e2e/tests/framework.e2e.spec.js new file mode 100644 index 0000000000..b262ddc316 --- /dev/null +++ b/e2e/tests/framework.e2e.spec.js @@ -0,0 +1,55 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to testing our use of the playwright framework as it +relates to how we've extended it (i.e. ./e2e/fixtures.js) and assumptions made in our dev environment +(app.js and ./e2e/webpack-dev-middleware.js) +*/ + +const { test } = require('../fixtures.js'); + +test.describe('fixtures.js tests', () => { + test('Verify that tests fail if console.error is thrown', async ({ page }) => { + test.fail(); + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + //Verify that ../fixtures.js detects console log errors + await Promise.all([ + page.evaluate(() => console.error('This should result in a failure')), + page.waitForEvent('console') // always wait for the event to happen while triggering it! + ]); + + }); + test('Verify that tests pass if console.warn is thrown', async ({ page }) => { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + //Verify that ../fixtures.js detects console log errors + await Promise.all([ + page.evaluate(() => console.warn('This should result in a pass')), + page.waitForEvent('console') // always wait for the event to happen while triggering it! + ]); + + }); +}); diff --git a/e2e/tests/moveObjects.e2e.spec.js b/e2e/tests/moveObjects.e2e.spec.js new file mode 100644 index 0000000000..e7afc2f68c --- /dev/null +++ b/e2e/tests/moveObjects.e2e.spec.js @@ -0,0 +1,137 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to tests which verify the basic operations surrounding moving objects. +*/ + +const { test } = require('../fixtures.js'); +const { expect } = require('@playwright/test'); + +test.describe('Move item tests', () => { + test('Create a basic object and verify that it can be moved to another folder', async ({ page }) => { + // Go to Open MCT + await page.goto('/'); + + // Create a new folder in the root my items folder + let folder1 = "Folder1"; + await page.locator('button:has-text("Create")').click(); + await page.locator('li.icon-folder').click(); + + await page.locator('text=Properties Title Notes >> input[type="text"]').click(); + await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder1); + + await Promise.all([ + page.waitForNavigation(), + page.locator('text=OK').click(), + page.waitForSelector('.c-message-banner__message') + ]); + //Wait until Save Banner is gone + await page.locator('.c-message-banner__close-button').click(); + await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); + + // Create another folder with a new name at default location, which is currently inside Folder 1 + let folder2 = "Folder2"; + await page.locator('button:has-text("Create")').click(); + await page.locator('li.icon-folder').click(); + await page.locator('text=Properties Title Notes >> input[type="text"]').click(); + await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder2); + + await Promise.all([ + page.waitForNavigation(), + page.locator('text=OK').click(), + page.waitForSelector('.c-message-banner__message') + ]); + //Wait until Save Banner is gone + await page.locator('.c-message-banner__close-button').click(); + await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); + + // Move Folder 2 from Folder 1 to My Items + await page.locator('text=Open MCT My Items >> span').nth(3).click(); + await page.locator('.c-tree__scrollable div div:nth-child(2) .c-tree__item .c-tree__item__view-control').click(); + + await page.locator(`a:has-text("${folder2}")`).click({ + button: 'right' + }); + await page.locator('li.icon-move').click(); + await page.locator('form[name="mctForm"] >> text=My Items').click(); + + await page.locator('text=OK').click(); + + // Expect that Folder 2 is in My Items, the root folder + expect(page.locator(`text=My Items >> nth=0:has(text=${folder2})`)).toBeTruthy(); + }); + test('Create a basic object and verify that it cannot be moved to telemetry object without Composition Provider', async ({ page }) => { + // Go to Open MCT + await page.goto('/'); + + // Create Telemetry Table + let telemetryTable = 'Test Telemetry Table'; + await page.locator('button:has-text("Create")').click(); + await page.locator('li:has-text("Telemetry Table")').click(); + await page.locator('text=Properties Title Notes >> input[type="text"]').click(); + await page.locator('text=Properties Title Notes >> input[type="text"]').fill(telemetryTable); + + await page.locator('text=OK').click(); + + // Finish editing and save Telemetry Table + await page.locator('.c-button--menu.c-button--major.icon-save').click(); + await page.locator('text=Save and Finish Editing').click(); + + // Create New Folder Basic Domain Object + let folder = 'Test Folder'; + await page.locator('button:has-text("Create")').click(); + await page.locator('li:has-text("Folder")').click(); + await page.locator('text=Properties Title Notes >> input[type="text"]').click(); + await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder); + + // See if it's possible to put the folder in the Telemetry object during creation (Soft Assert) + await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click(); + let okButton = await page.locator('button.c-button.c-button--major:has-text("OK")'); + let okButtonStateDisabled = await okButton.isDisabled(); + expect.soft(okButtonStateDisabled).toBeTruthy(); + + // Continue test regardless of assertion and create it in My Items + await page.locator('form[name="mctForm"] >> text=My Items').click(); + await page.locator('text=OK').click(); + + // Open My Items + await page.locator('text=Open MCT My Items >> span').nth(3).click(); + + // Select Folder Object and select Move from context menu + await Promise.all([ + page.waitForNavigation(), + page.locator(`a:has-text("${folder}")`).click() + ]); + await page.locator('.c-tree__item.is-navigated-object .c-tree__item__label .c-tree__item__type-icon').click({ + button: 'right' + }); + await page.locator('li.icon-move').click(); + + // See if it's possible to put the folder in the Telemetry object after creation + await page.locator('text=Location Open MCT My Items >> span').nth(3).click(); + await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click(); + let okButton2 = await page.locator('button.c-button.c-button--major:has-text("OK")'); + let okButtonStateDisabled2 = await okButton2.isDisabled(); + expect(okButtonStateDisabled2).toBeTruthy(); + }); +}); diff --git a/e2e/tests/performance/imagery.perf.spec.js b/e2e/tests/performance/imagery.perf.spec.js new file mode 100644 index 0000000000..433bc1699d --- /dev/null +++ b/e2e/tests/performance/imagery.perf.spec.js @@ -0,0 +1,177 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to performance tests to ensure that testability of performance +is not broken upstream on Open MCT. Any assumptions made downstream will be tested here + +TODO: + - Update resolution of performance config + - Add Performance Observer on init to push all performance marks + - Move client CDP connection to before or to a fixture + - + +*/ + +const { test, expect } = require('@playwright/test'); + +const filePath = 'e2e/test-data/PerformanceDisplayLayout.json'; + +test.describe('Performance tests', () => { + test.beforeEach(async ({ page, browser }, testInfo) => { + // Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Click a:has-text("My Items") + await page.locator('a:has-text("My Items")').click({ + button: 'right' + }); + + // Click text=Import from JSON + await page.locator('text=Import from JSON').click(); + + // Upload Performance Display Layout.json + await page.setInputFiles('#fileElem', filePath); + + // Click text=OK + await page.locator('text=OK').click(); + + await expect(page.locator('a:has-text("Performance Display Layout Display Layout")')).toBeVisible(); + + //Create a Chrome Performance Timeline trace to store as a test artifact + console.log("\n==== Devtools: startTracing ====\n"); + await browser.startTracing(page, { + path: `${testInfo.outputPath()}-trace.json`, + screenshots: true + }); + }); + test.afterEach(async ({ page, browser}) => { + console.log("\n==== Devtools: stopTracing ====\n"); + await browser.stopTracing(); + + /* Measurement Section + / The following section includes a block of performance measurements. + */ + //Get time difference between viewlarge actionability and evaluate time + await page.evaluate(() => (window.performance.measure("machine-time-difference", "viewlarge.start", "viewLarge.start.test"))); + + //Get StartTime + const startTime = await page.evaluate(() => window.performance.timing.navigationStart); + console.log('window.performance.timing.navigationStart', startTime); + + //Get All Performance Marks + const getAllMarksJson = await page.evaluate(() => JSON.stringify(window.performance.getEntriesByType("mark"))); + const getAllMarks = JSON.parse(getAllMarksJson); + console.log('window.performance.getEntriesByType("mark")', getAllMarks); + + //Get All Performance Measures + const getAllMeasuresJson = await page.evaluate(() => JSON.stringify(window.performance.getEntriesByType("measure"))); + const getAllMeasures = JSON.parse(getAllMeasuresJson); + console.log('window.performance.getEntriesByType("measure")', getAllMeasures); + + }); + /* The following test will navigate to a previously created Performance Display Layout and measure the + / following metrics: + / - ElementResourceTiming + / - Interaction Timing + */ + test('Embedded View Large for Imagery is performant in Fixed Time', async ({ page, browser }) => { + const client = await page.context().newCDPSession(page); + // Tell the DevTools session to record performance metrics + // https://chromedevtools.github.io/devtools-protocol/tot/Performance/#method-getMetrics + await client.send('Performance.enable'); + // Go to baseURL + await page.goto('/'); + + // Search Available after Launch + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); + await page.evaluate(() => window.performance.mark("search-available")); + // Fill Search input + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Performance Display Layout'); + await page.evaluate(() => window.performance.mark("search-entered")); + //Search Result Appears and is clicked + await Promise.all([ + page.waitForNavigation(), + page.locator('a:has-text("Performance Display Layout")').first().click(), + page.evaluate(() => window.performance.mark("click-search-result")) + ]); + + //Time to Example Imagery Frame loads within Display Layout + await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible'}); + //Time to Example Imagery object loads + await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible'}); + + //Get background-image url from background-image css prop + const backgroundImage = await page.locator('.c-imagery__main-image__background-image'); + let backgroundImageUrl = await backgroundImage.evaluate((el) => { + return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1]; + }); + backgroundImageUrl = backgroundImageUrl.slice(1, -1); //forgive me, padre + console.log('backgroundImageurl ' + backgroundImageUrl); + + //Get ResourceTiming of background-image jpg + const resourceTimingJson = await page.evaluate((bgImageUrl) => + JSON.stringify(window.performance.getEntriesByName(bgImageUrl).pop()), + backgroundImageUrl + ); + console.log('resourceTimingJson ' + resourceTimingJson); + + //Open Large view + await page.locator('button:has-text("Large View")').click(); //This action includes the performance.mark named 'viewLarge.start' + await page.evaluate(() => window.performance.mark("viewLarge.start.test")); //This is a mark only to compare evaluate timing + + //Time to Imagery Rendered in Large Frame + await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible'}); + await page.evaluate(() => window.performance.mark("background-image-frame")); + + //Time to Example Imagery object loads + await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible'}); + await page.evaluate(() => window.performance.mark("background-image-visible")); + + // Get Current number of images in thumbstrip + await page.waitForSelector('.c-imagery__thumb'); + const thumbCount = await page.locator('.c-imagery__thumb').count(); + console.log('number of thumbs rendered ' + thumbCount); + await page.locator('.c-imagery__thumb').last().click(); + + //Get ResourceTiming of all jpg resources + const resourceTimingJson2 = await page.evaluate(() => + JSON.stringify(window.performance.getEntriesByType('resource')) + ); + const resourceTiming = JSON.parse(resourceTimingJson2); + const jpgResourceTiming = resourceTiming.find((element) => + element.name.includes('.jpg') + ); + console.log('jpgResourceTiming ' + JSON.stringify(jpgResourceTiming)); + + // Click Close Icon + await page.locator('[aria-label="Close"]').click(); + await page.evaluate(() => window.performance.mark("view-large-close-button")); + + //await client.send('HeapProfiler.enable'); + await client.send('HeapProfiler.collectGarbage'); + + let performanceMetrics = await client.send('Performance.getMetrics'); + console.log(performanceMetrics.metrics); + + }); +}); diff --git a/e2e/tests/performance/memleak-imagery.perf.spec.js b/e2e/tests/performance/memleak-imagery.perf.spec.js new file mode 100644 index 0000000000..6ce14f5533 --- /dev/null +++ b/e2e/tests/performance/memleak-imagery.perf.spec.js @@ -0,0 +1,119 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is an initial example for memory leak testing using performance. This configuration and execution must +be kept separate from the traditional performance measurements to avoid any "observer" effects associated with tracing +or profiling playwright and/or the browser. + +Based on a pattern identified in https://github.com/trentmwillis/devtools-protocol-demos/blob/master/testing-demos/memory-leak-by-heap.js +and https://github.com/paulirish/automated-chrome-profiling/issues/3 + +Best path forward: https://github.com/cowchimp/headless-devtools/blob/master/src/Memory/example.js + +*/ + +const { test, expect } = require('@playwright/test'); + +const filePath = 'e2e/test-data/PerformanceDisplayLayout.json'; + +// eslint-disable-next-line playwright/no-skipped-test +test.describe.skip('Memory Performance tests', () => { + test.beforeEach(async ({ page, browser }, testInfo) => { + // Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Click a:has-text("My Items") + await page.locator('a:has-text("My Items")').click({ + button: 'right' + }); + + // Click text=Import from JSON + await page.locator('text=Import from JSON').click(); + + // Upload Performance Display Layout.json + await page.setInputFiles('#fileElem', filePath); + + // Click text=OK + await page.locator('text=OK').click(); + + await expect(page.locator('a:has-text("Performance Display Layout Display Layout")')).toBeVisible(); + }); + + test('Embedded View Large for Imagery is performant in Fixed Time', async ({ page, browser }) => { + + await page.goto('/', {waitUntil: 'networkidle'}); + + // To to Search Available after Launch + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); + // Fill Search input + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Performance Display Layout'); + //Search Result Appears and is clicked + await Promise.all([ + page.waitForNavigation(), + page.locator('a:has-text("Performance Display Layout")').first().click() + ]); + + //Time to Example Imagery Frame loads within Display Layout + await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible'}); + //Time to Example Imagery object loads + await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible'}); + + const client = await page.context().newCDPSession(page); + await client.send('HeapProfiler.enable'); + await client.send('HeapProfiler.startSampling'); + // await client.send('HeapProfiler.collectGarbage'); + await client.send('Performance.enable'); + + let performanceMetricsBefore = await client.send('Performance.getMetrics'); + console.log(performanceMetricsBefore.metrics); + + //await client.send('Performance.disable'); + + //Open Large view + await page.locator('button:has-text("Large View")').click(); + await client.send('HeapProfiler.takeHeapSnapshot'); + + //Time to Imagery Rendered in Large Frame + await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible'}); + + //Time to Example Imagery object loads + await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible'}); + + // Click Close Icon + await page.locator('.c-click-icon').click(); + + //Time to Example Imagery Frame loads within Display Layout + await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible'}); + //Time to Example Imagery object loads + await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible'}); + + await client.send('HeapProfiler.collectGarbage'); + //await client.send('Performance.enable'); + + let performanceMetricsAfter = await client.send('Performance.getMetrics'); + console.log(performanceMetricsAfter.metrics); + + //await client.send('Performance.disable'); + + }); +}); diff --git a/e2e/tests/performance/notebook.perf.spec.js b/e2e/tests/performance/notebook.perf.spec.js new file mode 100644 index 0000000000..1c10ad6ba5 --- /dev/null +++ b/e2e/tests/performance/notebook.perf.spec.js @@ -0,0 +1,158 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to performance tests to ensure that testability of performance +is not broken upstream on Open MCT. Any assumptions made downstream will be tested here. + +TODO: + - Update resolution of performance config + - Add Performance Observer on init to push all performance marks + - Move client CDP connection to before or to a fixture + +*/ + +const { test, expect } = require('@playwright/test'); + +const notebookFilePath = 'e2e/test-data/PerformanceNotebook.json'; + +test.describe('Performance tests', () => { + test.beforeEach(async ({ page, browser }, testInfo) => { + // Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Click a:has-text("My Items") + await page.locator('a:has-text("My Items")').click({ + button: 'right' + }); + + // Click text=Import from JSON + await page.locator('text=Import from JSON').click(); + + // Upload Performance Display Layout.json + await page.setInputFiles('#fileElem', notebookFilePath); + + // TODO Fix this + await page.locator('text=OK >> nth=1').click(); + + await expect(page.locator('a:has-text("Performance Notebook")')).toBeVisible(); + + //Create a Chrome Performance Timeline trace to store as a test artifact + console.log("\n==== Devtools: startTracing ====\n"); + await browser.startTracing(page, { + path: `${testInfo.outputPath()}-trace.json`, + screenshots: true + }); + }); + test.afterEach(async ({ page, browser}) => { + console.log("\n==== Devtools: stopTracing ====\n"); + await browser.stopTracing(); + + /* Measurement Section + / The following section includes a block of performance measurements. + */ + const startTime = await page.evaluate(() => window.performance.timing.navigationStart); + console.log('window.performance.timing.navigationStart', startTime); + + //Get All Performance Marks + const getAllMarksJson = await page.evaluate(() => JSON.stringify(window.performance.getEntriesByType("mark"))); + const getAllMarks = JSON.parse(getAllMarksJson); + console.log('window.performance.getEntriesByType("mark")', getAllMarks); + + //Get All Performance Measures + const getAllMeasuresJson = await page.evaluate(() => JSON.stringify(window.performance.getEntriesByType("measure"))); + const getAllMeasures = JSON.parse(getAllMeasuresJson); + console.log('window.performance.getEntriesByType("measure")', getAllMeasures); + + }); + /* The following test will navigate to a previously created Performance Display Layout and measure the + / following metrics: + / - ElementResourceTiming + / - Interaction Timing + */ + test('Notebook Search, Add Entry, Update Entry are performant', async ({ page, browser }) => { + const client = await page.context().newCDPSession(page); + // Tell the DevTools session to record performance metrics + // https://chromedevtools.github.io/devtools-protocol/tot/Performance/#method-getMetrics + await client.send('Performance.enable'); + // Go to baseURL + await page.goto('/'); + + // To to Search Available after Launch + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); + await page.evaluate(() => window.performance.mark("search-available")); + // Fill Search input + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Performance Notebook'); + await page.evaluate(() => window.performance.mark("search-entered")); + //Search Result Appears and is clicked + await Promise.all([ + page.waitForNavigation(), + page.locator('a:has-text("Performance Notebook")').first().click(), + page.evaluate(() => window.performance.mark("click-search-result")) + ]); + + await page.waitForSelector('.c-tree__item c-tree-and-search__loading loading', {state: 'hidden'}); + await page.evaluate(() => window.performance.mark("search-spinner-gone")); + + await page.waitForSelector('.l-browse-bar__object-name', { state: 'visible'}); + await page.evaluate(() => window.performance.mark("object-title-appears")); + + await page.waitForSelector('.c-notebook__entry >> nth=0', { state: 'visible'}); + await page.evaluate(() => window.performance.mark("notebook-entry-appears")); + + // Click Add new Notebook Entry + await page.locator('.c-notebook__drag-area').click(); + await page.evaluate(() => window.performance.mark("new-notebook-entry-created")); + + // Enter Notebook Entry text + await page.locator('div.c-ne__text').last().fill('New Entry'); + await page.keyboard.press('Enter'); + await page.evaluate(() => window.performance.mark("new-notebook-entry-filled")); + + //Individual Notebook Entry Search + await page.evaluate(() => window.performance.mark("notebook-search-start")); + await page.locator('.c-notebook__search >> input').fill('Existing Entry'); + await page.evaluate(() => window.performance.mark("notebook-search-filled")); + await page.waitForSelector('text=Search Results (3)', { state: 'visible'}); + await page.evaluate(() => window.performance.mark("notebook-search-processed")); + await page.waitForSelector('.c-notebook__entry >> nth=2', { state: 'visible'}); + await page.evaluate(() => window.performance.mark("notebook-search-processed")); + + //Clear Search + await page.locator('.c-search.c-notebook__search .c-search__clear-input').click(); + await page.evaluate(() => window.performance.mark("notebook-search-processed")); + + // Hover on Last + await page.evaluate(() => window.performance.mark("new-notebook-entry-delete")); + await page.locator('div.c-ne__time-and-content').last().hover(); + await page.locator('button[title="Delete this entry"]').last().click(); + await page.locator('button:has-text("Ok")').click(); + await page.waitForSelector('.c-notebook__entry >> nth=3', { state: 'detached'}); + await page.evaluate(() => window.performance.mark("new-notebook-entry-deleted")); + + //await client.send('HeapProfiler.enable'); + await client.send('HeapProfiler.collectGarbage'); + + let performanceMetrics = await client.send('Performance.getMetrics'); + console.log(performanceMetrics.metrics); + }); +}); diff --git a/e2e/tests/persistence/addNoneditableObject.js b/e2e/tests/persistence/addNoneditableObject.js new file mode 100644 index 0000000000..55da25358c --- /dev/null +++ b/e2e/tests/persistence/addNoneditableObject.js @@ -0,0 +1,27 @@ +(function () { + document.addEventListener('DOMContentLoaded', () => { + const PERSISTENCE_KEY = 'persistence-tests'; + const openmct = window.openmct; + + openmct.objects.addRoot({ + namespace: PERSISTENCE_KEY, + key: PERSISTENCE_KEY + }); + + openmct.objects.addProvider(PERSISTENCE_KEY, { + get(identifier) { + if (identifier.key !== PERSISTENCE_KEY) { + return undefined; + } else { + return Promise.resolve({ + identifier, + type: 'folder', + name: 'Persistence Testing', + location: 'ROOT', + composition: [] + }); + } + } + }); + }); +}()); diff --git a/e2e/tests/persistence/persistability.e2e.spec.js b/e2e/tests/persistence/persistability.e2e.spec.js new file mode 100644 index 0000000000..aa7c0b9d73 --- /dev/null +++ b/e2e/tests/persistence/persistability.e2e.spec.js @@ -0,0 +1,80 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to tests which verify the basic operations surrounding conditionSets. +*/ + +const { test } = require('../../fixtures.js'); +const { expect } = require('@playwright/test'); +const path = require('path'); + +test.describe('Persistence operations @addInit', () => { + // add non persistable root item + test.beforeEach(async ({ page }) => { + // eslint-disable-next-line no-undef + await page.addInitScript({ path: path.join(__dirname, 'addNoneditableObject.js') }); + }); + + test('Persistability should be respected in the create form location field', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/nasa/openmct/issues/4323' + }); + // Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Click the Create button + await page.click('button:has-text("Create")'); + + // Click text=Condition Set + await page.click('text=Condition Set'); + + // Click form[name="mctForm"] >> text=Persistence Testing + await page.locator('form[name="mctForm"] >> text=Persistence Testing').click(); + + // Check that "OK" button is disabled + const okButton = page.locator('button:has-text("OK")'); + await expect(okButton).toBeDisabled(); + }); + test('Non-persistable objects should not show persistence related actions', async ({ page }) => { + // Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Click text=Persistence Testing >> nth=0 + await page.locator('text=Persistence Testing').first().click({ + button: 'right' + }); + + const menuOptions = page.locator('.c-menu ul'); + + await expect.soft(menuOptions).toContainText(['Open In New Tab', 'View', 'Create Link']); + await expect(menuOptions).not.toContainText(['Move', 'Duplicate', 'Remove', 'Add New Folder', 'Edit Properties...', 'Export as JSON', 'Import from JSON']); + }); + test.fixme('Cannot move a previously created domain object to non-peristable boject in Move Modal', async ({ page }) => { + //Create a domain object + //Save Domain object + //Move Object and verify that cannot select non-persistable object + //Move Object to My Items + //Verify successful move + }); +}); diff --git a/e2e/tests/plugins/ExportAsJSON/exportAsJson.e2e.spec.js b/e2e/tests/plugins/ExportAsJSON/exportAsJson.e2e.spec.js new file mode 100644 index 0000000000..6da6c0287f --- /dev/null +++ b/e2e/tests/plugins/ExportAsJSON/exportAsJson.e2e.spec.js @@ -0,0 +1,51 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to tests which verify the basic operations surrounding exportAsJSON. +*/ + +const { test } = require('../../../fixtures.js'); +// FIXME: Remove this eslint exception once tests are implemented +// eslint-disable-next-line no-unused-vars +const { expect } = require('@playwright/test'); + +test.describe('ExportAsJSON', () => { + test.fixme('Create a basic object and verify that it can be exported as JSON from Tree', async ({ page }) => { + //Create domain object + //Save Domain Object + //Verify that the newly created domain object can be exported as JSON from the Tree + }); + test.fixme('Create a basic object and verify that it can be exported as JSON from 3 dot menu', async ({ page }) => { + //Create domain object + //Save Domain Object + //Verify that the newly created domain object can be exported as JSON from the 3 dot menu + }); + test.fixme('Verify that a nested Object can be exported as JSON', async ({ page }) => { + // Create 2 objects with hierarchy + // Export as JSON + // Verify Hiearchy + }); + test.fixme('Verify that the ExportAsJSON dropdown does not appear for the item X', async ({ page }) => { + // Other than non-persistible objects + }); +}); diff --git a/e2e/tests/plugins/ImportAsJSON/importAsJson.e2e.spec.js b/e2e/tests/plugins/ImportAsJSON/importAsJson.e2e.spec.js new file mode 100644 index 0000000000..6469e8f157 --- /dev/null +++ b/e2e/tests/plugins/ImportAsJSON/importAsJson.e2e.spec.js @@ -0,0 +1,49 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to tests which verify the basic operations surrounding importAsJSON. +*/ + +const { test } = require('../../../fixtures.js'); +// FIXME: Remove this eslint exception once tests are implemented +// eslint-disable-next-line no-unused-vars +const { expect } = require('@playwright/test'); + +test.describe('ExportAsJSON', () => { + test.fixme('Verify that domain object can be importAsJSON from Tree', async ({ page }) => { + //Verify that an testdata JSON file can be imported from Tree + //Verify correctness of imported domain object + }); + test.fixme('Verify that domain object can be importAsJSON from 3 dot menu on folder', async ({ page }) => { + //Verify that an testdata JSON file can be imported from 3 dot menu on folder domain object + //Verify correctness of imported domain object + }); + test.fixme('Verify that a nested Objects can be importAsJSON', async ({ page }) => { + // Testdata with hierarchy + // ImportAsJSON on Tree + // Verify Hierarchy + }); + test.fixme('Verify that the ImportAsJSON dropdown does not appear for the item X', async ({ page }) => { + // Other than non-persistible objects + }); +}); diff --git a/e2e/tests/plugins/clock/Clock.e2e.spec.js b/e2e/tests/plugins/clock/Clock.e2e.spec.js new file mode 100644 index 0000000000..645770a55d --- /dev/null +++ b/e2e/tests/plugins/clock/Clock.e2e.spec.js @@ -0,0 +1,67 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to tests which verify the basic operations surrounding Clock. +*/ + +const { test } = require('../../../fixtures.js'); +const { expect } = require('@playwright/test'); + +test.describe('Clock Generator', () => { + + test('Timezone dropdown will collapse when clicked outside or on dropdown icon again', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/nasa/openmct/issues/4878' + }); + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + //Click the Create button + await page.click('button:has-text("Create")'); + + // Click Clock + await page.click('text=Clock'); + + // Click .icon-arrow-down + await page.locator('.icon-arrow-down').click(); + //verify if the autocomplete dropdown is visible + await expect(page.locator(".c-input--autocomplete__options")).toBeVisible(); + // Click .icon-arrow-down + await page.locator('.icon-arrow-down').click(); + + // Verify clicking on the autocomplete arrow collapses the dropdown + await expect(page.locator(".c-input--autocomplete__options")).not.toBeVisible(); + + // Click timezone input to open dropdown + await page.locator('.c-input--autocomplete__input').click(); + //verify if the autocomplete dropdown is visible + await expect(page.locator(".c-input--autocomplete__options")).toBeVisible(); + + // Verify clicking outside the autocomplete dropdown collapses it + await page.locator('text=Timezone').click(); + // Verify clicking on the autocomplete arrow collapses the dropdown + await expect(page.locator(".c-input--autocomplete__options")).not.toBeVisible(); + + }); +}); diff --git a/e2e/tests/plugins/condition/condition.e2e.spec.js b/e2e/tests/plugins/condition/condition.e2e.spec.js index d3ecb4be26..9662b36926 100644 --- a/e2e/tests/plugins/condition/condition.e2e.spec.js +++ b/e2e/tests/plugins/condition/condition.e2e.spec.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -21,13 +21,21 @@ *****************************************************************************/ /* -This test suite is dedicated to tests which verify the basic operations surrounding conditionSets. +This test suite is dedicated to tests which verify the basic operations surrounding conditionSets. Note: this +suite is sharing state between tests which is considered an anti-pattern. Implimenting in this way to +demonstrate some playwright for test developers. This pattern should not be re-used in other CRUD suites. */ -const { test, expect } = require('@playwright/test'); +const { test } = require('../../../fixtures.js'); +const { expect } = require('@playwright/test'); -test.describe('condition set', () => { - test('create new button `condition set` creates new condition object', async ({ page }) => { +let conditionSetUrl; +let getConditionSetIdentifierFromUrl; + +test.describe.serial('Condition Set CRUD Operations on @localStorage', () => { + test.beforeAll(async ({ browser}) => { + const context = await browser.newContext(); + const page = await context.newPage(); //Go to baseURL await page.goto('/', { waitUntil: 'networkidle' }); @@ -35,14 +43,139 @@ test.describe('condition set', () => { await page.click('button:has-text("Create")'); // Click text=Condition Set - await page.click('text=Condition Set'); + await page.locator('li:has-text("Condition Set")').click(); // Click text=OK await Promise.all([ - page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/dab945d4-5a84-480e-8180-222b4aa730fa?tc.mode=fixed&tc.startBound=1639696164435&tc.endBound=1639697964435&tc.timeSystem=utc&view=conditionSet.view' }*/), + page.waitForNavigation(), page.click('text=OK') ]); + //Save localStorage for future test execution + await context.storageState({ path: './e2e/test-data/recycled_local_storage.json' }); + + //Set object identifier from url + conditionSetUrl = page.url(); + console.log('conditionSetUrl ' + conditionSetUrl); + + getConditionSetIdentifierFromUrl = conditionSetUrl.split('/').pop().split('?')[0]; + console.debug('getConditionSetIdentifierFromUrl ' + getConditionSetIdentifierFromUrl); + }); + + //Load localStorage for subsequent tests + test.use({ storageState: './e2e/test-data/recycled_local_storage.json' }); + //Begin suite of tests again localStorage + test('Condition set object properties persist in main view and inspector @localStorage', async ({ page }) => { + //Navigate to baseURL with injected localStorage + await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); + + //Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto() + await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); + + //Assertions on loaded Condition Set in Inspector + expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy(); + + //Reload Page + await Promise.all([ + page.reload(), + page.waitForLoadState('networkidle') + ]); + + //Re-verify after reload + await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); + //Assertions on loaded Condition Set in Inspector + expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy(); + + }); + test('condition set object can be modified on @localStorage', async ({ page }) => { + await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); + + //Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto() + await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); + + //Update the Condition Set properties + // Click Edit Button + await page.locator('text=Conditions View Snapshot >> button').nth(3).click(); + + //Edit Condition Set Name from main view + await page.locator('text=Unnamed Condition Set').first().fill('Renamed Condition Set'); + await page.locator('text=Renamed Condition Set').first().press('Enter'); + // Click Save Button + await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); + // Click Save and Finish Editing Option + await page.locator('text=Save and Finish Editing').click(); + + //Verify Main section reflects updated Name Property + await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Renamed Condition Set'); + + // Verify Inspector properties + // Verify Inspector has updated Name property + expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy(); + // Verify Inspector Details has updated Name property + expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy(); + + // Verify Tree reflects updated Name proprety + // Expand Tree + await page.locator('text=Open MCT My Items >> span >> nth=3').click(); + // Verify Condition Set Object is renamed in Tree + expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); + // Verify Search Tree reflects renamed Name property + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Renamed'); + expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); + + //Reload Page + await Promise.all([ + page.reload(), + page.waitForLoadState('networkidle') + ]); + + //Verify Main section reflects updated Name Property + await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Renamed Condition Set'); + + // Verify Inspector properties + // Verify Inspector has updated Name property + expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy(); + // Verify Inspector Details has updated Name property + expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy(); + + // Verify Tree reflects updated Name proprety + // Expand Tree + await page.locator('text=Open MCT My Items >> span >> nth=3').click(); + // Verify Condition Set Object is renamed in Tree + expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); + // Verify Search Tree reflects renamed Name property + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Renamed'); + expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); + }); + test('condition set object can be deleted by Search Tree Actions menu on @localStorage', async ({ page }) => { + //Navigate to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + //Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto() + await expect(page.locator('a:has-text("Unnamed Condition Set Condition Set") >> nth=0')).toBeVisible(); + + const numberOfConditionSetsToStart = await page.locator('a:has-text("Unnamed Condition Set Condition Set")').count(); + + // Search for Unnamed Condition Set + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Unnamed Condition Set'); + // Click Search Result + await page.locator('[aria-label="OpenMCT Search"] >> text=Unnamed Condition Set').first().click(); + // Click hamburger button + await page.locator('[title="More options"]').click(); + + // Click text=Remove + await page.locator('text=Remove').click(); + await page.locator('text=OK').click(); + + //Expect Unnamed Condition Set to be removed in Main View + const numberOfConditionSetsAtEnd = await page.locator('a:has-text("Unnamed Condition Set Condition Set")').count(); + + expect(numberOfConditionSetsAtEnd).toEqual(numberOfConditionSetsToStart - 1); + + //Feature? + //Domain Object is still available by direct URL after delete + await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); + }); }); diff --git a/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js b/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js new file mode 100644 index 0000000000..d4f767be87 --- /dev/null +++ b/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js @@ -0,0 +1,753 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to tests which verify the basic operations surrounding imagery, +but only assume that example imagery is present. +*/ +/* globals process */ + +const { test } = require('../../../fixtures.js'); +const { expect } = require('@playwright/test'); + +const backgroundImageSelector = '.c-imagery__main-image__background-image'; + +//The following block of tests verifies the basic functionality of example imagery and serves as a template for Imagery objects embedded in other objects. +test.describe('Example Imagery Object', () => { + + test.beforeEach(async ({ page }) => { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + //Click the Create button + await page.click('button:has-text("Create")'); + + // Click text=Example Imagery + await page.click('text=Example Imagery'); + + // Click text=OK + await Promise.all([ + page.waitForNavigation({waitUntil: 'networkidle'}), + page.click('text=OK'), + //Wait for Save Banner to appear + page.waitForSelector('.c-message-banner__message') + ]); + // Close Banner + await page.locator('.c-message-banner__close-button').click(); + + //Wait until Save Banner is gone + await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); + await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery'); + await page.locator(backgroundImageSelector).hover({trial: true}); + }); + + test('Can use Mouse Wheel to zoom in and out of latest image', async ({ page }) => { + const deltaYStep = 100; //equivalent to 1x zoom + await page.locator(backgroundImageSelector).hover({trial: true}); + const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox(); + // zoom in + await page.locator(backgroundImageSelector).hover({trial: true}); + await page.mouse.wheel(0, deltaYStep * 2); + // wait for zoom animation to finish + await page.locator(backgroundImageSelector).hover({trial: true}); + const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox(); + // zoom out + await page.locator(backgroundImageSelector).hover({trial: true}); + await page.mouse.wheel(0, -deltaYStep); + // wait for zoom animation to finish + await page.locator(backgroundImageSelector).hover({trial: true}); + const imageMouseZoomedOut = await page.locator(backgroundImageSelector).boundingBox(); + + expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height); + expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width); + expect(imageMouseZoomedOut.height).toBeLessThan(imageMouseZoomedIn.height); + expect(imageMouseZoomedOut.width).toBeLessThan(imageMouseZoomedIn.width); + + }); + + test('Can use alt+drag to move around image once zoomed in', async ({ page }) => { + const deltaYStep = 100; //equivalent to 1x zoom + const panHotkey = process.platform === 'linux' ? ['Control', 'Alt'] : ['Alt']; + + await page.locator(backgroundImageSelector).hover({trial: true}); + + // zoom in + await page.mouse.wheel(0, deltaYStep * 2); + await page.locator(backgroundImageSelector).hover({trial: true}); + const zoomedBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2; + const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2; + // move to the right + + // center the mouse pointer + await page.mouse.move(imageCenterX, imageCenterY); + + //Get Diagnostic info about process environment + console.log('process.platform is ' + process.platform); + const getUA = await page.evaluate(() => navigator.userAgent); + console.log('navigator.userAgent ' + getUA); + // Pan Imagery Hints + const expectedAltText = process.platform === 'linux' ? 'Ctrl+Alt drag to pan' : 'Alt drag to pan'; + const imageryHintsText = await page.locator('.c-imagery__hints').innerText(); + expect(expectedAltText).toEqual(imageryHintsText); + + // pan right + await Promise.all(panHotkey.map(x => page.keyboard.down(x))); + await page.mouse.down(); + await page.mouse.move(imageCenterX - 200, imageCenterY, 10); + await page.mouse.up(); + await Promise.all(panHotkey.map(x => page.keyboard.up(x))); + const afterRightPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + expect(zoomedBoundingBox.x).toBeGreaterThan(afterRightPanBoundingBox.x); + + // pan left + await Promise.all(panHotkey.map(x => page.keyboard.down(x))); + await page.mouse.down(); + await page.mouse.move(imageCenterX, imageCenterY, 10); + await page.mouse.up(); + await Promise.all(panHotkey.map(x => page.keyboard.up(x))); + const afterLeftPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + expect(afterRightPanBoundingBox.x).toBeLessThan(afterLeftPanBoundingBox.x); + + // pan up + await page.mouse.move(imageCenterX, imageCenterY); + await Promise.all(panHotkey.map(x => page.keyboard.down(x))); + await page.mouse.down(); + await page.mouse.move(imageCenterX, imageCenterY + 200, 10); + await page.mouse.up(); + await Promise.all(panHotkey.map(x => page.keyboard.up(x))); + const afterUpPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + expect(afterUpPanBoundingBox.y).toBeGreaterThan(afterLeftPanBoundingBox.y); + + // pan down + await Promise.all(panHotkey.map(x => page.keyboard.down(x))); + await page.mouse.down(); + await page.mouse.move(imageCenterX, imageCenterY - 200, 10); + await page.mouse.up(); + await Promise.all(panHotkey.map(x => page.keyboard.up(x))); + const afterDownPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + expect(afterDownPanBoundingBox.y).toBeLessThan(afterUpPanBoundingBox.y); + + }); + + test('Can use + - buttons to zoom on the image', async ({ page }) => { + await page.locator(backgroundImageSelector).hover({trial: true}); + const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0); + const zoomOutBtn = page.locator('.t-btn-zoom-out').nth(0); + const initialBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + + await zoomInBtn.click(); + await zoomInBtn.click(); + // wait for zoom animation to finish + await page.locator(backgroundImageSelector).hover({trial: true}); + const zoomedInBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + expect(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height); + expect(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width); + + await zoomOutBtn.click(); + // wait for zoom animation to finish + await page.locator(backgroundImageSelector).hover({trial: true}); + const zoomedOutBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + expect(zoomedOutBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height); + expect(zoomedOutBoundingBox.width).toBeLessThan(zoomedInBoundingBox.width); + + }); + + test('Can use the reset button to reset the image', async ({ page }, testInfo) => { + test.slow(testInfo.project === 'chrome-beta', "This test is slow in chrome-beta"); + // wait for zoom animation to finish + await page.locator(backgroundImageSelector).hover({trial: true}); + + const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0); + const zoomResetBtn = page.locator('.t-btn-zoom-reset').nth(0); + const initialBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + + await zoomInBtn.click(); + // wait for zoom animation to finish + await page.locator(backgroundImageSelector).hover({trial: true}); + await zoomInBtn.click(); + // wait for zoom animation to finish + await page.locator(backgroundImageSelector).hover({trial: true}); + + const zoomedInBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + expect.soft(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height); + expect.soft(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width); + + // wait for zoom animation to finish + // FIXME: The zoom is flakey, sometimes not returning to original dimensions + // https://github.com/nasa/openmct/issues/5491 + await expect.poll(async () => { + await zoomResetBtn.click(); + const boundingBox = await page.locator(backgroundImageSelector).boundingBox(); + + return boundingBox; + }, { + timeout: 10 * 1000 + }).toEqual(initialBoundingBox); + }); + + test('Using the zoom features does not pause telemetry', async ({ page }) => { + const pausePlayButton = page.locator('.c-button.pause-play'); + // wait for zoom animation to finish + await page.locator(backgroundImageSelector).hover({trial: true}); + + // open the time conductor drop down + await page.locator('button:has-text("Fixed Timespan")').click(); + // Click local clock + await page.locator('[data-testid="conductor-modeOption-realtime"]').click(); + + await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/); + const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0); + await zoomInBtn.click(); + // wait for zoom animation to finish + await page.locator(backgroundImageSelector).hover({trial: true}); + + return expect(pausePlayButton).not.toHaveClass(/is-paused/); + }); + +}); + +// The following test case will cover these scenarios +// ('Can use Mouse Wheel to zoom in and out of previous image'); +// ('Can use alt+drag to move around image once zoomed in'); +// ('Clicking on the left arrow should pause the imagery and go to previous image'); +// ('If the imagery view is in pause mode, it should not be updated when new images come in'); +// ('If the imagery view is not in pause mode, it should be updated when new images come in'); +test('Example Imagery in Display layout', async ({ page, browserName }) => { + test.fixme(browserName === 'firefox', 'This test needs to be updated to work with firefox'); + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/nasa/openmct/issues/5265' + }); + + // Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Click the Create button + await page.click('button:has-text("Create")'); + + // Click text=Example Imagery + await page.click('text=Example Imagery'); + + // Clear and set Image load delay to minimum value + // FIXME: Update the value to 5000 ms when this bug is fixed. + // See: https://github.com/nasa/openmct/issues/5265 + await page.locator('input[type="number"]').fill(''); + await page.locator('input[type="number"]').fill('0'); + + // Click text=OK + await Promise.all([ + page.waitForNavigation({waitUntil: 'networkidle'}), + page.click('text=OK'), + //Wait for Save Banner to appear + page.waitForSelector('.c-message-banner__message') + ]); + + // Wait until Save Banner is gone + await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); + await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery'); + await page.locator(backgroundImageSelector).hover({trial: true}); + + // Click previous image button + const previousImageButton = page.locator('.c-nav--prev'); + await previousImageButton.click(); + + // Verify previous image + const selectedImage = page.locator('.selected'); + await expect(selectedImage).toBeVisible(); + + // Zoom in + const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox(); + await page.locator(backgroundImageSelector).hover({trial: true}); + const deltaYStep = 100; // equivalent to 1x zoom + await page.mouse.wheel(0, deltaYStep * 2); + const zoomedBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2; + const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2; + + // Wait for zoom animation to finish + await page.locator(backgroundImageSelector).hover({trial: true}); + const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox(); + expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height); + expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width); + + // Center the mouse pointer + await page.mouse.move(imageCenterX, imageCenterY); + + // Pan Imagery Hints + const expectedAltText = process.platform === 'linux' ? 'Ctrl+Alt drag to pan' : 'Alt drag to pan'; + const imageryHintsText = await page.locator('.c-imagery__hints').innerText(); + expect(expectedAltText).toEqual(imageryHintsText); + + // Click next image button + const nextImageButton = page.locator('.c-nav--next'); + await nextImageButton.click(); + + // Click time conductor mode button + await page.locator('.c-mode-button').click(); + + // Select local clock mode + await page.locator('[data-testid=conductor-modeOption-realtime]').click(); + + // Zoom in on next image + await page.locator(backgroundImageSelector).hover({trial: true}); + await page.mouse.wheel(0, deltaYStep * 2); + + // Wait for zoom animation to finish + await page.locator(backgroundImageSelector).hover({trial: true}); + const imageNextMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox(); + expect(imageNextMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height); + expect(imageNextMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width); + + // Click previous image button + await previousImageButton.click(); + + // Verify previous image + await expect(selectedImage).toBeVisible(); + + const imageCount = await page.locator('.c-imagery__thumb').count(); + await expect.poll(async () => { + const newImageCount = await page.locator('.c-imagery__thumb').count(); + + return newImageCount; + }, { + message: "verify that old images are discarded", + timeout: 6 * 1000 + }).toBe(imageCount); + + // Verify selected image is still displayed + await expect(selectedImage).toBeVisible(); +}); + +test.describe('Example imagery thumbnails resize in display layouts', () => { + test('Resizing the layout changes thumbnail visibility and size', async ({ page }) => { + await page.goto('/', { waitUntil: 'networkidle' }); + + const thumbsWrapperLocator = page.locator('.c-imagery__thumbs-wrapper'); + // Click button:has-text("Create") + await page.locator('button:has-text("Create")').click(); + + // Click li:has-text("Display Layout") + await page.locator('li:has-text("Display Layout")').click(); + const displayLayoutTitleField = page.locator('text=Properties Title Notes Horizontal grid (px) Vertical grid (px) Horizontal size ( >> input[type="text"]'); + await displayLayoutTitleField.click(); + + await displayLayoutTitleField.fill('Thumbnail Display Layout'); + + // Click text=OK + await Promise.all([ + page.waitForNavigation(), + page.locator('text=OK').click() + ]); + + // Click text=Snapshot Save and Finish Editing Save and Continue Editing >> button >> nth=1 + await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); + + // Click text=Save and Finish Editing + await page.locator('text=Save and Finish Editing').click(); + + // Click button:has-text("Create") + await page.locator('button:has-text("Create")').click(); + + // Click li:has-text("Example Imagery") + await page.locator('li:has-text("Example Imagery")').click(); + + const imageryTitleField = page.locator('text=Properties Title Notes Images url list (comma separated) Image load delay (milli >> input[type="text"]'); + // Click text=Properties Title Notes Images url list (comma separated) Image load delay (milli >> input[type="text"] + await imageryTitleField.click(); + + // Fill text=Properties Title Notes Images url list (comma separated) Image load delay (milli >> input[type="text"] + await imageryTitleField.fill('Thumbnail Example Imagery'); + + // Click text=OK + await Promise.all([ + page.waitForNavigation(), + page.locator('text=OK').click() + ]); + + // Click text=Thumbnail Example Imagery Imagery Layout Snapshot >> button >> nth=0 + await Promise.all([ + page.waitForNavigation(), + page.locator('text=Thumbnail Example Imagery Imagery Layout Snapshot >> button').first().click() + ]); + + // Edit mode + await page.locator('text=Thumbnail Display Layout Snapshot >> button').nth(3).click(); + + // Click on example imagery to expose toolbar + await page.locator('text=Thumbnail Example Imagery Snapshot Large View').click(); + + // expect thumbnails not be visible when first added + expect.soft(thumbsWrapperLocator.isHidden()).toBeTruthy(); + + // Resize the example imagery vertically to change the thumbnail visibility + /* + The following arbitrary values are added to observe the separate visual + conditions of the thumbnails (hidden, small thumbnails, regular thumbnails). + Specifically, height is set to 50px for small thumbs and 100px for regular + */ + // Click #mct-input-id-103 + await page.locator('#mct-input-id-103').click(); + + // Fill #mct-input-id-103 + await page.locator('#mct-input-id-103').fill('50'); + + expect(thumbsWrapperLocator.isVisible()).toBeTruthy(); + await expect(thumbsWrapperLocator).toHaveClass(/is-small-thumbs/); + + // Resize the example imagery vertically to change the thumbnail visibility + // Click #mct-input-id-103 + await page.locator('#mct-input-id-103').click(); + + // Fill #mct-input-id-103 + await page.locator('#mct-input-id-103').fill('100'); + + expect(thumbsWrapperLocator.isVisible()).toBeTruthy(); + await expect(thumbsWrapperLocator).not.toHaveClass(/is-small-thumbs/); + }); +}); + +test.describe('Example Imagery in Flexible layout', () => { + test('Example Imagery in Flexible layout', async ({ page, browserName }) => { + test.fixme(browserName === 'firefox', 'This test needs to be updated to work with firefox'); + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/nasa/openmct/issues/5326' + }); + + // Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Click the Create button + await page.click('button:has-text("Create")'); + + // Click text=Example Imagery + await page.click('text=Example Imagery'); + + // Clear and set Image load delay (milliseconds) + await page.click('input[type="number"]', {clickCount: 3}); + await page.type('input[type="number"]', "20"); + + // Click text=OK + await Promise.all([ + page.waitForNavigation({waitUntil: 'networkidle'}), + page.click('text=OK'), + //Wait for Save Banner to appear + page.waitForSelector('.c-message-banner__message') + ]); + // Wait until Save Banner is gone + await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); + await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery'); + await page.locator(backgroundImageSelector).hover({trial: true}); + + // Click the Create button + await page.click('button:has-text("Create")'); + + // Click text=Flexible Layout + await page.click('text=Flexible Layout'); + + // Assert Flexable layout + await expect(page.locator('.js-form-title')).toHaveText('Create a New Flexible Layout'); + + await page.locator('form[name="mctForm"] >> text=My Items').click(); + + // Click My Items + await Promise.all([ + page.locator('text=OK').click(), + page.waitForNavigation({waitUntil: 'networkidle'}) + ]); + + // Click My Items + await page.locator('.c-disclosure-triangle').click(); + + // Right click example imagery + await page.click(('text=Unnamed Example Imagery'), { button: 'right' }); + + // Click move + await page.locator('.icon-move').click(); + + // Click triangle to open sub menu + await page.locator('.c-form__section .c-disclosure-triangle').click(); + + // Click Flexable Layout + await page.click('.c-overlay__outer >> text=Unnamed Flexible Layout'); + + // Click text=OK + await page.locator('text=OK').click(); + + // Save template + await saveTemplate(page); + + // Zoom in + await mouseZoomIn(page); + + // Center the mouse pointer + const zoomedBoundingBox = await await page.locator(backgroundImageSelector).boundingBox(); + const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2; + const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2; + await page.mouse.move(imageCenterX, imageCenterY); + + // Pan zoom + await panZoomAndAssertImageProperties(page); + + // Click previous image button + const previousImageButton = page.locator('.c-nav--prev'); + await previousImageButton.click(); + + // Verify previous image + const selectedImage = page.locator('.selected'); + await expect(selectedImage).toBeVisible(); + + // Click time conductor mode button + await page.locator('.c-mode-button').click(); + + // Select local clock mode + await page.locator('[data-testid=conductor-modeOption-realtime]').click(); + + // Zoom in on next image + await mouseZoomIn(page); + + // Click previous image button + await previousImageButton.click(); + + // Verify previous image + await expect(selectedImage).toBeVisible(); + + const imageCount = await page.locator('.c-imagery__thumb').count(); + await expect.poll(async () => { + const newImageCount = await page.locator('.c-imagery__thumb').count(); + + return newImageCount; + }, { + message: "verify that old images are discarded", + timeout: 6 * 1000 + }).toBe(imageCount); + + // Verify selected image is still displayed + await expect(selectedImage).toBeVisible(); + + // Unpause imagery + await page.locator('.pause-play').click(); + + //Get background-image url from background-image css prop + await assertBackgroundImageUrlFromBackgroundCss(page); + + // Open the image filter menu + await page.locator('[role=toolbar] button[title="Brightness and contrast"]').click(); + + // Drag the brightness and contrast sliders around and assert filter values + await dragBrightnessSliderAndAssertFilterValues(page); + await dragContrastSliderAndAssertFilterValues(page); + }); +}); + +test.describe('Example Imagery in Tabs view', () => { + test.fixme('Can use Mouse Wheel to zoom in and out of previous image'); + test.fixme('Can use alt+drag to move around image once zoomed in'); + test.fixme('Can zoom into the latest image and the real-time/fixed-time imagery will pause'); + test.fixme('Can zoom into a previous image from thumbstrip in real-time or fixed-time'); + test.fixme('Clicking on the left arrow should pause the imagery and go to previous image'); + test.fixme('If the imagery view is in pause mode, it should not be updated when new images come in'); + test.fixme('If the imagery view is not in pause mode, it should be updated when new images come in'); +}); + +/** + * @param {import('@playwright/test').Page} page + */ +async function saveTemplate(page) { + await page.locator('.c-button--menu.c-button--major.icon-save').click(); + await page.locator('text=Save and Finish Editing').click(); +} + +/** + * Drag the brightness slider to max, min, and midpoint and assert the filter values + * @param {import('@playwright/test').Page} page + */ +async function dragBrightnessSliderAndAssertFilterValues(page) { + const brightnessSlider = 'div.c-image-controls__slider-wrapper.icon-brightness > input'; + const brightnessBoundingBox = await page.locator(brightnessSlider).boundingBox(); + const brightnessMidX = brightnessBoundingBox.x + brightnessBoundingBox.width / 2; + const brightnessMidY = brightnessBoundingBox.y + brightnessBoundingBox.height / 2; + + await page.locator(brightnessSlider).hover({trial: true}); + await page.mouse.down(); + await page.mouse.move(brightnessBoundingBox.x + brightnessBoundingBox.width, brightnessMidY); + await assertBackgroundImageBrightness(page, '500'); + await page.mouse.move(brightnessBoundingBox.x, brightnessMidY); + await assertBackgroundImageBrightness(page, '0'); + await page.mouse.move(brightnessMidX, brightnessMidY); + await assertBackgroundImageBrightness(page, '250'); + await page.mouse.up(); +} + +/** + * Drag the contrast slider to max, min, and midpoint and assert the filter values + * @param {import('@playwright/test').Page} page + */ +async function dragContrastSliderAndAssertFilterValues(page) { + const contrastSlider = 'div.c-image-controls__slider-wrapper.icon-contrast > input'; + const contrastBoundingBox = await page.locator(contrastSlider).boundingBox(); + const contrastMidX = contrastBoundingBox.x + contrastBoundingBox.width / 2; + const contrastMidY = contrastBoundingBox.y + contrastBoundingBox.height / 2; + + await page.locator(contrastSlider).hover({trial: true}); + await page.mouse.down(); + await page.mouse.move(contrastBoundingBox.x + contrastBoundingBox.width, contrastMidY); + await assertBackgroundImageContrast(page, '500'); + await page.mouse.move(contrastBoundingBox.x, contrastMidY); + await assertBackgroundImageContrast(page, '0'); + await page.mouse.move(contrastMidX, contrastMidY); + await assertBackgroundImageContrast(page, '250'); + await page.mouse.up(); +} + +/** + * Gets the filter:brightness value of the current background-image and + * asserts against an expected value + * @param {import('@playwright/test').Page} page + * @param {String} expected The expected brightness value + */ +async function assertBackgroundImageBrightness(page, expected) { + const backgroundImage = page.locator('.c-imagery__main-image__background-image'); + + // Get the brightness filter value (i.e: filter: brightness(500%) => "500") + const actual = await backgroundImage.evaluate((el) => { + return el.style.filter.match(/brightness\((\d{1,3})%\)/)[1]; + }); + expect(actual).toBe(expected); +} + +/** + * Gets the filter:contrast value of the current background-image and + * asserts against an expected value + * @param {import('@playwright/test').Page} page + * @param {String} expected The expected contrast value + */ +async function assertBackgroundImageContrast(page, expected) { + const backgroundImage = page.locator('.c-imagery__main-image__background-image'); + + // Get the contrast filter value (i.e: filter: contrast(500%) => "500") + const actual = await backgroundImage.evaluate((el) => { + return el.style.filter.match(/contrast\((\d{1,3})%\)/)[1]; + }); + expect(actual).toBe(expected); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function assertBackgroundImageUrlFromBackgroundCss(page) { + const backgroundImage = page.locator('.c-imagery__main-image__background-image'); + let backgroundImageUrl = await backgroundImage.evaluate((el) => { + return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1]; + }); + let backgroundImageUrl1 = backgroundImageUrl.slice(1, -1); //forgive me, padre + console.log('backgroundImageUrl1 ' + backgroundImageUrl1); + + let backgroundImageUrl2; + await expect.poll(async () => { + // Verify next image has updated + let backgroundImageUrlNext = await backgroundImage.evaluate((el) => { + return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1]; + }); + backgroundImageUrl2 = backgroundImageUrlNext.slice(1, -1); //forgive me, padre + + return backgroundImageUrl2; + }, { + message: "verify next image has updated", + timeout: 6 * 1000 + }).not.toBe(backgroundImageUrl1); + console.log('backgroundImageUrl2 ' + backgroundImageUrl2); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function panZoomAndAssertImageProperties(page) { + const panHotkey = process.platform === 'linux' ? ['Control', 'Alt'] : ['Alt']; + const expectedAltText = process.platform === 'linux' ? 'Ctrl+Alt drag to pan' : 'Alt drag to pan'; + const imageryHintsText = await page.locator('.c-imagery__hints').innerText(); + expect(expectedAltText).toEqual(imageryHintsText); + const zoomedBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2; + const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2; + + // Pan right + await Promise.all(panHotkey.map(x => page.keyboard.down(x))); + await page.mouse.down(); + await page.mouse.move(imageCenterX - 200, imageCenterY, 10); + await page.mouse.up(); + await Promise.all(panHotkey.map(x => page.keyboard.up(x))); + const afterRightPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + expect(zoomedBoundingBox.x).toBeGreaterThan(afterRightPanBoundingBox.x); + + // Pan left + await Promise.all(panHotkey.map(x => page.keyboard.down(x))); + await page.mouse.down(); + await page.mouse.move(imageCenterX, imageCenterY, 10); + await page.mouse.up(); + await Promise.all(panHotkey.map(x => page.keyboard.up(x))); + const afterLeftPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + expect(afterRightPanBoundingBox.x).toBeLessThan(afterLeftPanBoundingBox.x); + + // Pan up + await page.mouse.move(imageCenterX, imageCenterY); + await Promise.all(panHotkey.map(x => page.keyboard.down(x))); + await page.mouse.down(); + await page.mouse.move(imageCenterX, imageCenterY + 200, 10); + await page.mouse.up(); + await Promise.all(panHotkey.map(x => page.keyboard.up(x))); + const afterUpPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + expect(afterUpPanBoundingBox.y).toBeGreaterThanOrEqual(afterLeftPanBoundingBox.y); + + // Pan down + await Promise.all(panHotkey.map(x => page.keyboard.down(x))); + await page.mouse.down(); + await page.mouse.move(imageCenterX, imageCenterY - 200, 10); + await page.mouse.up(); + await Promise.all(panHotkey.map(x => page.keyboard.up(x))); + const afterDownPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + expect(afterDownPanBoundingBox.y).toBeLessThanOrEqual(afterUpPanBoundingBox.y); +} + +/** + * @param {import('@playwright/test').Page} page +*/ +async function mouseZoomIn(page) { + // Zoom in + const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox(); + await page.locator(backgroundImageSelector).hover({trial: true}); + const deltaYStep = 100; // equivalent to 1x zoom + await page.mouse.wheel(0, deltaYStep * 2); + const zoomedBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); + const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2; + const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2; + + // center the mouse pointer + await page.mouse.move(imageCenterX, imageCenterY); + + // Wait for zoom animation to finish + await page.locator(backgroundImageSelector).hover({trial: true}); + const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox(); + expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height); + expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width); +} diff --git a/src/adapter/capabilities/AdapterCapability.js b/e2e/tests/plugins/notebook/addInitRestrictedNotebook.js similarity index 71% rename from src/adapter/capabilities/AdapterCapability.js rename to e2e/tests/plugins/notebook/addInitRestrictedNotebook.js index e98ac518f9..dd303fb521 100644 --- a/src/adapter/capabilities/AdapterCapability.js +++ b/e2e/tests/plugins/notebook/addInitRestrictedNotebook.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -20,17 +20,11 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -define(['objectUtils'], function (objectUtils) { - function AdapterCapability(domainObject) { - this.domainObject = domainObject; - } +// this will be called from the test suite with +// await page.addInitScript({ path: path.join(__dirname, 'addInitRestrictedNotebook.js') }); +// it will install the RestrictedNotebook since it is not installed by default - AdapterCapability.prototype.invoke = function () { - return objectUtils.toNewFormat( - this.domainObject.getModel(), - this.domainObject.getId() - ); - }; - - return AdapterCapability; +document.addEventListener('DOMContentLoaded', () => { + const openmct = window.openmct; + openmct.install(openmct.plugins.RestrictedNotebook('CUSTOM_NAME')); }); diff --git a/e2e/tests/plugins/notebook/notebook.e2e.spec.js b/e2e/tests/plugins/notebook/notebook.e2e.spec.js new file mode 100644 index 0000000000..021946c875 --- /dev/null +++ b/e2e/tests/plugins/notebook/notebook.e2e.spec.js @@ -0,0 +1,198 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to tests which verify the basic operations surrounding Notebooks. +*/ + +const { test } = require('../../../fixtures'); + +test.describe('Notebook CRUD Operations', () => { + test.fixme('Can create a Notebook Object', async ({ page }) => { + //Create domain object + //Newly created notebook should have one Section and one page, 'Unnamed Section'/'Unnamed Page' + }); + test.fixme('Can update a Notebook Object', async ({ page }) => {}); + test.fixme('Can view a perviously created Notebook Object', async ({ page }) => {}); + test.fixme('Can Delete a Notebook Object', async ({ page }) => { + // Other than non-persistible objects + }); +}); + +test.describe('Default Notebook', () => { + // General Default Notebook statements + // ## Useful commands: + // 1. - To check default notebook: + // `JSON.parse(localStorage.getItem('notebook-storage'));` + // 1. - Clear default notebook: + // `localStorage.setItem('notebook-storage', null);` + test.fixme('A newly created Notebook is automatically set as the default notebook if no other notebooks exist', async ({ page }) => { + //Create new notebook + //Verify Default Notebook Characteristics + }); + test.fixme('A newly created Notebook is automatically set as the default notebook if at least one other notebook exists', async ({ page }) => { + //Create new notebook A + //Create second notebook B + //Verify Non-Default Notebook A Characteristics + //Verify Default Notebook B Characteristics + }); + test.fixme('If a default notebook is deleted, the second most recent notebook becomes the default', async ({ page }) => { + //Create new notebook A + //Create second notebook B + //Delete Notebook B + //Verify Default Notebook A Characteristics + }); +}); + +test.describe('Notebook section tests', () => { + //The following test cases are associated with Notebook Sections + test.fixme('New sections are automatically named Unnamed Section with Unnamed Page', async ({ page }) => { + //Create new notebook A + //Add section + //Verify new section and new page details + }); + test.fixme('Section selection operations and associated behavior', async ({ page }) => { + //Create new notebook A + //Add Sections until 6 total with no default section/page + //Select 3rd section + //Delete 4th section + //3rd section is still selected + //Delete 3rd section + //1st section is selected + //Set 3rd section as default + //Delete 2nd section + //3rd section is still default + //Delete 3rd section + //1st is selected and there is no default notebook + }); +}); + +test.describe('Notebook page tests', () => { + //The following test cases are associated with Notebook Pages + test.fixme('Page selection operations and associated behavior', async ({ page }) => { + //Create new notebook A + //Delete existing Page + //New 'Unnamed Page' automatically created + //Create 6 total Pages without a default page + //Select 3rd + //Delete 3rd + //First is now selected + //Set 3rd as default + //Select 2nd page + //Delete 2nd page + //3rd (default) is now selected + //Set 3rd as default page + //Select 3rd (default) page + //Delete 3rd page + //First is now selected and there is no default notebook + }); +}); + +test.describe('Notebook search tests', () => { + test.fixme('Can search for a single result', async ({ page }) => {}); + test.fixme('Can search for many results', async ({ page }) => {}); + test.fixme('Can search for new and recently modified entries', async ({ page }) => {}); + test.fixme('Can search for section text', async ({ page }) => {}); + test.fixme('Can search for page text', async ({ page }) => {}); + test.fixme('Can search for entry text', async ({ page }) => {}); +}); + +test.describe('Notebook entry tests', () => { + test.fixme('When a new entry is created, it should be focused', async ({ page }) => {}); + test.fixme('When a telemetry object is dropped into a notebook, a new entry is created and it should be focused', async ({ page }) => { + // Drag and drop any telmetry object on 'drop object' + // new entry gets created with telemtry object + }); + test.fixme('When a telemetry object is dropped into a notebooks existing entry, it should be focused', async ({ page }) => { + // Drag and drop any telemetry object onto existing entry + // Entry updated with object and snapshot + }); + test.fixme('new entries persist through navigation events without save', async ({ page }) => {}); + test.fixme('previous and new entries can be deleted', async ({ page }) => {}); +}); + +test.describe('Snapshot Menu tests', () => { + test.fixme('When no default notebook is selected, Snapshot Menu dropdown should only have a single option', async ({ page }) => { + // There should be no default notebook + // Clear default notebook if exists using `localStorage.setItem('notebook-storage', null);` + // refresh page + // Click on 'Notebook Snaphot Menu' + // 'save to Notebook Snapshots' should be only option there + }); + test.fixme('When default notebook is updated selected, Snapshot Menu dropdown should list it as the newest option', async ({ page }) => { + // Create 2a notebooks + // Set Notebook A as Default + // Open Snapshot Menu and note that Notebook A is listed + // Close Snapshot Menu + // Set Default Notebook to Notebook B + // Open Snapshot Notebook and note that Notebook B is listed + // Select Default Notebook Option and verify that Snapshot is added to Notebook B + }); + test.fixme('Can add Snapshots via Snapshot Menu and details are correct', async ({ page }) => { + //Note this should be a visual test, too + // Create Telemetry object + // Create A notebook with many pages and sections. + // Set page and section defaults to be between first and last of many. i.e. 3 of 5 + // Navigate to Telemetry object + // Select Default Notebook Option and verify that Snapshot is added to Notebook A + // Verify Snapshot Details appear correctly + }); + test.fixme('Snapshots adjust time conductor', async ({ page }) => { + // Create Telemetry object + // Set Telemetry object's timeconductor to Fixed time with Start and Endtimes are recorded + // Embed Telemetry object into notebook + // Set Time Conductor to Local clock + // Click into embedded telemetry object and verify object appears with same fixed time from record + }); +}); + +test.describe('Snapshot Container tests', () => { + test.fixme('5 Snapshots can be added to a container', async ({ page }) => {}); + test.fixme('5 Snapshots can be added to a container and Deleted with Delete All action', async ({ page }) => {}); + test.fixme('A snapshot can be Deleted from Container', async ({ page }) => {}); + test.fixme('A snapshot can be Previewed from Container', async ({ page }) => {}); + test.fixme('A snapshot Container can be open and closed', async ({ page }) => {}); + test.fixme('Can add object to Snapshot container and pull into notebook and create a new entry', async ({ page }) => { + //Create Notebook + //Create Telemetry Object + //From Telemetry Object, use 'save to Notebook Snapshots' + //Snapshots indicator should blink, click on it to view snapshots + //Navigate to Notebook + //Drag and Drop onto droppable area for new entry + //New Entry created with given snapshot added + //Snapshot removed from container? + }); + test.fixme('Can add object to Snapshot container and pull into notebook and existing entry', async ({ page }) => { + //Create Notebook + //Create Telemetry Object + //From Telemetry Object, use 'save to Notebook Snapshots' + //Snapshots indicator should blink, click on it to view snapshots + //Navigate to Notebook + //Drag and Drop into exiting entry + //Existing Entry updated with given snapshot + //Snapshot removed from container? + }); + test.fixme('Verify Embedded options for PNG, JPG, and Annotate work correctly', async ({ page }) => { + //Add snapshot to container + //Verify PNG, JPG, and Annotate buttons work correctly + }); +}); diff --git a/e2e/tests/plugins/notebook/restrictedNotebook.e2e.spec.js b/e2e/tests/plugins/notebook/restrictedNotebook.e2e.spec.js new file mode 100644 index 0000000000..18c4653d85 --- /dev/null +++ b/e2e/tests/plugins/notebook/restrictedNotebook.e2e.spec.js @@ -0,0 +1,255 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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 { test } = require('../../../fixtures'); +const { expect } = require('@playwright/test'); +const path = require('path'); + +const TEST_TEXT = 'Testing text for entries.'; +const TEST_TEXT_NAME = 'Test Page'; +const CUSTOM_NAME = 'CUSTOM_NAME'; +const NOTEBOOK_DROP_AREA = '.c-notebook__drag-area'; + +test.describe('Restricted Notebook', () => { + test.beforeEach(async ({ page }) => { + await startAndAddRestrictedNotebookObject(page); + }); + + test('Can be renamed @addInit', async ({ page }) => { + await expect(page.locator('.l-browse-bar__object-name')).toContainText(`Unnamed ${CUSTOM_NAME}`); + }); + + test('Can be deleted if there are no locked pages @addInit', async ({ page }) => { + await openContextMenuRestrictedNotebook(page); + + const menuOptions = page.locator('.c-menu ul'); + await expect.soft(menuOptions).toContainText('Remove'); + + const restrictedNotebookTreeObject = page.locator(`a:has-text("Unnamed ${CUSTOM_NAME}")`); + + // notbook tree object exists + expect.soft(await restrictedNotebookTreeObject.count()).toEqual(1); + + // Click Remove Text + await page.locator('text=Remove').click(); + + // Click 'OK' on confirmation window and wait for save banner to appear + await Promise.all([ + page.waitForNavigation(), + page.locator('text=OK').click(), + page.waitForSelector('.c-message-banner__message') + ]); + + // has been deleted + expect(await restrictedNotebookTreeObject.count()).toEqual(0); + }); + + test('Can be locked if at least one page has one entry @addInit', async ({ page }) => { + + await enterTextEntry(page); + + const commitButton = page.locator('button:has-text("Commit Entries")'); + expect(await commitButton.count()).toEqual(1); + }); + +}); + +test.describe('Restricted Notebook with at least one entry and with the page locked @addInit', () => { + + test.beforeEach(async ({ page }) => { + await startAndAddRestrictedNotebookObject(page); + await enterTextEntry(page); + await lockPage(page); + + // FIXME: Give ample time for the mutation to happen + // https://github.com/nasa/openmct/issues/5409 + // eslint-disable-next-line playwright/no-wait-for-timeout + await page.waitForTimeout(1 * 1000); + + // open sidebar + await page.locator('button.c-notebook__toggle-nav-button').click(); + }); + + test('Locked page should now be in a locked state @addInit', async ({ page }, testInfo) => { + test.fixme(testInfo.project === 'chrome-beta', "Test is unreliable on chrome-beta"); + // main lock message on page + const lockMessage = page.locator('text=This page has been committed and cannot be modified or removed'); + expect.soft(await lockMessage.count()).toEqual(1); + + // lock icon on page in sidebar + const pageLockIcon = page.locator('ul.c-notebook__pages li div.icon-lock'); + expect.soft(await pageLockIcon.count()).toEqual(1); + + // no way to remove a restricted notebook with a locked page + await openContextMenuRestrictedNotebook(page); + const menuOptions = page.locator('.c-menu ul'); + + await expect(menuOptions).not.toContainText('Remove'); + }); + + test('Can still: add page, rename, add entry, delete unlocked pages @addInit', async ({ page }) => { + // Click text=Page Add >> button + await Promise.all([ + page.waitForNavigation(), + page.locator('text=Page Add >> button').click() + ]); + // Click text=Unnamed Page >> nth=1 + await page.locator('text=Unnamed Page').nth(1).click(); + // Press a with modifiers + await page.locator('text=Unnamed Page').nth(1).fill(TEST_TEXT_NAME); + + // expect to be able to rename unlocked pages + const newPageElement = page.locator(`text=${TEST_TEXT_NAME}`); + const newPageCount = await newPageElement.count(); + await newPageElement.press('Enter'); // exit contenteditable state + expect.soft(newPageCount).toEqual(1); + + // enter test text + await enterTextEntry(page); + + // expect new page to be lockable + const commitButton = page.locator('BUTTON:HAS-TEXT("COMMIT ENTRIES")'); + expect.soft(await commitButton.count()).toEqual(1); + + // Click text=Unnamed PageTest Page >> button + await page.locator('text=Unnamed PageTest Page >> button').click(); + // Click text=Delete Page + await page.locator('text=Delete Page').click(); + // Click text=Ok + await Promise.all([ + page.waitForNavigation(), + page.locator('text=Ok').click() + ]); + + // deleted page, should no longer exist + const deletedPageElement = page.locator(`text=${TEST_TEXT_NAME}`); + expect(await deletedPageElement.count()).toEqual(0); + }); +}); + +test.describe('Restricted Notebook with a page locked and with an embed @addInit', () => { + + test.beforeEach(async ({ page }) => { + await startAndAddRestrictedNotebookObject(page); + await dragAndDropEmbed(page); + }); + + test('Allows embeds to be deleted if page unlocked @addInit', async ({ page }) => { + // Click .c-ne__embed__name .c-popup-menu-button + await page.locator('.c-ne__embed__name .c-popup-menu-button').click(); // embed popup menu + + const embedMenu = page.locator('body >> .c-menu'); + await expect(embedMenu).toContainText('Remove This Embed'); + }); + + test('Disallows embeds to be deleted if page locked @addInit', async ({ page }) => { + await lockPage(page); + // Click .c-ne__embed__name .c-popup-menu-button + await page.locator('.c-ne__embed__name .c-popup-menu-button').click(); // embed popup menu + + const embedMenu = page.locator('body >> .c-menu'); + await expect(embedMenu).not.toContainText('Remove This Embed'); + }); + +}); + +/** + * @param {import('@playwright/test').Page} page + */ +async function startAndAddRestrictedNotebookObject(page) { + // eslint-disable-next-line no-undef + await page.addInitScript({ path: path.join(__dirname, 'addInitRestrictedNotebook.js') }); + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + //Click the Create button + await page.click('button:has-text("Create")'); + // Click text=CUSTOME_NAME + await page.click(`text=${CUSTOM_NAME}`); // secondarily tests renamability also + // Click text=OK + await Promise.all([ + page.waitForNavigation({waitUntil: 'networkidle'}), + page.click('text=OK') + ]); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function enterTextEntry(page) { + // Click .c-notebook__drag-area + await page.locator(NOTEBOOK_DROP_AREA).click(); + + // enter text + await page.locator('div.c-ne__text').click(); + await page.locator('div.c-ne__text').fill(TEST_TEXT); + await page.locator('div.c-ne__text').press('Enter'); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function dragAndDropEmbed(page) { + // Click button:has-text("Create") + await page.locator('button:has-text("Create")').click(); + // Click li:has-text("Sine Wave Generator") + await page.locator('li:has-text("Sine Wave Generator")').click(); + // Click form[name="mctForm"] >> text=My Items + await page.locator('form[name="mctForm"] >> text=My Items').click(); + // Click text=OK + await page.locator('text=OK').click(); + // Click text=Open MCT My Items >> span >> nth=3 + await page.locator('text=Open MCT My Items >> span').nth(3).click(); + // Click text=Unnamed CUSTOM_NAME + await Promise.all([ + page.waitForNavigation(), + page.locator('text=Unnamed CUSTOM_NAME').click() + ]); + + await page.dragAndDrop('text=UNNAMED SINE WAVE GENERATOR', NOTEBOOK_DROP_AREA); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function lockPage(page) { + const commitButton = page.locator('button:has-text("Commit Entries")'); + await commitButton.click(); + + //Wait until Lock Banner is visible + await page.locator('text=Lock Page').click(); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function openContextMenuRestrictedNotebook(page) { + const myItemsFolder = page.locator('text=Open MCT My Items >> span').nth(3); + const className = await myItemsFolder.getAttribute('class'); + if (!className.includes('c-disclosure-triangle--expanded')) { + await myItemsFolder.click(); + } + + // Click a:has-text("Unnamed CUSTOM_NAME") + await page.locator(`a:has-text("Unnamed ${CUSTOM_NAME}")`).click({ + button: 'right' + }); +} diff --git a/e2e/tests/plugins/notebook/tags.e2e.spec.js b/e2e/tests/plugins/notebook/tags.e2e.spec.js new file mode 100644 index 0000000000..c7f145c2ee --- /dev/null +++ b/e2e/tests/plugins/notebook/tags.e2e.spec.js @@ -0,0 +1,205 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to tests which verify form functionality. +*/ + +const { expect } = require('@playwright/test'); +const { test } = require('../../../fixtures'); + +/** + * Creates a notebook object and adds an entry. + * @param {import('@playwright/test').Page} - page to load + * @param {number} [iterations = 1] - the number of entries to create + */ +async function createNotebookAndEntry(page, iterations = 1) { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Click button:has-text("Create") + await page.locator('button:has-text("Create")').click(); + await page.locator('[title="Create and save timestamped notes with embedded object snapshots."]').click(); + // Click button:has-text("OK") + await Promise.all([ + page.waitForNavigation(), + page.locator('[name="mctForm"] >> text=My Items').click(), + page.locator('button:has-text("OK")').click() + ]); + + for (let iteration = 0; iteration < iterations; iteration++) { + // Click text=To start a new entry, click here or drag and drop any object + await page.locator('text=To start a new entry, click here or drag and drop any object').click(); + const entryLocator = `[aria-label="Notebook Entry Input"] >> nth = ${iteration}`; + await page.locator(entryLocator).click(); + await page.locator(entryLocator).fill(`Entry ${iteration}`); + } + +} + +/** + * Creates a notebook object, adds an entry, and adds a tag. + * @param {import('@playwright/test').Page} page + * @param {number} [iterations = 1] - the number of entries (and tags) to create + */ +async function createNotebookEntryAndTags(page, iterations = 1) { + await createNotebookAndEntry(page, iterations); + + for (let iteration = 0; iteration < iterations; iteration++) { + // Click text=To start a new entry, click here or drag and drop any object + await page.locator(`button:has-text("Add Tag") >> nth = ${iteration}`).click(); + + // Click [placeholder="Type to select tag"] + await page.locator('[placeholder="Type to select tag"]').click(); + // Click text=Driving + await page.locator('[aria-label="Autocomplete Options"] >> text=Driving').click(); + + // Click button:has-text("Add Tag") + await page.locator(`button:has-text("Add Tag") >> nth = ${iteration}`).click(); + // Click [placeholder="Type to select tag"] + await page.locator('[placeholder="Type to select tag"]').click(); + // Click text=Science + await page.locator('[aria-label="Autocomplete Options"] >> text=Science').click(); + } +} + +test.describe('Tagging in Notebooks', () => { + test('Can load tags', async ({ page }) => { + await createNotebookAndEntry(page); + // Click text=To start a new entry, click here or drag and drop any object + await page.locator('button:has-text("Add Tag")').click(); + + // Click [placeholder="Type to select tag"] + await page.locator('[placeholder="Type to select tag"]').click(); + + await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Science"); + await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Drilling"); + await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Driving"); + }); + test('Can add tags', async ({ page }) => { + await createNotebookEntryAndTags(page); + + await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Science"); + await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Driving"); + + // Click button:has-text("Add Tag") + await page.locator('button:has-text("Add Tag")').click(); + // Click [placeholder="Type to select tag"] + await page.locator('[placeholder="Type to select tag"]').click(); + + await expect(page.locator('[aria-label="Autocomplete Options"]')).not.toContainText("Science"); + await expect(page.locator('[aria-label="Autocomplete Options"]')).not.toContainText("Driving"); + await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Drilling"); + }); + test('Can search for tags', async ({ page }) => { + await createNotebookEntryAndTags(page); + // Click [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); + // Fill [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc'); + await expect(page.locator('[aria-label="Search Result"]')).toContainText("Science"); + await expect(page.locator('[aria-label="Search Result"]')).toContainText("Driving"); + + // Click [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); + // Fill [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Sc'); + await expect(page.locator('[aria-label="Search Result"]')).toContainText("Science"); + await expect(page.locator('[aria-label="Search Result"]')).toContainText("Driving"); + + // Click [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); + // Fill [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Xq'); + await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible(); + await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible(); + }); + + test('Can delete tags', async ({ page }) => { + await createNotebookEntryAndTags(page); + await page.locator('[aria-label="Notebook Entries"]').click(); + // Delete Driving + await page.locator('text=Science Driving Add Tag >> button').nth(1).click(); + + await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Science"); + await expect(page.locator('[aria-label="Notebook Entry"]')).not.toContainText("Driving"); + + // Fill [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc'); + await expect(page.locator('[aria-label="Search Result"]')).not.toContainText("Driving"); + }); + test('Tags persist across reload', async ({ page }) => { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Create a clock object we can navigate to + await page.click('button:has-text("Create")'); + + // Click Clock + await page.click('text=Clock'); + // Click button:has-text("OK") + await Promise.all([ + page.waitForNavigation(), + page.locator('[name="mctForm"] >> text=My Items').click(), + page.locator('button:has-text("OK")').click() + ]); + + await page.click('.c-disclosure-triangle'); + + const ITERATIONS = 4; + await createNotebookEntryAndTags(page, ITERATIONS); + + for (let iteration = 0; iteration < ITERATIONS; iteration++) { + const entryLocator = `[aria-label="Notebook Entry"] >> nth = ${iteration}`; + await expect(page.locator(entryLocator)).toContainText("Science"); + await expect(page.locator(entryLocator)).toContainText("Driving"); + } + + // Click Unnamed Clock + await page.click('text="Unnamed Clock"'); + + // Click Unnamed Notebook + await page.click('text="Unnamed Notebook"'); + + for (let iteration = 0; iteration < ITERATIONS; iteration++) { + const entryLocator = `[aria-label="Notebook Entry"] >> nth = ${iteration}`; + await expect(page.locator(entryLocator)).toContainText("Science"); + await expect(page.locator(entryLocator)).toContainText("Driving"); + } + + //Reload Page + await Promise.all([ + page.reload(), + page.waitForLoadState('networkidle') + ]); + + // Click Unnamed Notebook + await page.click('text="Unnamed Notebook"'); + + for (let iteration = 0; iteration < ITERATIONS; iteration++) { + const entryLocator = `[aria-label="Notebook Entry"] >> nth = ${iteration}`; + await expect(page.locator(entryLocator)).toContainText("Science"); + await expect(page.locator(entryLocator)).toContainText("Driving"); + } + + }); +}); diff --git a/e2e/tests/plugins/plot/autoscale.e2e.spec.js b/e2e/tests/plugins/plot/autoscale.e2e.spec.js new file mode 100644 index 0000000000..ee9782ec42 --- /dev/null +++ b/e2e/tests/plugins/plot/autoscale.e2e.spec.js @@ -0,0 +1,185 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + +/* +Testsuite for plot autoscale. +*/ + +const { test } = require('../../../fixtures.js'); +const { expect } = require('@playwright/test'); + +test.use({ + viewport: { + width: 1280, + height: 720 + } +}); + +test.describe('ExportAsJSON', () => { + test('User can set autoscale with a valid range @snapshot', async ({ page }) => { + //This is necessary due to the size of the test suite. + test.slow(); + + await page.goto('/', { waitUntil: 'networkidle' }); + + await setTimeRange(page); + + await createSinewaveOverlayPlot(page); + + await testYTicks(page, ['-1.00', '-0.50', '0.00', '0.50', '1.00']); + + await turnOffAutoscale(page); + + // Make sure that after turning off autoscale, the user selected range values start at the same values the plot had prior. + await testYTicks(page, ['-1.00', '-0.50', '0.00', '0.50', '1.00']); + + const canvas = page.locator('canvas').nth(1); + + await canvas.hover({trial: true}); + + expect(await canvas.screenshot()).toMatchSnapshot('autoscale-canvas-prepan'); + + //Alt Drag Start + await page.keyboard.down('Alt'); + + await canvas.dragTo(canvas, { + sourcePosition: { + x: 200, + y: 200 + }, + targetPosition: { + x: 400, + y: 400 + } + }); + + //Alt Drag End + await page.keyboard.up('Alt'); + + // Ensure the drag worked. + await testYTicks(page, ['0.00', '0.50', '1.00', '1.50', '2.00']); + + await canvas.hover({trial: true}); + + expect(await canvas.screenshot()).toMatchSnapshot('autoscale-canvas-panned'); + }); +}); + +/** + * @param {import('@playwright/test').Page} page + * @param {string} start + * @param {string} end + */ +async function setTimeRange(page, start = '2022-03-29 22:00:00.000Z', end = '2022-03-29 22:00:30.000Z') { + // Set a specific time range for consistency, otherwise it will change + // on every test to a range based on the current time. + + const timeInputs = page.locator('input.c-input--datetime'); + await timeInputs.first().click(); + await timeInputs.first().fill(start); + + await timeInputs.nth(1).click(); + await timeInputs.nth(1).fill(end); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function createSinewaveOverlayPlot(page) { + // click create button + await page.locator('button:has-text("Create")').click(); + + // add overlay plot with defaults + await page.locator('li:has-text("Overlay Plot")').click(); + await Promise.all([ + page.waitForNavigation(), + page.locator('text=OK').click(), + //Wait for Save Banner to appear1 + page.waitForSelector('.c-message-banner__message') + ]); + //Wait until Save Banner is gone + await page.locator('.c-message-banner__close-button').click(); + await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); + + // save (exit edit mode) + await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); + await page.locator('text=Save and Finish Editing').click(); + + // click create button + await page.locator('button:has-text("Create")').click(); + + // add sine wave generator with defaults + await page.locator('li:has-text("Sine Wave Generator")').click(); + await Promise.all([ + page.waitForNavigation(), + page.locator('text=OK').click(), + //Wait for Save Banner to appear1 + page.waitForSelector('.c-message-banner__message') + ]); + //Wait until Save Banner is gone + await page.locator('.c-message-banner__close-button').click(); + await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); + + // focus the overlay plot + await page.locator('text=Open MCT My Items >> span').nth(3).click(); + await Promise.all([ + page.waitForNavigation(), + page.locator('text=Unnamed Overlay Plot').first().click() + ]); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function turnOffAutoscale(page) { + // enter edit mode + await page.locator('text=Unnamed Overlay Plot Snapshot >> button').nth(3).click(); + + // uncheck autoscale + await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"] >> nth=1').uncheck(); + + // save + await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); + await Promise.all([ + page.locator('text=Save and Finish Editing').click(), + //Wait for Save Banner to appear + page.waitForSelector('.c-message-banner__message') + ]); + //Wait until Save Banner is gone + await page.locator('.c-message-banner__close-button').click(); + await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function testYTicks(page, values) { + const yTicks = page.locator('.gl-plot-y-tick-label'); + await page.locator('canvas >> nth=1').hover(); + let promises = [yTicks.count().then(c => expect(c).toBe(values.length))]; + + for (let i = 0, l = values.length; i < l; i += 1) { + promises.push(expect(yTicks.nth(i)).toHaveText(values[i])); // eslint-disable-line + } + + await Promise.all(promises); +} diff --git a/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-panned-chrome-darwin b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-panned-chrome-darwin new file mode 100644 index 0000000000..bea4d7c408 Binary files /dev/null and b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-panned-chrome-darwin differ diff --git a/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-panned-chrome-linux b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-panned-chrome-linux new file mode 100644 index 0000000000..345901fcce Binary files /dev/null and b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-panned-chrome-linux differ diff --git a/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-prepan-chrome-darwin b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-prepan-chrome-darwin new file mode 100644 index 0000000000..fc3db7c140 Binary files /dev/null and b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-prepan-chrome-darwin differ diff --git a/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-prepan-chrome-linux b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-prepan-chrome-linux new file mode 100644 index 0000000000..88e71e7895 Binary files /dev/null and b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-prepan-chrome-linux differ diff --git a/e2e/tests/plugins/plot/logPlot.e2e.spec.js b/e2e/tests/plugins/plot/logPlot.e2e.spec.js new file mode 100644 index 0000000000..798109a5d8 --- /dev/null +++ b/e2e/tests/plugins/plot/logPlot.e2e.spec.js @@ -0,0 +1,298 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + +/* +Tests to verify log plot functionality. Note this test suite if very much under active development and should not +necessarily be used for reference when writing new tests in this area. +*/ + +const { test } = require('../../../fixtures.js'); +const { expect } = require('@playwright/test'); + +test.describe('Log plot tests', () => { + test('Log Plot ticks are functionally correct in regular and log mode and after refresh', async ({ page }) => { + //Test.slow decorator is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374 + test.slow(); + + await makeOverlayPlot(page); + await testRegularTicks(page); + await enableEditMode(page); + await enableLogMode(page); + await testLogTicks(page); + await disableLogMode(page); + await testRegularTicks(page); + await enableLogMode(page); + await testLogTicks(page); + await saveOverlayPlot(page); + await testLogTicks(page); + }); + + // Leaving test as 'TODO' for now. + // NOTE: Not eligible for community contributions. + test.fixme('Verify that log mode option is reflected in import/export JSON', async ({ page }) => { + await makeOverlayPlot(page); + await enableEditMode(page); + await enableLogMode(page); + await saveOverlayPlot(page); + + // TODO ...export, delete the overlay, then import it... + + //await testLogTicks(page); + + // TODO, the plot is slightly at different position that in the other test, so this fails. + // ...We can fix it by copying all steps from the first test... + // await testLogPlotPixels(page); + }); +}); + +/** + * Makes an overlay plot with a sine wave generator and clicks on the overlay plot in the sidebar so it is the active thing displayed. + * @param {import('@playwright/test').Page} page + */ +async function makeOverlayPlot(page) { + // fresh page with time range from 2022-03-29 22:00:00.000Z to 2022-03-29 22:00:30.000Z + await page.goto('/', { waitUntil: 'networkidle' }); + + // Set a specific time range for consistency, otherwise it will change + // on every test to a range based on the current time. + + const timeInputs = page.locator('input.c-input--datetime'); + await timeInputs.first().click(); + await timeInputs.first().fill('2022-03-29 22:00:00.000Z'); + + await timeInputs.nth(1).click(); + await timeInputs.nth(1).fill('2022-03-29 22:00:30.000Z'); + + // create overlay plot + + await page.locator('button.c-create-button').click(); + await page.locator('li:has-text("Overlay Plot")').click(); + await Promise.all([ + page.waitForNavigation({ waitUntil: 'networkidle'}), + page.locator('text=OK').click(), + //Wait for Save Banner to appear + page.waitForSelector('.c-message-banner__message') + ]); + //Wait until Save Banner is gone + await page.locator('.c-message-banner__close-button').click(); + await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); + + // save the overlay plot + + await saveOverlayPlot(page); + + // create a sinewave generator + + await page.locator('button.c-create-button').click(); + await page.locator('li:has-text("Sine Wave Generator")').click(); + + // set amplitude to 6, offset 4, period 2 + + await page.locator('div:nth-child(5) .c-form-row__controls .form-control .field input').click(); + await page.locator('div:nth-child(5) .c-form-row__controls .form-control .field input').fill('6'); + + await page.locator('div:nth-child(6) .c-form-row__controls .form-control .field input').click(); + await page.locator('div:nth-child(6) .c-form-row__controls .form-control .field input').fill('4'); + + await page.locator('div:nth-child(7) .c-form-row__controls .form-control .field input').click(); + await page.locator('div:nth-child(7) .c-form-row__controls .form-control .field input').fill('2'); + + // Click OK to make generator + + await Promise.all([ + page.waitForNavigation({ waitUntil: 'networkidle'}), + page.locator('text=OK').click(), + //Wait for Save Banner to appear + page.waitForSelector('.c-message-banner__message') + ]); + //Wait until Save Banner is gone + await page.locator('.c-message-banner__close-button').click(); + await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); + + // click on overlay plot + + await page.locator('text=Open MCT My Items >> span').nth(3).click(); + await Promise.all([ + page.waitForNavigation(), + page.locator('text=Unnamed Overlay Plot').first().click() + ]); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function testRegularTicks(page) { + const yTicks = await page.locator('.gl-plot-y-tick-label'); + expect(await yTicks.count()).toBe(7); + await expect(yTicks.nth(0)).toHaveText('-2'); + await expect(yTicks.nth(1)).toHaveText('0'); + await expect(yTicks.nth(2)).toHaveText('2'); + await expect(yTicks.nth(3)).toHaveText('4'); + await expect(yTicks.nth(4)).toHaveText('6'); + await expect(yTicks.nth(5)).toHaveText('8'); + await expect(yTicks.nth(6)).toHaveText('10'); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function testLogTicks(page) { + const yTicks = await page.locator('.gl-plot-y-tick-label'); + expect(await yTicks.count()).toBe(28); + await expect(yTicks.nth(0)).toHaveText('-2.98'); + await expect(yTicks.nth(1)).toHaveText('-2.50'); + await expect(yTicks.nth(2)).toHaveText('-2.00'); + await expect(yTicks.nth(3)).toHaveText('-1.51'); + await expect(yTicks.nth(4)).toHaveText('-1.20'); + await expect(yTicks.nth(5)).toHaveText('-1.00'); + await expect(yTicks.nth(6)).toHaveText('-0.80'); + await expect(yTicks.nth(7)).toHaveText('-0.58'); + await expect(yTicks.nth(8)).toHaveText('-0.40'); + await expect(yTicks.nth(9)).toHaveText('-0.20'); + await expect(yTicks.nth(10)).toHaveText('-0.00'); + await expect(yTicks.nth(11)).toHaveText('0.20'); + await expect(yTicks.nth(12)).toHaveText('0.40'); + await expect(yTicks.nth(13)).toHaveText('0.58'); + await expect(yTicks.nth(14)).toHaveText('0.80'); + await expect(yTicks.nth(15)).toHaveText('1.00'); + await expect(yTicks.nth(16)).toHaveText('1.20'); + await expect(yTicks.nth(17)).toHaveText('1.51'); + await expect(yTicks.nth(18)).toHaveText('2.00'); + await expect(yTicks.nth(19)).toHaveText('2.50'); + await expect(yTicks.nth(20)).toHaveText('2.98'); + await expect(yTicks.nth(21)).toHaveText('3.50'); + await expect(yTicks.nth(22)).toHaveText('4.00'); + await expect(yTicks.nth(23)).toHaveText('4.50'); + await expect(yTicks.nth(24)).toHaveText('5.31'); + await expect(yTicks.nth(25)).toHaveText('7.00'); + await expect(yTicks.nth(26)).toHaveText('8.00'); + await expect(yTicks.nth(27)).toHaveText('9.00'); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function enableEditMode(page) { + // turn on edit mode + await page.locator('text=Unnamed Overlay Plot Snapshot >> button').nth(3).click(); + await expect(await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1)).toBeVisible(); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function enableLogMode(page) { + // turn on log mode + await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"]').first().check(); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function disableLogMode(page) { + // turn off log mode + await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"]').first().uncheck(); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function saveOverlayPlot(page) { + // save overlay plot + await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); + + await Promise.all([ + page.locator('text=Save and Finish Editing').click(), + //Wait for Save Banner to appear + page.waitForSelector('.c-message-banner__message') + ]); + //Wait until Save Banner is gone + await page.locator('.c-message-banner__close-button').click(); + await page.waitForSelector('.c-message-banner__message', { state: 'detached' }); +} + +/** + * @param {import('@playwright/test').Page} page + */ +// FIXME: Remove this eslint exception once implemented +// eslint-disable-next-line no-unused-vars +async function testLogPlotPixels(page) { + const pixelsMatch = await page.evaluate(async () => { + // TODO get canvas pixels at a few locations to make sure they're the correct color, to test that the plot comes out as expected. + + await new Promise((r) => setTimeout(r, 5 * 1000)); + + // These are some pixels that should be blue points in the log plot. + // If the plot changes shape to an unexpected shape, this will + // likely fail, which is what we want. + // + // I found these pixels by pausing playwright in debug mode at this + // point, and using similar code as below to output the pixel data, then + // I logged those pixels here. + const expectedBluePixels = [ + // TODO these pixel sets only work with the first test, but not the second test. + + // [60, 35], + // [121, 125], + // [156, 377], + // [264, 73], + // [372, 186], + // [576, 73], + // [659, 439], + // [675, 423] + + [60, 35], + [120, 125], + [156, 375], + [264, 73], + [372, 185], + [575, 72], + [659, 437], + [675, 421] + ]; + + // The first canvas in the DOM is the one that has the plot point + // icons (canvas 2d), which is the one we are testing. The second + // one in the DOM is the WebGL canvas with the line. (Why aren't + // they both WebGL?) + const canvas = document.querySelector('canvas'); + + const ctx = canvas.getContext('2d'); + + for (const pixel of expectedBluePixels) { + // XXX Possible optimization: call getImageData only once with + // area including all pixels to be tested. + const data = ctx.getImageData(pixel[0], pixel[1], 1, 1).data; + + // #43b0ffff <-- openmct cyanish-blue with 100% opacity + // if (data[0] !== 0x43 || data[1] !== 0xb0 || data[2] !== 0xff || data[3] !== 0xff) { + if (data[0] === 0 && data[1] === 0 && data[2] === 0 && data[3] === 0) { + // If any pixel is empty, it means we didn't hit a plot point. + return false; + } + } + + return true; + }); + + expect(pixelsMatch).toBe(true); +} diff --git a/e2e/tests/plugins/plot/missingPlotObj.e2e.spec.js b/e2e/tests/plugins/plot/missingPlotObj.e2e.spec.js new file mode 100644 index 0000000000..a10f990c53 --- /dev/null +++ b/e2e/tests/plugins/plot/missingPlotObj.e2e.spec.js @@ -0,0 +1,155 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + +/* +Tests to verify log plot functionality when objects are missing +*/ + +const { test } = require('../../../fixtures.js'); +const { expect } = require('@playwright/test'); + +test.describe('Handle missing object for plots', () => { + test('Displays empty div for missing stacked plot item', async ({ page, browserName }) => { + test.fixme(browserName === 'firefox', 'Firefox failing due to console events being missed'); + const errorLogs = []; + + page.on("console", (message) => { + if (message.type() === 'warning' && message.text().includes('Missing domain object')) { + errorLogs.push(message.text()); + } + }); + + //Make stacked plot + await makeStackedPlot(page); + + //Gets local storage and deletes the last sine wave generator in the stacked plot + const localStorage = await page.evaluate(() => window.localStorage); + const parsedData = JSON.parse(localStorage.mct); + const keys = Object.keys(parsedData); + const lastKey = keys[keys.length - 1]; + + delete parsedData[lastKey]; + + //Sets local storage with missing object + await page.evaluate( + `window.localStorage.setItem('mct', '${JSON.stringify(parsedData)}')` + ); + + //Reloads page and clicks on stacked plot + await Promise.all([ + page.reload(), + page.waitForLoadState('networkidle') + ]); + + //Verify Main section is there on load + await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Stacked Plot'); + + await page.locator('text=Open MCT My Items >> span').nth(3).click(); + await Promise.all([ + page.waitForNavigation(), + page.locator('text=Unnamed Stacked Plot').first().click() + ]); + + //Check that there is only one stacked item plot with a plot, the missing one will be empty + await expect(page.locator(".c-plot--stacked-container:has(.gl-plot)")).toHaveCount(1); + //Verify that console.warn is thrown + expect(errorLogs).toHaveLength(1); + }); +}); + +/** + * This is used the create a stacked plot object + * @private + */ +async function makeStackedPlot(page) { + // fresh page with time range from 2022-03-29 22:00:00.000Z to 2022-03-29 22:00:30.000Z + await page.goto('/', { waitUntil: 'networkidle' }); + + // create stacked plot + await page.locator('button.c-create-button').click(); + await page.locator('li:has-text("Stacked Plot")').click(); + + await Promise.all([ + page.waitForNavigation({ waitUntil: 'networkidle'}), + page.locator('text=OK').click(), + //Wait for Save Banner to appear + page.waitForSelector('.c-message-banner__message') + ]); + + // save the stacked plot + await saveStackedPlot(page); + + // create a sinewave generator + await createSineWaveGenerator(page); + + // click on stacked plot + await page.locator('text=Open MCT My Items >> span').nth(3).click(); + await Promise.all([ + page.waitForNavigation(), + page.locator('text=Unnamed Stacked Plot').first().click() + ]); + + // create a second sinewave generator + await createSineWaveGenerator(page); + + // click on stacked plot + await page.locator('text=Open MCT My Items >> span').nth(3).click(); + await Promise.all([ + page.waitForNavigation(), + page.locator('text=Unnamed Stacked Plot').first().click() + ]); +} + +/** + * This is used to save a stacked plot object + * @private + */ +async function saveStackedPlot(page) { + // save stacked plot + await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); + + await Promise.all([ + page.locator('text=Save and Finish Editing').click(), + //Wait for Save Banner to appear + page.waitForSelector('.c-message-banner__message') + ]); + //Wait until Save Banner is gone + await page.locator('.c-message-banner__close-button').click(); + await page.waitForSelector('.c-message-banner__message', { state: 'detached' }); +} + +/** + * This is used to create a sine wave generator object + * @private + */ +async function createSineWaveGenerator(page) { + //Create sine wave generator + await page.locator('button.c-create-button').click(); + await page.locator('li:has-text("Sine Wave Generator")').click(); + + await Promise.all([ + page.waitForNavigation({ waitUntil: 'networkidle'}), + page.locator('text=OK').click(), + //Wait for Save Banner to appear + page.waitForSelector('.c-message-banner__message') + ]); +} diff --git a/platform/commonUI/general/test/filters/ReverseFilterSpec.js b/e2e/tests/plugins/remoteClock/remoteClock.e2e.spec.js similarity index 56% rename from platform/commonUI/general/test/filters/ReverseFilterSpec.js rename to e2e/tests/plugins/remoteClock/remoteClock.e2e.spec.js index 8a2f515600..f9167d9e4e 100644 --- a/platform/commonUI/general/test/filters/ReverseFilterSpec.js +++ b/e2e/tests/plugins/remoteClock/remoteClock.e2e.spec.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -20,24 +20,22 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -define( - ['../../src/filters/ReverseFilter'], - function (ReverseFilter) { +const { test } = require('../../../fixtures.js'); +// eslint-disable-next-line no-unused-vars +const { expect } = require('@playwright/test'); - describe("The reverse filter", function () { - var reverse; - - beforeEach(function () { - reverse = new ReverseFilter(); - }); - - it("reverses text", function () { - expect(reverse('foo')).toEqual('oof'); - }); - - it("returns undefined for undefined inputs", function () { - expect(reverse(undefined)).toBeUndefined(); - }); +test.describe('Remote Clock', () => { + // eslint-disable-next-line require-await + test.fixme('blocks historical requests until first tick is received', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/nasa/openmct/issues/5221' }); - } -); + // addInitScript to with remote clock + // Switch time conductor mode to 'remote clock' + // Navigate to telemetry + // Verify that the plot renders historical data within the correct bounds + // Refresh the page + // Verify again that the plot renders historical data within the correct bounds + }); +}); diff --git a/e2e/tests/plugins/telemetryTable/telemetryTable.e2e.spec.js b/e2e/tests/plugins/telemetryTable/telemetryTable.e2e.spec.js new file mode 100644 index 0000000000..192c116283 --- /dev/null +++ b/e2e/tests/plugins/telemetryTable/telemetryTable.e2e.spec.js @@ -0,0 +1,104 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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 { test } = require('../../../fixtures'); +const { expect } = require('@playwright/test'); + +test.describe('Telemetry Table', () => { + test('unpauses and filters data when paused by button and user changes bounds', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/nasa/openmct/issues/5113' + }); + + const bannerMessage = '.c-message-banner__message'; + const createButton = 'button:has-text("Create")'; + + await page.goto('/', { waitUntil: 'networkidle' }); + + // Click create button + await page.locator(createButton).click(); + await page.locator('li:has-text("Telemetry Table")').click(); + + await Promise.all([ + page.waitForNavigation(), + page.locator('text=OK').click(), + // Wait for Save Banner to appear + page.waitForSelector(bannerMessage) + ]); + + // Save (exit edit mode) + await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(3).click(); + await page.locator('text=Save and Finish Editing').click(); + + // Click create button + await page.locator(createButton).click(); + + // add Sine Wave Generator with defaults + await page.locator('li:has-text("Sine Wave Generator")').click(); + + await Promise.all([ + page.waitForNavigation(), + page.locator('text=OK').click(), + // Wait for Save Banner to appear + page.waitForSelector(bannerMessage) + ]); + + // focus the Telemetry Table + await page.locator('text=Open MCT My Items >> span').nth(3).click(); + await Promise.all([ + page.waitForNavigation(), + page.locator('text=Unnamed Telemetry Table').first().click() + ]); + + // Click pause button + const pauseButton = page.locator('button.c-button.icon-pause'); + await pauseButton.click(); + + const tableWrapper = page.locator('div.c-table-wrapper'); + await expect(tableWrapper).toHaveClass(/is-paused/); + + // Subtract 5 minutes from the current end bound datetime and set it + const endTimeInput = page.locator('input[type="text"].c-input--datetime').nth(1); + await endTimeInput.click(); + + let endDate = await endTimeInput.inputValue(); + endDate = new Date(endDate); + + endDate.setUTCMinutes(endDate.getUTCMinutes() - 5); + endDate = endDate.toISOString().replace(/T/, ' '); + + await endTimeInput.fill(''); + await endTimeInput.fill(endDate); + await page.keyboard.press('Enter'); + + await expect(tableWrapper).not.toHaveClass(/is-paused/); + + // Get the most recent telemetry date + const latestTelemetryDate = await page.locator('table.c-telemetry-table__body > tbody > tr').last().locator('td').nth(1).getAttribute('title'); + + // Verify that it is <= our new end bound + const latestMilliseconds = Date.parse(latestTelemetryDate); + const endBoundMilliseconds = Date.parse(endDate); + expect(latestMilliseconds).toBeLessThanOrEqual(endBoundMilliseconds); + }); +}); diff --git a/e2e/tests/plugins/timeConductor/timeConductor.e2e.spec.js b/e2e/tests/plugins/timeConductor/timeConductor.e2e.spec.js new file mode 100644 index 0000000000..ed759a53df --- /dev/null +++ b/e2e/tests/plugins/timeConductor/timeConductor.e2e.spec.js @@ -0,0 +1,235 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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 { test } = require('../../../fixtures.js'); +const { expect } = require('@playwright/test'); + +test.describe('Time conductor operations', () => { + test('validate start time does not exceeds end time', async ({ page }) => { + // Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + const year = new Date().getFullYear(); + + let startDate = 'xxxx-01-01 01:00:00.000Z'; + startDate = year + startDate.substring(4); + + let endDate = 'xxxx-01-01 02:00:00.000Z'; + endDate = year + endDate.substring(4); + + const startTimeLocator = page.locator('input[type="text"]').first(); + const endTimeLocator = page.locator('input[type="text"]').nth(1); + + // Click start time + await startTimeLocator.click(); + + // Click end time + await endTimeLocator.click(); + + await endTimeLocator.fill(endDate.toString()); + await startTimeLocator.fill(startDate.toString()); + + // invalid start date + startDate = (year + 1) + startDate.substring(4); + await startTimeLocator.fill(startDate.toString()); + await endTimeLocator.click(); + + const startDateValidityStatus = await startTimeLocator.evaluate((element) => element.checkValidity()); + expect(startDateValidityStatus).not.toBeTruthy(); + + // fix to valid start date + startDate = (year - 1) + startDate.substring(4); + await startTimeLocator.fill(startDate.toString()); + + // invalid end date + endDate = (year - 2) + endDate.substring(4); + await endTimeLocator.fill(endDate.toString()); + await startTimeLocator.click(); + + const endDateValidityStatus = await endTimeLocator.evaluate((element) => element.checkValidity()); + expect(endDateValidityStatus).not.toBeTruthy(); + }); +}); + +// Testing instructions: +// Try to change the realtime offsets when in realtime (local clock) mode. +test.describe('Time conductor input fields real-time mode', () => { + test('validate input fields in real-time mode', async ({ page }) => { + const startOffset = { + secs: '23' + }; + + const endOffset = { + secs: '31' + }; + + // Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Switch to real-time mode + await setRealTimeMode(page); + + // Set start time offset + await setStartOffset(page, startOffset); + + // Verify time was updated on time offset button + await expect(page.locator('data-testid=conductor-start-offset-button')).toContainText('00:30:23'); + + // Set end time offset + await setEndOffset(page, endOffset); + + // Verify time was updated on preceding time offset button + await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:31'); + }); + + /** + * Verify that offsets and url params are preserved when switching + * between fixed timespan and real-time mode. + */ + test('preserve offsets and url params when switching between fixed and real-time mode', async ({ page }) => { + const startOffset = { + mins: '30', + secs: '23' + }; + + const endOffset = { + secs: '01' + }; + + // Convert offsets to milliseconds + const startDelta = (30 * 60 * 1000) + (23 * 1000); + const endDelta = (1 * 1000); + + // Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Switch to real-time mode + await setRealTimeMode(page); + + // Set start time offset + await setStartOffset(page, startOffset); + + // Set end time offset + await setEndOffset(page, endOffset); + + // Switch to fixed timespan mode + await setFixedTimeMode(page); + + // Switch back to real-time mode + await setRealTimeMode(page); + + // Verify updated start time offset persists after mode switch + await expect(page.locator('data-testid=conductor-start-offset-button')).toContainText('00:30:23'); + + // Verify updated end time offset persists after mode switch + await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:01'); + + // Verify url parameters persist after mode switch + await page.waitForNavigation(); + expect(page.url()).toContain(`startDelta=${startDelta}`); + expect(page.url()).toContain(`endDelta=${endDelta}`); + }); +}); + +/** + * @typedef {Object} OffsetValues + * @property {string | undefined} hours + * @property {string | undefined} mins + * @property {string | undefined} secs + */ + +/** + * Set the values (hours, mins, secs) for the start time offset when in realtime mode + * @param {import('@playwright/test').Page} page + * @param {OffsetValues} offset + */ +async function setStartOffset(page, offset) { + const startOffsetButton = page.locator('data-testid=conductor-start-offset-button'); + await setTimeConductorOffset(page, offset, startOffsetButton); +} + +/** + * Set the values (hours, mins, secs) for the end time offset when in realtime mode + * @param {import('@playwright/test').Page} page + * @param {OffsetValues} offset + */ +async function setEndOffset(page, offset) { + const endOffsetButton = page.locator('data-testid=conductor-end-offset-button'); + await setTimeConductorOffset(page, offset, endOffsetButton); +} + +/** + * Set the time conductor to fixed timespan mode + * @param {import('@playwright/test').Page} page + */ +async function setFixedTimeMode(page) { + await setTimeConductorMode(page, true); +} + +/** + * Set the time conductor to realtime mode + * @param {import('@playwright/test').Page} page + */ +async function setRealTimeMode(page) { + await setTimeConductorMode(page, false); +} + +/** + * Set the values (hours, mins, secs) for the TimeConductor offsets when in realtime mode + * @param {import('@playwright/test').Page} page + * @param {OffsetValues} offset + * @param {import('@playwright/test').Locator} offsetButton + */ +async function setTimeConductorOffset(page, {hours, mins, secs}, offsetButton) { + await offsetButton.click(); + + if (hours) { + await page.fill('.pr-time-controls__hrs', hours); + } + + if (mins) { + await page.fill('.pr-time-controls__mins', mins); + } + + if (secs) { + await page.fill('.pr-time-controls__secs', secs); + } + + // Click the check button + await page.locator('.icon-check').click(); +} + +/** + * Set the time conductor mode to either fixed timespan or realtime mode. + * @param {import('@playwright/test').Page} page + * @param {boolean} [isFixedTimespan=true] true for fixed timespan mode, false for realtime mode; default is true + */ +async function setTimeConductorMode(page, isFixedTimespan = true) { + // Click 'mode' button + await page.locator('.c-mode-button').click(); + + // Switch time conductor mode + if (isFixedTimespan) { + await page.locator('data-testid=conductor-modeOption-fixed').click(); + } else { + await page.locator('data-testid=conductor-modeOption-realtime').click(); + } +} diff --git a/e2e/tests/plugins/timer/timer.e2e.spec.js b/e2e/tests/plugins/timer/timer.e2e.spec.js new file mode 100644 index 0000000000..3c8a051a90 --- /dev/null +++ b/e2e/tests/plugins/timer/timer.e2e.spec.js @@ -0,0 +1,185 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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 { test } = require('../../../fixtures.js'); +const { expect } = require('@playwright/test'); + +test.describe('Timer', () => { + + test.beforeEach(async ({ page }) => { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + //Click the Create button + await page.click('button:has-text("Create")'); + + // Click 'Timer' + await page.click('text=Timer'); + + // Click text=OK + await Promise.all([ + page.waitForNavigation({waitUntil: 'networkidle'}), + page.click('text=OK') + ]); + + await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Timer'); + }); + + test('Can perform actions on the Timer', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/nasa/openmct/issues/4313' + }); + + await test.step("From the tree context menu", async () => { + await triggerTimerContextMenuAction(page, 'Start'); + await triggerTimerContextMenuAction(page, 'Pause'); + await triggerTimerContextMenuAction(page, 'Restart at 0'); + await triggerTimerContextMenuAction(page, 'Stop'); + }); + + await test.step("From the 3dot menu", async () => { + await triggerTimer3dotMenuAction(page, 'Start'); + await triggerTimer3dotMenuAction(page, 'Pause'); + await triggerTimer3dotMenuAction(page, 'Restart at 0'); + await triggerTimer3dotMenuAction(page, 'Stop'); + }); + + await test.step("From the object view", async () => { + await triggerTimerViewAction(page, 'Start'); + await triggerTimerViewAction(page, 'Pause'); + await triggerTimerViewAction(page, 'Restart at 0'); + }); + }); +}); + +/** + * Actions that can be performed on a timer from context menus. + * @typedef {'Start' | 'Stop' | 'Pause' | 'Restart at 0'} TimerAction + */ + +/** + * Actions that can be performed on a timer from the object view. + * @typedef {'Start' | 'Pause' | 'Restart at 0'} TimerViewAction + */ + +/** + * Open the timer context menu from the object tree. + * Expands the 'My Items' folder if it is not already expanded. + * @param {import('@playwright/test').Page} page + */ +async function openTimerContextMenu(page) { + const myItemsFolder = page.locator('text=Open MCT My Items >> span').nth(3); + const className = await myItemsFolder.getAttribute('class'); + if (!className.includes('c-disclosure-triangle--expanded')) { + await myItemsFolder.click(); + } + + await page.locator(`a:has-text("Unnamed Timer")`).click({ + button: 'right' + }); +} + +/** + * Trigger a timer action from the tree context menu + * @param {import('@playwright/test').Page} page + * @param {TimerAction} action + */ +async function triggerTimerContextMenuAction(page, action) { + const menuAction = `.c-menu ul li >> text="${action}"`; + await openTimerContextMenu(page); + await page.locator(menuAction).click(); + assertTimerStateAfterAction(page, action); +} + +/** + * Trigger a timer action from the 3dot menu + * @param {import('@playwright/test').Page} page + * @param {TimerAction} action + */ +async function triggerTimer3dotMenuAction(page, action) { + const menuAction = `.c-menu ul li >> text="${action}"`; + const threeDotMenuButton = 'button[title="More options"]'; + let isActionAvailable = false; + let iterations = 0; + // Dismiss/open the 3dot menu until the action is available + // or a maxiumum number of iterations is reached + while (!isActionAvailable && iterations <= 20) { + await page.click('.c-object-view'); + await page.click(threeDotMenuButton); + isActionAvailable = await page.locator(menuAction).isVisible(); + iterations++; + } + + await page.locator(menuAction).click(); + assertTimerStateAfterAction(page, action); +} + +/** + * Trigger a timer action from the object view + * @param {import('@playwright/test').Page} page + * @param {TimerViewAction} action + */ +async function triggerTimerViewAction(page, action) { + await page.locator('.c-timer').hover({trial: true}); + const buttonTitle = buttonTitleFromAction(action); + await page.click(`button[title="${buttonTitle}"]`); + assertTimerStateAfterAction(page, action); +} + +/** + * Takes in a TimerViewAction and returns the button title + * @param {TimerViewAction} action + */ +function buttonTitleFromAction(action) { + switch (action) { + case 'Start': + return 'Start'; + case 'Pause': + return 'Pause'; + case 'Restart at 0': + return 'Reset'; + } +} + +/** + * Verify the timer state after a timer action has been performed. + * @param {import('@playwright/test').Page} page + * @param {TimerAction} action + */ +async function assertTimerStateAfterAction(page, action) { + let timerStateClass; + switch (action) { + case 'Start': + case 'Restart at 0': + timerStateClass = "is-started"; + break; + case 'Stop': + timerStateClass = 'is-stopped'; + break; + case 'Pause': + timerStateClass = 'is-paused'; + break; + } + + await expect.soft(page.locator('.c-timer')).toHaveClass(new RegExp(timerStateClass)); +} diff --git a/e2e/tests/smoke.spec.js b/e2e/tests/smoke.e2e.spec.js similarity index 78% rename from e2e/tests/smoke.spec.js rename to e2e/tests/smoke.e2e.spec.js index 1ce574b1a6..f8f87794f7 100644 --- a/e2e/tests/smoke.spec.js +++ b/e2e/tests/smoke.e2e.spec.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -33,7 +33,8 @@ comfortable running this test during a live mission?" Avoid creating or deleting Make no assumptions about the order that elements appear in the DOM. */ -const { test, expect } = require('@playwright/test'); +const { test } = require('../fixtures.js'); +const { expect } = require('@playwright/test'); test('Verify that the create button appears and that the Folder Domain Object is available for selection', async ({ page }) => { @@ -44,6 +45,15 @@ test('Verify that the create button appears and that the Folder Domain Object is await page.click('button:has-text("Create")'); // Verify that Create Folder appears in the dropdown - const locator = page.locator(':nth-match(:text("Folder"), 2)'); - await expect(locator).toBeEnabled(); + await expect(page.locator(':nth-match(:text("Folder"), 2)')).toBeEnabled(); +}); + +test('Verify that My Items Tree appears @ipad', async ({ page }) => { + //Test.slow annotation is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374 + test.slow(); + //Go to baseURL + await page.goto('/'); + + //My Items to be visible + await expect(page.locator('a:has-text("My Items")')).toBeEnabled(); }); diff --git a/e2e/tests/ui/layout/search/grandsearch.e2e.spec.js b/e2e/tests/ui/layout/search/grandsearch.e2e.spec.js new file mode 100644 index 0000000000..f67b336731 --- /dev/null +++ b/e2e/tests/ui/layout/search/grandsearch.e2e.spec.js @@ -0,0 +1,111 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to tests which verify search functionality. +*/ + +const { expect } = require('@playwright/test'); +const { test } = require('../../../../fixtures'); + +/** + * Creates a notebook object and adds an entry. + * @param {import('@playwright/test').Page} page + */ +async function createClockAndDisplayLayout(page) { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Click button:has-text("Create") + await page.locator('button:has-text("Create")').click(); + // Click li:has-text("Notebook") + await page.locator('li:has-text("Clock")').click(); + // Click button:has-text("OK") + await Promise.all([ + page.waitForNavigation(), + page.locator('button:has-text("OK")').click() + ]); + + // Click a:has-text("My Items") + await Promise.all([ + page.waitForNavigation(), + page.locator('a:has-text("My Items") >> nth=0').click() + ]); + // Click button:has-text("Create") + await page.locator('button:has-text("Create")').click(); + // Click li:has-text("Notebook") + await page.locator('li:has-text("Display Layout")').click(); + // Click button:has-text("OK") + await Promise.all([ + page.waitForNavigation(), + page.locator('button:has-text("OK")').click() + ]); +} + +test.describe('Grand Search', () => { + test('Can search for objects, and subsequent search dropdown behaves properly', async ({ page }) => { + await createClockAndDisplayLayout(page); + + // Click [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); + // Fill [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Cl'); + await expect(page.locator('[aria-label="Search Result"]')).toContainText('Clock'); + // Click text=Elements >> nth=0 + await page.locator('text=Elements').first().click(); + await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible(); + + // Click [aria-label="OpenMCT Search"] [aria-label="Search Input"] + await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click(); + // Click [aria-label="Unnamed Clock clock result"] >> text=Unnamed Clock + await page.locator('[aria-label="Unnamed Clock clock result"] >> text=Unnamed Clock').click(); + await expect(page.locator('.js-preview-window')).toBeVisible(); + + // Click [aria-label="Close"] + await page.locator('[aria-label="Close"]').click(); + await expect(page.locator('[aria-label="Search Result"]')).toBeVisible(); + await expect(page.locator('[aria-label="Search Result"]')).toContainText('Cloc'); + + // Click [aria-label="OpenMCT Search"] a >> nth=0 + await page.locator('[aria-label="OpenMCT Search"] a').first().click(); + await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible(); + + // Fill [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('foo'); + await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible(); + + // Click text=Snapshot Save and Finish Editing Save and Continue Editing >> button >> nth=1 + await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); + // Click text=Save and Finish Editing + await page.locator('text=Save and Finish Editing').click(); + // Click [aria-label="OpenMCT Search"] [aria-label="Search Input"] + await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click(); + // Fill [aria-label="OpenMCT Search"] [aria-label="Search Input"] + await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').fill('Cl'); + // Click text=Unnamed Clock + await Promise.all([ + page.waitForNavigation(), + page.locator('text=Unnamed Clock').click() + ]); + await expect(page.locator('.is-object-type-clock')).toBeVisible(); + }); +}); diff --git a/e2e/tests/visual/addInit.visual.spec.js b/e2e/tests/visual/addInit.visual.spec.js new file mode 100644 index 0000000000..c7f142c242 --- /dev/null +++ b/e2e/tests/visual/addInit.visual.spec.js @@ -0,0 +1,76 @@ +/* eslint-disable no-undef */ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + +/* +Collection of Visual Tests set to run with modified init scripts to inject plugins not otherwise available in the default contexts. + +These should only use functional expect statements to verify assumptions about the state +in a test and not for functional verification of correctness. Visual tests are not supposed +to "fail" on assertions. Instead, they should be used to detect changes between builds or branches. + +Note: Larger testsuite sizes are OK due to the setup time associated with these tests. +*/ + +const { test } = require('@playwright/test'); +const percySnapshot = require('@percy/playwright'); +const path = require('path'); +const sinon = require('sinon'); + +const VISUAL_GRACE_PERIOD = 5 * 1000; //Lets the application "simmer" before the snapshot is taken + +const CUSTOM_NAME = 'CUSTOM_NAME'; + +// Snippet from https://github.com/microsoft/playwright/issues/6347#issuecomment-965887758 +// Will replace with cy.clock() equivalent +test.beforeEach(async ({ context }) => { + await context.addInitScript({ + path: path.join(__dirname, '../../..', './node_modules/sinon/pkg/sinon.js') + }); + await context.addInitScript(() => { + window.__clock = sinon.useFakeTimers({ + now: 0, + shouldAdvanceTime: true + }); //Set browser clock to UNIX Epoch + }); +}); + +test('Visual - Restricted Notebook is visually correct @addInit', async ({ page }) => { + // eslint-disable-next-line no-undef + await page.addInitScript({ path: path.join(__dirname, '../plugins/notebook', './addInitRestrictedNotebook.js') }); + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + //Click the Create button + await page.click('button:has-text("Create")'); + // Click text=CUSTOM_NAME + await page.click(`text=${CUSTOM_NAME}`); + // Click text=OK + await Promise.all([ + page.waitForNavigation({waitUntil: 'networkidle'}), + page.click('text=OK') + ]); + + // Take a snapshot of the newly created CUSTOM_NAME notebook + await page.waitForTimeout(VISUAL_GRACE_PERIOD); + await percySnapshot(page, 'Restricted Notebook with CUSTOM_NAME'); + +}); diff --git a/e2e/tests/visual/controlledClock.visual.spec.js b/e2e/tests/visual/controlledClock.visual.spec.js new file mode 100644 index 0000000000..7eb7f64c84 --- /dev/null +++ b/e2e/tests/visual/controlledClock.visual.spec.js @@ -0,0 +1,70 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + +/* +Collection of Visual Tests set to run in a default context. The tests within this suite +are only meant to run against openmct's app.js started by `npm run start` within the +`./e2e/playwright-visual.config.js` file. + +These should only use functional expect statements to verify assumptions about the state +in a test and not for functional verification of correctness. Visual tests are not supposed +to "fail" on assertions. Instead, they should be used to detect changes between builds or branches. + +Note: Larger testsuite sizes are OK due to the setup time associated with these tests. +*/ + +const { test, expect } = require('@playwright/test'); +const percySnapshot = require('@percy/playwright'); +const path = require('path'); +const sinon = require('sinon'); + +// Snippet from https://github.com/microsoft/playwright/issues/6347#issuecomment-965887758 +// Will replace with cy.clock() equivalent +test.beforeEach(async ({ context }) => { + await context.addInitScript({ + // eslint-disable-next-line no-undef + path: path.join(__dirname, '../../..', './node_modules/sinon/pkg/sinon.js') + }); + await context.addInitScript(() => { + window.__clock = sinon.useFakeTimers({ + now: 0, //Set browser clock to UNIX Epoch + shouldAdvanceTime: false, //Don't advance the clock + toFake: ["setTimeout", "nextTick"] + }); + }); +}); +test.use({ storageState: './e2e/test-data/VisualTestData_storage.json' }); + +test('Visual - Overlay Plot Loading Indicator @localstorage', async ({ page }) => { + // Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + await page.locator('a:has-text("Unnamed Overlay Plot Overlay Plot")').click(); + //Ensure that we're on the Unnamed Overlay Plot object + await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Overlay Plot'); + + //Wait for canvas to be rendered and stop animating + await page.locator('canvas >> nth=1').hover({trial: true}); + + //Take snapshot of Sine Wave Generator within Overlay Plot + await percySnapshot(page, 'SineWaveInOverlayPlot'); +}); diff --git a/e2e/tests/visual/default.spec.js b/e2e/tests/visual/default.spec.js deleted file mode 100644 index fa6308f2fb..0000000000 --- a/e2e/tests/visual/default.spec.js +++ /dev/null @@ -1,113 +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. - *****************************************************************************/ - -/* -Collection of Visual Tests set to run in a default context. The tests within this suite -are only meant to run against openmct's app.js started by `npm run start` within the -`./e2e/playwright-visual.config.js` file. - -These should only use functional expect statements to verify assumptions about the state -in a test and not for functional verification of correctness. Visual tests are not supposed -to "fail" on assertions. Instead, they should be used to detect changes between builds or branches. - -Note: Larger testsuite sizes are OK due to the setup time associated with these tests. -*/ - -const { test, expect } = require('@playwright/test'); -const percySnapshot = require('@percy/playwright'); -const path = require('path'); -const sinon = require('sinon'); - -const VISUAL_GRACE_PERIOD = 5 * 1000; //Lets the application "simmer" before the snapshot is taken - -// Snippet from https://github.com/microsoft/playwright/issues/6347#issuecomment-965887758 -// Will replace with cy.clock() equivalent -test.beforeEach(async ({ context }) => { - await context.addInitScript({ - // eslint-disable-next-line no-undef - path: path.join(__dirname, '../../..', './node_modules/sinon/pkg/sinon.js') - }); - await context.addInitScript(() => { - window.__clock = sinon.useFakeTimers(); //Set browser clock to UNIX Epoch - }); -}); - -test('Visual - Root and About', async ({ page }) => { - // Go to baseURL - await page.goto('/', { waitUntil: 'networkidle' }); - - // Verify that Create button is actionable - const createButtonLocator = page.locator('button:has-text("Create")'); - await expect(createButtonLocator).toBeEnabled(); - - // Take a snapshot of the Dashboard - await page.waitForTimeout(VISUAL_GRACE_PERIOD); - await percySnapshot(page, 'Root'); - - // Click About button - await page.click('.l-shell__app-logo'); - - // Modify the Build information in 'about' to be consistent run-over-run - const versionInformationLocator = page.locator('ul.t-info.l-info.s-info'); - await expect(versionInformationLocator).toBeEnabled(); - await versionInformationLocator.evaluate(node => node.innerHTML = '
  • Version: visual-snapshot
  • Build Date: Mon Nov 15 2021 08:07:51 GMT-0800 (Pacific Standard Time)
  • Revision: 93049cdbc6c047697ca204893db9603b864b8c9f
  • Branch: master
  • '); - - // Take a snapshot of the About modal - await page.waitForTimeout(VISUAL_GRACE_PERIOD); - await percySnapshot(page, 'About'); -}); - -test('Visual - Default Condition Set', async ({ page }) => { - //Go to baseURL - await page.goto('/', { waitUntil: 'networkidle' }); - - //Click the Create button - await page.click('button:has-text("Create")'); - - // Click text=Condition Set - await page.click('text=Condition Set'); - - // Click text=OK - await page.click('text=OK'); - - // Take a snapshot of the newly created Condition Set object - await page.waitForTimeout(VISUAL_GRACE_PERIOD); - await percySnapshot(page, 'Default Condition Set'); -}); - -test('Visual - Default Condition Widget', async ({ page }) => { - //Go to baseURL - await page.goto('/', { waitUntil: 'networkidle' }); - - //Click the Create button - await page.click('button:has-text("Create")'); - - // Click text=Condition Widget - await page.click('text=Condition Widget'); - - // Click text=OK - await page.click('text=OK'); - - // Take a snapshot of the newly created Condition Widget object - await page.waitForTimeout(VISUAL_GRACE_PERIOD); - await percySnapshot(page, 'Default Condition Widget'); -}); diff --git a/e2e/tests/visual/default.visual.spec.js b/e2e/tests/visual/default.visual.spec.js new file mode 100644 index 0000000000..d5c2210ee3 --- /dev/null +++ b/e2e/tests/visual/default.visual.spec.js @@ -0,0 +1,232 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + +/* +Collection of Visual Tests set to run in a default context. The tests within this suite +are only meant to run against openmct's app.js started by `npm run start` within the +`./e2e/playwright-visual.config.js` file. + +These should only use functional expect statements to verify assumptions about the state +in a test and not for functional verification of correctness. Visual tests are not supposed +to "fail" on assertions. Instead, they should be used to detect changes between builds or branches. + +Note: Larger testsuite sizes are OK due to the setup time associated with these tests. +*/ + +const { test } = require('../../fixtures.js'); +const { expect } = require('@playwright/test'); +const percySnapshot = require('@percy/playwright'); +const path = require('path'); +const sinon = require('sinon'); + +const VISUAL_GRACE_PERIOD = 5 * 1000; //Lets the application "simmer" before the snapshot is taken + +// Snippet from https://github.com/microsoft/playwright/issues/6347#issuecomment-965887758 +// Will replace with cy.clock() equivalent +test.beforeEach(async ({ context }) => { + await context.addInitScript({ + // eslint-disable-next-line no-undef + path: path.join(__dirname, '../../..', './node_modules/sinon/pkg/sinon.js') + }); + await context.addInitScript(() => { + window.__clock = sinon.useFakeTimers({ + now: 0, + shouldAdvanceTime: true + }); //Set browser clock to UNIX Epoch + }); +}); + +test('Visual - Root and About', async ({ page }) => { + // Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Verify that Create button is actionable + await expect(page.locator('button:has-text("Create")')).toBeEnabled(); + + // Take a snapshot of the Dashboard + await page.waitForTimeout(VISUAL_GRACE_PERIOD); + await percySnapshot(page, 'Root'); + + // Click About button + await page.click('.l-shell__app-logo'); + + // Modify the Build information in 'about' to be consistent run-over-run + const versionInformationLocator = page.locator('ul.t-info.l-info.s-info'); + await expect(versionInformationLocator).toBeEnabled(); + await versionInformationLocator.evaluate(node => node.innerHTML = '
  • Version: visual-snapshot
  • Build Date: Mon Nov 15 2021 08:07:51 GMT-0800 (Pacific Standard Time)
  • Revision: 93049cdbc6c047697ca204893db9603b864b8c9f
  • Branch: master
  • '); + + // Take a snapshot of the About modal + await page.waitForTimeout(VISUAL_GRACE_PERIOD); + await percySnapshot(page, 'About'); +}); + +test('Visual - Default Condition Set', async ({ page }) => { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + //Click the Create button + await page.click('button:has-text("Create")'); + + // Click text=Condition Set + await page.click('text=Condition Set'); + + // Click text=OK + await page.click('text=OK'); + + // Take a snapshot of the newly created Condition Set object + await page.waitForTimeout(VISUAL_GRACE_PERIOD); + await percySnapshot(page, 'Default Condition Set'); +}); + +test.fixme('Visual - Default Condition Widget', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/nasa/openmct/issues/5349' + }); + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + //Click the Create button + await page.click('button:has-text("Create")'); + + // Click text=Condition Widget + await page.click('text=Condition Widget'); + + // Click text=OK + await page.click('text=OK'); + + // Take a snapshot of the newly created Condition Widget object + await page.waitForTimeout(VISUAL_GRACE_PERIOD); + await percySnapshot(page, 'Default Condition Widget'); +}); + +test('Visual - Time Conductor start time is less than end time', async ({ page }) => { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + const year = new Date().getFullYear(); + + let startDate = 'xxxx-01-01 01:00:00.000Z'; + startDate = year + startDate.substring(4); + + let endDate = 'xxxx-01-01 02:00:00.000Z'; + endDate = year + endDate.substring(4); + + await page.locator('input[type="text"]').nth(1).fill(endDate.toString()); + await page.locator('input[type="text"]').first().fill(startDate.toString()); + + // verify no error msg + await page.waitForTimeout(VISUAL_GRACE_PERIOD); + await percySnapshot(page, 'Default Time conductor'); + + startDate = (year + 1) + startDate.substring(4); + await page.locator('input[type="text"]').first().fill(startDate.toString()); + await page.locator('input[type="text"]').nth(1).click(); + + // verify error msg for start time (unable to capture snapshot of popup) + await page.waitForTimeout(VISUAL_GRACE_PERIOD); + await percySnapshot(page, 'Start time error'); + + startDate = (year - 1) + startDate.substring(4); + await page.locator('input[type="text"]').first().fill(startDate.toString()); + + endDate = (year - 2) + endDate.substring(4); + await page.locator('input[type="text"]').nth(1).fill(endDate.toString()); + + await page.locator('input[type="text"]').first().click(); + + // verify error msg for end time (unable to capture snapshot of popup) + await page.waitForTimeout(VISUAL_GRACE_PERIOD); + await percySnapshot(page, 'End time error'); +}); + +test('Visual - Sine Wave Generator Form', async ({ page }) => { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + //Click the Create button + await page.click('button:has-text("Create")'); + + // Click text=Sine Wave Generator + await page.click('text=Sine Wave Generator'); + + await page.waitForTimeout(VISUAL_GRACE_PERIOD); + await percySnapshot(page, 'Default Sine Wave Generator Form'); + + await page.locator('.field.control.l-input-sm input').first().click(); + await page.locator('.field.control.l-input-sm input').first().fill(''); + + // Validate red x mark + await page.waitForTimeout(VISUAL_GRACE_PERIOD); + await percySnapshot(page, 'removed amplitude property value'); +}); + +test('Visual - Save Successful Banner', async ({ page }) => { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + //Click the Create button + await page.click('button:has-text("Create")'); + + //NOTE Something other than example imagery + await page.click('text=Timer'); + + // Click text=OK + await page.click('text=OK'); + await page.locator('.c-message-banner__message').hover({ trial: true }); + await percySnapshot(page, 'Banner message shown'); + + //Wait until Save Banner is gone + await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); + await percySnapshot(page, 'Banner message gone'); +}); + +test('Visual - Display Layout Icon is correct', async ({ page }) => { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + //Click the Create button + await page.click('button:has-text("Create")'); + + //Hover on Display Layout option. + await page.locator('text=Display Layout').hover(); + await percySnapshot(page, 'Display Layout Create Menu'); + +}); + +test('Visual - Default Gauge is correct', async ({ page }) => { + + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + //Click the Create button + await page.click('button:has-text("Create")'); + + await page.click('text=Gauge'); + + await page.click('text=OK'); + + // Take a snapshot of the newly created Gauge object + await page.waitForTimeout(VISUAL_GRACE_PERIOD); + await percySnapshot(page, 'Default Gauge'); + +}); + diff --git a/e2e/tests/visual/generateVisualTestData.e2e.spec.js b/e2e/tests/visual/generateVisualTestData.e2e.spec.js new file mode 100644 index 0000000000..fc67dab723 --- /dev/null +++ b/e2e/tests/visual/generateVisualTestData.e2e.spec.js @@ -0,0 +1,86 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to generating LocalStorage via Session Storage to be used +in some visual test suites like controlledClock.visual.spec.js. This suite should run to completion +and generate an artifact named ./e2e/test-data/VisualTestData_storage.json . This will run +on every Commit to ensure that this object still loads into tests correctly and will retain the +.e2e.spec.js suffix. + +TODO: Provide additional validation of object properties as it grows. + +*/ + +const { test } = require('../../fixtures.js'); +const { expect } = require('@playwright/test'); + +test('Generate Visual Test Data @localStorage', async ({ page, context }) => { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + await page.locator('button:has-text("Create")').click(); + + // add overlay plot with defaults + await page.locator('li:has-text("Overlay Plot")').click(); + + // Click on My Items in Tree. Workaround for https://github.com/nasa/openmct/issues/5184 + await page.click('form[name="mctForm"] a:has-text("My Items")'); + + await Promise.all([ + page.waitForNavigation(), + page.locator('text=OK').click(), + //Wait for Save Banner to appear1 + page.waitForSelector('.c-message-banner__message') + ]); + + // save (exit edit mode) + await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); + await page.locator('text=Save and Finish Editing').click(); + + // click create button + await page.locator('button:has-text("Create")').click(); + + // add sine wave generator with defaults + await page.locator('li:has-text("Sine Wave Generator")').click(); + + //Add a 5000 ms Delay + await page.locator('[aria-label="Loading Delay \\(ms\\)"]').fill('5000'); + + await Promise.all([ + page.waitForNavigation(), + page.locator('text=OK').click(), + //Wait for Save Banner to appear1 + page.waitForSelector('.c-message-banner__message') + ]); + + // focus the overlay plot + await page.locator('text=Open MCT My Items >> span').nth(3).click(); + await Promise.all([ + page.waitForNavigation(), + page.locator('text=Unnamed Overlay Plot').first().click() + ]); + + await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Overlay Plot'); + //Save localStorage for future test execution + await context.storageState({ path: './e2e/test-data/VisualTestData_storage.json' }); +}); diff --git a/e2e/tests/visual/search.visual.spec.js b/e2e/tests/visual/search.visual.spec.js new file mode 100644 index 0000000000..654a336dd8 --- /dev/null +++ b/e2e/tests/visual/search.visual.spec.js @@ -0,0 +1,104 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to tests which verify search functionality. +*/ + +const { test, expect } = require('@playwright/test'); +const percySnapshot = require('@percy/playwright'); + +/** + * Creates a notebook object and adds an entry. + * @param {import('@playwright/test').Page} page + */ +async function createClockAndDisplayLayout(page) { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + // Click button:has-text("Create") + await page.locator('button:has-text("Create")').click(); + // Click li:has-text("Notebook") + await page.locator('li:has-text("Clock")').click(); + // Click button:has-text("OK") + await Promise.all([ + page.waitForNavigation(), + page.locator('button:has-text("OK")').click() + ]); + + // Click a:has-text("My Items") + await Promise.all([ + page.waitForNavigation(), + page.locator('a:has-text("My Items") >> nth=0').click() + ]); + // Click button:has-text("Create") + await page.locator('button:has-text("Create")').click(); + // Click li:has-text("Notebook") + await page.locator('li:has-text("Display Layout")').click(); + // Click button:has-text("OK") + await Promise.all([ + page.waitForNavigation(), + page.locator('button:has-text("OK")').click() + ]); +} + +test.describe('Grand Search', () => { + test('Can search for objects, and subsequent search dropdown behaves properly', async ({ page }) => { + await createClockAndDisplayLayout(page); + + // Click [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); + // Fill [aria-label="OpenMCT Search"] input[type="search"] + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Cl'); + await expect(page.locator('[aria-label="Search Result"]')).toContainText('Clock'); + await percySnapshot(page, 'Searching for Clocks'); + // Click text=Elements >> nth=0 + await page.locator('text=Elements').first().click(); + await expect(page.locator('[aria-label="Search Result"]')).not.toBeVisible(); + + // Click [aria-label="OpenMCT Search"] [aria-label="Search Input"] + await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click(); + // Click [aria-label="Unnamed Clock clock result"] >> text=Unnamed Clock + await page.locator('[aria-label="Unnamed Clock clock result"] >> text=Unnamed Clock').click(); + await percySnapshot(page, 'Preview for clock should display when editing enabled and search item clicked'); + + // Click [aria-label="Close"] + await page.locator('[aria-label="Close"]').click(); + await percySnapshot(page, 'Search should still be showing after preview closed'); + + // Click text=Snapshot Save and Finish Editing Save and Continue Editing >> button >> nth=1 + await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); + // Click text=Save and Finish Editing + await page.locator('text=Save and Finish Editing').click(); + // Click [aria-label="OpenMCT Search"] [aria-label="Search Input"] + await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click(); + // Fill [aria-label="OpenMCT Search"] [aria-label="Search Input"] + await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').fill('Cl'); + // Click text=Unnamed Clock + await Promise.all([ + page.waitForNavigation(), + page.locator('text=Unnamed Clock').click() + ]); + await percySnapshot(page, 'Clicking on search results should navigate to them if not editing'); + + }); +}); diff --git a/example/profiling/bundle.js b/example/eventGenerator/EventMetadataProvider.js similarity index 53% rename from example/profiling/bundle.js rename to example/eventGenerator/EventMetadataProvider.js index df48fcc0a9..bf566f61ba 100644 --- a/example/profiling/bundle.js +++ b/example/eventGenerator/EventMetadataProvider.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -20,36 +20,45 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -define([ - "./src/WatchIndicator", - "./src/DigestIndicator" -], function ( - WatchIndicator, - DigestIndicator -) { - "use strict"; - - return { - name: "example/profiling", - definition: { - "extensions": { - "indicators": [ +class EventMetadataProvider { + constructor() { + this.METADATA_BY_TYPE = { + 'eventGenerator': { + values: [ { - "implementation": WatchIndicator, - "depends": [ - "$interval", - "$rootScope" - ] + key: "name", + name: "Name", + format: "string" }, { - "implementation": DigestIndicator, - "depends": [ - "$interval", - "$rootScope" - ] + key: "utc", + name: "Time", + format: "utc", + hints: { + domain: 1 + } + }, + { + key: "message", + name: "Message", + format: "string" } ] } - } - }; -}); + }; + } + + supportsMetadata(domainObject) { + return Object.prototype.hasOwnProperty.call(this.METADATA_BY_TYPE, domainObject.type); + } + + getMetadata(domainObject) { + return Object.assign( + {}, + domainObject.telemetry, + this.METADATA_BY_TYPE[domainObject.type] + ); + } +} + +export default EventMetadataProvider; diff --git a/example/eventGenerator/EventTelemetryProvider.js b/example/eventGenerator/EventTelemetryProvider.js new file mode 100644 index 0000000000..b5e2dbec02 --- /dev/null +++ b/example/eventGenerator/EventTelemetryProvider.js @@ -0,0 +1,96 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + +/** + * Module defining EventTelemetryProvider. Created by chacskaylo on 06/18/2015. + */ + +import messages from './transcript.json'; + +class EventTelemetryProvider { + constructor() { + this.defaultSize = 25; + } + + generateData(firstObservedTime, count, startTime, duration, name) { + const millisecondsSinceStart = startTime - firstObservedTime; + const utc = startTime + (count * duration); + const ind = count % messages.length; + const message = messages[ind] + " - [" + millisecondsSinceStart + "]"; + + return { + name, + utc, + message + }; + } + + supportsRequest(domainObject) { + return domainObject.type === 'eventGenerator'; + } + + supportsSubscribe(domainObject) { + return domainObject.type === 'eventGenerator'; + } + + subscribe(domainObject, callback) { + const duration = domainObject.telemetry.duration * 1000; + const firstObservedTime = Date.now(); + let count = 0; + + const interval = setInterval(() => { + const startTime = Date.now(); + const datum = this.generateData(firstObservedTime, count, startTime, duration, domainObject.name); + count += 1; + callback(datum); + }, duration); + + return function () { + clearInterval(interval); + }; + } + + request(domainObject, options) { + let start = options.start; + const end = Math.min(Date.now(), options.end); // no future values + const duration = domainObject.telemetry.duration * 1000; + const size = options.size ? options.size : this.defaultSize; + const data = []; + const firstObservedTime = options.start; + let count = 0; + + if (options.strategy === 'latest' || options.size === 1) { + start = end; + } + + while (start <= end && data.length < size) { + const startTime = options.start + count; + data.push(this.generateData(firstObservedTime, count, startTime, duration, domainObject.name)); + start += duration; + count += 1; + } + + return Promise.resolve(data); + } +} + +export default EventTelemetryProvider; diff --git a/example/eventGenerator/bundle.js b/example/eventGenerator/bundle.js deleted file mode 100644 index 8c37d75623..0000000000 --- a/example/eventGenerator/bundle.js +++ /dev/null @@ -1,80 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/EventTelemetryProvider" -], function ( - EventTelemetryProvider -) { - "use strict"; - - return { - name: "example/eventGenerator", - definition: { - "name": "Event Message Generator", - "description": "For development use. Creates sample event message data that mimics a live data stream.", - "extensions": { - "components": [ - { - "implementation": EventTelemetryProvider, - "type": "provider", - "provides": "telemetryService", - "depends": [ - "$q", - "$timeout" - ] - } - ], - "types": [ - { - "key": "eventGenerator", - "name": "Event Message Generator", - "cssClass": "icon-generator-events", - "description": "For development use. Creates sample event message data that mimics a live data stream.", - "priority": 10, - "features": "creation", - "model": { - "telemetry": {} - }, - "telemetry": { - "source": "eventGenerator", - "domains": [ - { - "key": "utc", - "name": "Timestamp", - "format": "utc" - } - ], - "ranges": [ - { - "key": "message", - "name": "Message", - "format": "string" - } - ] - } - } - ] - } - } - }; -}); diff --git a/example/identity/bundle.js b/example/eventGenerator/plugin.js similarity index 57% rename from example/identity/bundle.js rename to example/eventGenerator/plugin.js index 45c7b43380..513406dc1a 100644 --- a/example/identity/bundle.js +++ b/example/eventGenerator/plugin.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -19,30 +19,24 @@ * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ +import EventTelmetryProvider from './EventTelemetryProvider'; +import EventMetadataProvider from './EventMetadataProvider'; -define([ - "./src/ExampleIdentityService" -], function ( - ExampleIdentityService -) { - "use strict"; - - return { - name: "example/identity", - definition: { - "extensions": { - "components": [ - { - "implementation": ExampleIdentityService, - "provides": "identityService", - "type": "provider", - "depends": [ - "dialogService", - "$q" - ] - } - ] +export default function EventGeneratorPlugin(options) { + return function install(openmct) { + openmct.types.addType("eventGenerator", { + name: "Event Message Generator", + description: "For development use. Creates sample event message data that mimics a live data stream.", + cssClass: "icon-generator-events", + creatable: true, + initialize: function (object) { + object.telemetry = { + duration: 5 + }; } - } + }); + openmct.telemetry.addProvider(new EventTelmetryProvider()); + openmct.telemetry.addProvider(new EventMetadataProvider()); + }; -}); +} diff --git a/example/eventGenerator/pluginSpec.js b/example/eventGenerator/pluginSpec.js new file mode 100644 index 0000000000..54627b7b22 --- /dev/null +++ b/example/eventGenerator/pluginSpec.js @@ -0,0 +1,76 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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 EventMessageGeneratorPlugin from './plugin.js'; +import { + createOpenMct, + resetApplicationState +} from '../../src/utils/testing'; + +describe('the plugin', () => { + let openmct; + const mockDomainObject = { + identifier: { + namespace: '', + key: 'some-value' + }, + telemetry: { + duration: 0 + }, + options: {}, + type: 'eventGenerator' + }; + + beforeEach((done) => { + const options = {}; + openmct = createOpenMct(); + openmct.install(new EventMessageGeneratorPlugin(options)); + openmct.on('start', done); + openmct.startHeadless(); + }); + + afterEach(async () => { + await resetApplicationState(openmct); + }); + + describe('the plugin', () => { + it("supports subscription", (done) => { + const unsubscribe = openmct.telemetry.subscribe(mockDomainObject, (telemetry) => { + expect(telemetry).not.toEqual(null); + expect(telemetry.message).toContain('CC: Eagle, Houston'); + expect(unsubscribe).not.toEqual(null); + unsubscribe(); + done(); + }); + }); + + it("supports requests without start/end defined", async () => { + const telemetry = await openmct.telemetry.request(mockDomainObject); + expect(telemetry[0].message).toContain('CC: Eagle, Houston'); + }); + + it("supports requests with arbitrary start time in the past", async () => { + mockDomainObject.options.start = 100000000000; // Mar 03 1973 + const telemetry = await openmct.telemetry.request(mockDomainObject); + expect(telemetry[0].message).toContain('CC: Eagle, Houston'); + }); + }); +}); diff --git a/example/eventGenerator/src/EventTelemetry.js b/example/eventGenerator/src/EventTelemetry.js deleted file mode 100644 index 3311761759..0000000000 --- a/example/eventGenerator/src/EventTelemetry.js +++ /dev/null @@ -1,62 +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. - *****************************************************************************/ - -/** - * Module defining EventTelemetry. - * Created by chacskaylo on 06/18/2015. - * Modified by shale on 06/23/2015. - */ -define( - ['../data/transcript.json'], - function (messages) { - "use strict"; - - var firstObservedTime = Date.now(); - - function EventTelemetry(request, interval) { - - var latestObservedTime = Date.now(), - count = Math.floor((latestObservedTime - firstObservedTime) / interval), - generatorData = {}; - - generatorData.getPointCount = function () { - return count; - }; - - generatorData.getDomainValue = function (i, domain) { - return i * interval - + (domain !== 'delta' ? firstObservedTime : 0); - }; - - generatorData.getRangeValue = function (i, range) { - var domainDelta = this.getDomainValue(i) - firstObservedTime, - ind = i % messages.length; - - return messages[ind] + " - [" + domainDelta.toString() + "]"; - }; - - return generatorData; - } - - return EventTelemetry; - } -); diff --git a/example/eventGenerator/src/EventTelemetryProvider.js b/example/eventGenerator/src/EventTelemetryProvider.js deleted file mode 100644 index c6ed7ac90b..0000000000 --- a/example/eventGenerator/src/EventTelemetryProvider.js +++ /dev/null @@ -1,118 +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. - *****************************************************************************/ - -/** - * Module defining EventTelemetryProvider. Created by chacskaylo on 06/18/2015. - */ -define( - ["./EventTelemetry"], - function (EventTelemetry) { - "use strict"; - - /** - * - * @constructor - */ - function EventTelemetryProvider($q, $timeout) { - var subscriptions = [], - genInterval = 1000, - generating = false; - - // - function matchesSource(request) { - return request.source === "eventGenerator"; - } - - // Used internally; this will be repacked by doPackage - function generateData(request) { - return { - key: request.key, - telemetry: new EventTelemetry(request, genInterval) - }; - } - - // - function doPackage(results) { - var packaged = {}; - results.forEach(function (result) { - packaged[result.key] = result.telemetry; - }); - - // Format as expected (sources -> keys -> telemetry) - return { eventGenerator: packaged }; - } - - function requestTelemetry(requests) { - return $timeout(function () { - return doPackage(requests.filter(matchesSource).map(generateData)); - }, 0); - } - - function handleSubscriptions(timeout) { - subscriptions.forEach(function (subscription) { - var requests = subscription.requests; - subscription.callback(doPackage( - requests.filter(matchesSource).map(generateData) - )); - }); - } - - function startGenerating() { - generating = true; - $timeout(function () { - handleSubscriptions(); - if (generating && subscriptions.length > 0) { - startGenerating(); - } else { - generating = false; - } - }, genInterval); - } - - function subscribe(callback, requests) { - var subscription = { - callback: callback, - requests: requests - }; - function unsubscribe() { - subscriptions = subscriptions.filter(function (s) { - return s !== subscription; - }); - } - - subscriptions.push(subscription); - if (!generating) { - startGenerating(); - } - - return unsubscribe; - } - - return { - requestTelemetry: requestTelemetry, - subscribe: subscribe - }; - } - - return EventTelemetryProvider; - } -); diff --git a/example/eventGenerator/data/transcript.json b/example/eventGenerator/transcript.json similarity index 100% rename from example/eventGenerator/data/transcript.json rename to example/eventGenerator/transcript.json diff --git a/platform/persistence/queue/test/PersistenceFailureConstantsSpec.js b/example/exampleTags/plugin.js similarity index 72% rename from platform/persistence/queue/test/PersistenceFailureConstantsSpec.js rename to example/exampleTags/plugin.js index 7a68bf152e..b78ad89eb1 100644 --- a/platform/persistence/queue/test/PersistenceFailureConstantsSpec.js +++ b/example/exampleTags/plugin.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -19,16 +19,15 @@ * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ - -define( - ["../src/PersistenceFailureConstants"], - function (PersistenceFailureConstants) { - - describe("Persistence failure constants", function () { - it("defines an overwrite key", function () { - expect(PersistenceFailureConstants.OVERWRITE_KEY) - .toEqual(jasmine.any(String)); - }); +import availableTags from './tags.json'; +/** + * @returns {function} The plugin install function + */ +export default function exampleTagsPlugin() { + return function install(openmct) { + Object.keys(availableTags.tags).forEach(tagKey => { + const tagDefinition = availableTags.tags[tagKey]; + openmct.annotation.defineTag(tagKey, tagDefinition); }); - } -); + }; +} diff --git a/example/exampleTags/tags.json b/example/exampleTags/tags.json new file mode 100644 index 0000000000..31a1b823a9 --- /dev/null +++ b/example/exampleTags/tags.json @@ -0,0 +1,19 @@ +{ + "tags": { + "46a62ad1-bb86-4f88-9a17-2a029e12669d": { + "label": "Science", + "backgroundColor": "#cc0000", + "foregroundColor": "#ffffff" + }, + "65f150ef-73b7-409a-b2e8-258cbd8b7323": { + "label": "Driving", + "backgroundColor": "#ffad32", + "foregroundColor": "#333333" + }, + "f156b038-c605-46db-88a6-67cf2489a371": { + "label": "Drilling", + "backgroundColor": "#b0ac4e", + "foregroundColor": "#FFFFFF" + } + } +} diff --git a/example/exampleUser/ExampleUserProvider.js b/example/exampleUser/ExampleUserProvider.js new file mode 100644 index 0000000000..8fdd029234 --- /dev/null +++ b/example/exampleUser/ExampleUserProvider.js @@ -0,0 +1,207 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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'; +import { v4 as uuid } from 'uuid'; +import createExampleUser from './exampleUserCreator'; + +const STATUSES = [{ + key: "NO_STATUS", + label: "Not set", + iconClass: "icon-question-mark", + iconClassPoll: "icon-status-poll-question-mark" +}, { + key: "GO", + label: "Go", + iconClass: "icon-check", + iconClassPoll: "icon-status-poll-question-mark", + statusClass: "s-status-ok", + statusBgColor: "#33cc33", + statusFgColor: "#000" +}, { + key: "MAYBE", + label: "Maybe", + iconClass: "icon-alert-triangle", + iconClassPoll: "icon-status-poll-question-mark", + statusClass: "s-status-warning", + statusBgColor: "#ffb66c", + statusFgColor: "#000" +}, { + key: "NO_GO", + label: "No go", + iconClass: "icon-circle-slash", + iconClassPoll: "icon-status-poll-question-mark", + statusClass: "s-status-error", + statusBgColor: "#9900cc", + statusFgColor: "#fff" +}]; +/** + * @implements {StatusUserProvider} + */ +export default class ExampleUserProvider extends EventEmitter { + constructor(openmct, {defaultStatusRole} = {defaultStatusRole: undefined}) { + super(); + + this.openmct = openmct; + this.user = undefined; + this.loggedIn = false; + this.autoLoginUser = undefined; + this.status = STATUSES[1]; + this.pollQuestion = undefined; + this.defaultStatusRole = defaultStatusRole; + + this.ExampleUser = createExampleUser(this.openmct.user.User); + this.loginPromise = undefined; + } + + isLoggedIn() { + return this.loggedIn; + } + + autoLogin(username) { + this.autoLoginUser = username; + } + + getCurrentUser() { + if (!this.loginPromise) { + this.loginPromise = this._login().then(() => this.user); + } + + return this.loginPromise; + } + + canProvideStatusForRole() { + return Promise.resolve(true); + } + + canSetPollQuestion() { + return Promise.resolve(true); + } + + hasRole(roleId) { + if (!this.loggedIn) { + Promise.resolve(undefined); + } + + return Promise.resolve(this.user.getRoles().includes(roleId)); + } + + getStatusRoleForCurrentUser() { + return Promise.resolve(this.defaultStatusRole); + } + + getAllStatusRoles() { + return Promise.resolve([this.defaultStatusRole]); + } + + getStatusForRole(role) { + return Promise.resolve(this.status); + } + + async getDefaultStatusForRole(role) { + const allRoles = await this.getPossibleStatuses(); + + return allRoles?.[0]; + } + + setStatusForRole(role, status) { + this.status = status; + this.emit('statusChange', { + role, + status + }); + + return true; + } + + getPollQuestion() { + return Promise.resolve({ + question: 'Set "GO" if your position is ready for a boarding action on the Klingon cruiser', + timestamp: Date.now() + }); + } + + setPollQuestion(pollQuestion) { + this.pollQuestion = { + question: pollQuestion, + timestamp: Date.now() + }; + this.emit("pollQuestionChange", this.pollQuestion); + + return true; + } + + getPossibleStatuses() { + return Promise.resolve(STATUSES); + } + + _login() { + const id = uuid(); + + // for testing purposes, this will skip the form, this wouldn't be used in + // a normal authentication process + if (this.autoLoginUser) { + this.user = new this.ExampleUser(id, this.autoLoginUser, ['example-role']); + this.loggedIn = true; + + return Promise.resolve(); + } + + const formStructure = { + title: "Login", + sections: [ + { + rows: [ + { + key: "username", + control: "textfield", + name: "Username", + pattern: "\\S+", + required: true, + cssClass: "l-input-lg", + value: '' + } + ] + } + ], + buttons: { + submit: { + label: 'Login' + } + } + }; + + return this.openmct.forms.showForm(formStructure).then( + (info) => { + this.user = new this.ExampleUser(id, info.username, ['example-role']); + this.loggedIn = true; + }, + () => { // user canceled, setting a default username + this.user = new this.ExampleUser(id, 'Pat', ['example-role']); + this.loggedIn = true; + } + ); + } +} +/** + * @typedef {import('@/api/user/StatusUserProvider').default} StatusUserProvider + */ diff --git a/example/mobile/res/sass/mobile-example.scss b/example/exampleUser/exampleUserCreator.js similarity index 73% rename from example/mobile/res/sass/mobile-example.scss rename to example/exampleUser/exampleUserCreator.js index 5c10aca813..8ffc88e3ff 100644 --- a/example/mobile/res/sass/mobile-example.scss +++ b/example/exampleUser/exampleUserCreator.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -19,13 +19,18 @@ * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ -@import "../../../../platform/commonUI/general/res/sass/constants"; -@import "../../../../platform/commonUI/general/res/sass/mobile/constants"; -@import "../../../../platform/commonUI/general/res/sass/mobile/mixins"; -@include phoneandtablet { - // Show the Create button - .create-button-holder { - display: block !important; - } +export default function createExampleUser(UserClass) { + return class ExampleUser extends UserClass { + constructor(id, name, roles) { + super(id, name); + + this.roles = roles; + this.getRoles = this.getRoles.bind(this); + } + + getRoles() { + return this.roles; + } + }; } diff --git a/platform/commonUI/general/src/SplashScreenManager.js b/example/exampleUser/plugin.js similarity index 67% rename from platform/commonUI/general/src/SplashScreenManager.js rename to example/exampleUser/plugin.js index cd24aec066..af533f098b 100644 --- a/platform/commonUI/general/src/SplashScreenManager.js +++ b/example/exampleUser/plugin.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -20,25 +20,21 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -define([ +import ExampleUserProvider from './ExampleUserProvider'; -], function ( +export default function ExampleUserPlugin({autoLoginUser, defaultStatusRole} = { + autoLoginUser: 'guest', + defaultStatusRole: 'test-role' +}) { + return function install(openmct) { + const userProvider = new ExampleUserProvider(openmct, { + defaultStatusRole + }); -) { - - function SplashScreenManager($document) { - var splash; - $document = $document[0]; - splash = $document.querySelectorAll('.l-splash-holder')[0]; - if (!splash) { - return; + if (autoLoginUser !== undefined) { + userProvider.autoLogin(autoLoginUser); } - splash.className += ' fadeout'; - splash.addEventListener('transitionend', function () { - splash.parentNode.removeChild(splash); - }); - } - - return SplashScreenManager; -}); + openmct.user.setProvider(userProvider); + }; +} diff --git a/platform/identity/test/IdentityProviderSpec.js b/example/exampleUser/pluginSpec.js similarity index 60% rename from platform/identity/test/IdentityProviderSpec.js rename to example/exampleUser/pluginSpec.js index 03da7c766a..02719d99d5 100644 --- a/platform/identity/test/IdentityProviderSpec.js +++ b/example/exampleUser/pluginSpec.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -20,30 +20,31 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -define( - [ - '../src/IdentityProvider' - ], - function (IdentityProvider) { +import { + createOpenMct, + resetApplicationState +} from '../../src/utils/testing'; +import ExampleUserProvider from './ExampleUserProvider'; - describe("IdentityProvider", function () { - var mockQ, provider; +describe("The Example User Plugin", () => { + let openmct; - beforeEach(function () { - mockQ = jasmine.createSpyObj('$q', ['when']); - mockQ.when.and.callFake(function (v) { - return Promise.resolve(v); - }); + beforeEach(() => { + openmct = createOpenMct(); + }); - provider = new IdentityProvider(mockQ); - }); + afterEach(() => { + return resetApplicationState(openmct); + }); - it("provides an undefined user", function () { - return provider.getUser().then(function (user) { - expect(user).toBe(undefined); - }); - }); + it('is not installed by default', () => { + expect(openmct.user.hasProvider()).toBeFalse(); + }); + it('can be installed', () => { + openmct.user.on('providerAdded', (provider) => { + expect(provider).toBeInstanceOf(ExampleUserProvider); }); - } -); + openmct.install(openmct.plugins.example.ExampleUser()); + }); +}); diff --git a/example/export/ExportTelemetryAsCSVAction.js b/example/export/ExportTelemetryAsCSVAction.js deleted file mode 100644 index fdc162dd95..0000000000 --- a/example/export/ExportTelemetryAsCSVAction.js +++ /dev/null @@ -1,89 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([], function () { - 'use strict'; - - /** - * An example of using the `exportService`; queries for telemetry - * and provides the results as a CSV file. - * @param {platform/exporters.ExportService} exportService the - * service which will handle the CSV export - * @param {ActionContext} context the action's context - * @constructor - * @memberof example/export - * @implements {Action} - */ - function ExportTelemetryAsCSVAction(exportService, context) { - this.exportService = exportService; - this.context = context; - } - - ExportTelemetryAsCSVAction.prototype.perform = function () { - var context = this.context, - domainObject = context.domainObject, - telemetry = domainObject.getCapability("telemetry"), - metadata = telemetry.getMetadata(), - domains = metadata.domains, - ranges = metadata.ranges, - exportService = this.exportService; - - function getName(domainOrRange) { - return domainOrRange.name; - } - - telemetry.requestData({}).then(function (series) { - var headers = domains.map(getName).concat(ranges.map(getName)), - rows = [], - row, - i; - - function copyDomainsToRow(telemetryRow, index) { - domains.forEach(function (domain) { - telemetryRow[domain.name] = series.getDomainValue(index, domain.key); - }); - } - - function copyRangesToRow(telemetryRow, index) { - ranges.forEach(function (range) { - telemetryRow[range.name] = series.getRangeValue(index, range.key); - }); - } - - for (i = 0; i < series.getPointCount(); i += 1) { - row = {}; - copyDomainsToRow(row, i); - copyRangesToRow(row, i); - rows.push(row); - } - - exportService.exportCSV(rows, { headers: headers }); - }); - }; - - ExportTelemetryAsCSVAction.appliesTo = function (context) { - return context.domainObject - && context.domainObject.hasCapability("telemetry"); - }; - - return ExportTelemetryAsCSVAction; -}); diff --git a/example/faultManagment/exampleFaultSource.js b/example/faultManagment/exampleFaultSource.js new file mode 100644 index 0000000000..338f0903b5 --- /dev/null +++ b/example/faultManagment/exampleFaultSource.js @@ -0,0 +1,83 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +export default function () { + return function install(openmct) { + openmct.install(openmct.plugins.FaultManagement()); + + openmct.faults.addProvider({ + request(domainObject, options) { + const faults = JSON.parse(localStorage.getItem('faults')); + + return Promise.resolve(faults.alarms); + }, + subscribe(domainObject, callback) { + const faultsData = JSON.parse(localStorage.getItem('faults')).alarms; + + function getRandomIndex(start, end) { + return Math.floor(start + (Math.random() * (end - start + 1))); + } + + let id = setInterval(() => { + const index = getRandomIndex(0, faultsData.length - 1); + const randomFaultData = faultsData[index]; + const randomFault = randomFaultData.fault; + randomFault.currentValueInfo.value = Math.random(); + callback({ + fault: randomFault, + type: 'alarms' + }); + }, 300); + + return () => { + clearInterval(id); + }; + }, + supportsRequest(domainObject) { + const faults = localStorage.getItem('faults'); + + return faults && domainObject.type === 'faultManagement'; + }, + supportsSubscribe(domainObject) { + const faults = localStorage.getItem('faults'); + + return faults && domainObject.type === 'faultManagement'; + }, + acknowledgeFault(fault, { comment = '' }) { + console.log('acknowledgeFault', fault); + console.log('comment', comment); + + return Promise.resolve({ + success: true + }); + }, + shelveFault(fault, shelveData) { + console.log('shelveFault', fault); + console.log('shelveData', shelveData); + + return Promise.resolve({ + success: true + }); + } + }); + }; +} diff --git a/src/adapter/policies/AdaptedViewPolicy.js b/example/faultManagment/pluginSpec.js similarity index 63% rename from src/adapter/policies/AdaptedViewPolicy.js rename to example/faultManagment/pluginSpec.js index 4701d2a8dd..b7a0fa6801 100644 --- a/src/adapter/policies/AdaptedViewPolicy.js +++ b/example/faultManagment/pluginSpec.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -20,23 +20,28 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -define([], function () { - function AdaptedViewPolicy(openmct) { - this.openmct = openmct; - } +import { + createOpenMct, + resetApplicationState +} from '../../src/utils/testing'; - AdaptedViewPolicy.prototype.allow = function ( - view, - legacyObject - ) { - if (Object.prototype.hasOwnProperty.call(view, 'provider')) { - const domainObject = legacyObject.useCapability('adapter'); +describe("The Example Fault Source Plugin", () => { + let openmct; - return view.provider.canView(domainObject, this.openmct.router.path); - } + beforeEach(() => { + openmct = createOpenMct(); + }); - return true; - }; + afterEach(() => { + return resetApplicationState(openmct); + }); - return AdaptedViewPolicy; + it('is not installed by default', () => { + expect(openmct.faults.provider).toBeUndefined(); + }); + + it('can be installed', () => { + openmct.install(openmct.plugins.example.ExampleFaultSource()); + expect(openmct.faults.provider).not.toBeUndefined(); + }); }); diff --git a/example/forms/bundle.js b/example/forms/bundle.js deleted file mode 100644 index 638b4d45c6..0000000000 --- a/example/forms/bundle.js +++ /dev/null @@ -1,53 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/ExampleFormController" -], function ( - ExampleFormController -) { - "use strict"; - - return { - name: "example/forms", - definition: { - "name": "Declarative Forms example", - "sources": "src", - "extensions": { - "controllers": [ - { - "key": "ExampleFormController", - "implementation": ExampleFormController, - "depends": [ - "$scope" - ] - } - ], - "routes": [ - { - "templateUrl": "templates/exampleForm.html" - } - ] - } - } - }; -}); diff --git a/example/forms/res/templates/exampleForm.html b/example/forms/res/templates/exampleForm.html deleted file mode 100644 index ec582b9e54..0000000000 --- a/example/forms/res/templates/exampleForm.html +++ /dev/null @@ -1,42 +0,0 @@ - -
    - - - - -
      -
    • Dirty: {{aForm.$dirty}}
    • -
    • Valid: {{aForm.$valid}}
    • -
    - -
    -        
    -    
    -
    diff --git a/example/forms/src/ExampleFormController.js b/example/forms/src/ExampleFormController.js deleted file mode 100644 index cea41b861d..0000000000 --- a/example/forms/src/ExampleFormController.js +++ /dev/null @@ -1,205 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - "use strict"; - - function ExampleFormController($scope) { - $scope.state = { - - }; - - $scope.toolbar = { - name: "An example toolbar.", - sections: [ - { - description: "First section", - items: [ - { - name: "X", - description: "X coordinate", - control: "textfield", - pattern: "^\\d+$", - disabled: true, - size: 2, - key: "x" - }, - { - name: "Y", - description: "Y coordinate", - control: "textfield", - pattern: "^\\d+$", - size: 2, - key: "y" - }, - { - name: "W", - description: "Cell width", - control: "textfield", - pattern: "^\\d+$", - size: 2, - key: "w" - }, - { - name: "H", - description: "Cell height", - control: "textfield", - pattern: "^\\d+$", - size: 2, - key: "h" - } - - ] - }, - { - description: "Second section", - items: [ - { - control: "button", - csslass: "icon-save", - click: function () { - console.log("Save"); - } - }, - { - control: "button", - csslass: "icon-x", - description: "Button B", - click: function () { - console.log("Cancel"); - } - }, - { - control: "button", - csslass: "icon-trash", - description: "Button C", - disabled: true, - click: function () { - console.log("Delete"); - } - } - ] - }, - { - items: [ - { - control: "color", - key: "color" - } - ] - } - ] - }; - - $scope.form = { - name: "An example form.", - sections: [ - { - name: "First section", - rows: [ - { - name: "Check me", - control: "checkbox", - key: "checkMe" - }, - { - name: "Enter your name", - required: true, - control: "textfield", - key: "yourName" - }, - { - name: "Enter a number", - control: "textfield", - pattern: "^\\d+$", - key: "aNumber" - } - ] - }, - { - name: "Second section", - rows: [ - { - name: "Pick a date", - required: true, - description: "Enter date in form YYYY-DDD", - control: "datetime", - key: "aDate" - }, - { - name: "Choose something", - control: "select", - options: [ - { - name: "Hats", - value: "hats" - }, - { - name: "Bats", - value: "bats" - }, - { - name: "Cats", - value: "cats" - }, - { - name: "Mats", - value: "mats" - } - ], - key: "aChoice" - }, - { - name: "Choose something", - control: "select", - required: true, - options: [ - { - name: "Hats", - value: "hats" - }, - { - name: "Bats", - value: "bats" - }, - { - name: "Cats", - value: "cats" - }, - { - name: "Mats", - value: "mats" - } - ], - key: "aRequiredChoice" - } - ] - } - ] - }; - } - - return ExampleFormController; - } -); diff --git a/example/generator/GeneratorMetadataProvider.js b/example/generator/GeneratorMetadataProvider.js index 7a8cd9832a..f274d2d53d 100644 --- a/example/generator/GeneratorMetadataProvider.js +++ b/example/generator/GeneratorMetadataProvider.js @@ -29,12 +29,12 @@ define([ } }, { - key: "cos", - name: "Cosine", - unit: "deg", - formatString: '%0.2f', + key: "wavelengths", + name: "Wavelength", + unit: "nm", + format: 'string[]', hints: { - domain: 3 + range: 4 } }, // Need to enable "LocalTimeSystem" plugin to make use of this @@ -64,6 +64,14 @@ define([ hints: { range: 2 } + }, + { + key: "intensities", + name: "Intensities", + format: 'number[]', + hints: { + range: 3 + } } ] }, diff --git a/example/generator/GeneratorProvider.js b/example/generator/GeneratorProvider.js index 14bd9031aa..ee0bf98f91 100644 --- a/example/generator/GeneratorProvider.js +++ b/example/generator/GeneratorProvider.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -32,11 +32,12 @@ define([ offset: 0, dataRateInHz: 1, randomness: 0, - phase: 0 + phase: 0, + loadDelay: 0 }; - function GeneratorProvider() { - this.workerInterface = new WorkerInterface(); + function GeneratorProvider(openmct) { + this.workerInterface = new WorkerInterface(openmct); } GeneratorProvider.prototype.canProvideTelemetry = function (domainObject) { @@ -53,8 +54,9 @@ define([ 'period', 'offset', 'dataRateInHz', + 'randomness', 'phase', - 'randomness' + 'loadDelay' ]; request = request || {}; diff --git a/example/generator/SinewaveLimitProvider.js b/example/generator/SinewaveLimitProvider.js index e714f3f0d4..53259f70a4 100644 --- a/example/generator/SinewaveLimitProvider.js +++ b/example/generator/SinewaveLimitProvider.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * diff --git a/example/generator/StateGeneratorProvider.js b/example/generator/StateGeneratorProvider.js index 3733d03a3b..98aa5940dd 100644 --- a/example/generator/StateGeneratorProvider.js +++ b/example/generator/StateGeneratorProvider.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * diff --git a/example/generator/WorkerInterface.js b/example/generator/WorkerInterface.js index 372bd3bbc7..1573800fff 100644 --- a/example/generator/WorkerInterface.js +++ b/example/generator/WorkerInterface.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -21,20 +21,13 @@ *****************************************************************************/ define([ - 'raw-loader!./generatorWorker.js', 'uuid' ], function ( - workerText, - uuid + { v4: uuid } ) { - - var workerBlob = new Blob( - [workerText], - {type: 'application/javascript'} - ); - var workerUrl = URL.createObjectURL(workerBlob); - - function WorkerInterface() { + function WorkerInterface(openmct) { + // eslint-disable-next-line no-undef + const workerUrl = `${openmct.getAssetPath()}${__OPENMCT_ROOT_RELATIVE__}generatorWorker.js`; this.worker = new Worker(workerUrl); this.worker.onmessage = this.onMessage.bind(this); this.callbacks = {}; diff --git a/example/generator/generatorWorker.js b/example/generator/generatorWorker.js index 563dbda690..bc9083da3a 100644 --- a/example/generator/generatorWorker.js +++ b/example/generator/generatorWorker.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -77,7 +77,8 @@ 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), + wavelengths: wavelengths(), + intensities: intensities(), cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness) } }); @@ -115,6 +116,7 @@ var dataRateInHz = request.dataRateInHz; var phase = request.phase; var randomness = request.randomness; + var loadDelay = Math.max(request.loadDelay, 0); var step = 1000 / dataRateInHz; var nextStep = start - (start % step) + step; @@ -126,11 +128,20 @@ utc: nextStep, yesterday: nextStep - 60 * 60 * 24 * 1000, sin: sin(nextStep, period, amplitude, offset, phase, randomness), - wavelength: wavelength(start, nextStep), + wavelengths: wavelengths(), + intensities: intensities(), cos: cos(nextStep, period, amplitude, offset, phase, randomness) }); } + if (loadDelay === 0) { + postOnRequest(message, request, data); + } else { + setTimeout(() => postOnRequest(message, request, data), loadDelay); + } + } + + function postOnRequest(message, request, data) { self.postMessage({ id: message.id, data: request.spectra ? { @@ -154,8 +165,28 @@ * Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset; } - function wavelength(start, nextStep) { - return (nextStep - start) / 10; + function wavelengths() { + let values = []; + while (values.length < 5) { + const randomValue = Math.random() * 100; + if (!values.includes(randomValue)) { + values.push(String(randomValue)); + } + } + + return values; + } + + function intensities() { + let values = []; + while (values.length < 5) { + const randomValue = Math.random() * 10; + if (!values.includes(randomValue)) { + values.push(String(randomValue)); + } + } + + return values; } function sendError(error, message) { diff --git a/example/generator/plugin.js b/example/generator/plugin.js index a7a027e32e..8f820e16fd 100644 --- a/example/generator/plugin.js +++ b/example/generator/plugin.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -81,7 +81,7 @@ define([ { name: "Amplitude", control: "numberfield", - cssClass: "l-input-sm l-numeric", + cssClass: "l-numeric", key: "amplitude", required: true, property: [ @@ -92,7 +92,7 @@ define([ { name: "Offset", control: "numberfield", - cssClass: "l-input-sm l-numeric", + cssClass: "l-numeric", key: "offset", required: true, property: [ @@ -132,6 +132,17 @@ define([ "telemetry", "randomness" ] + }, + { + name: "Loading Delay (ms)", + control: "numberfield", + cssClass: "l-input-sm l-numeric", + key: "loadDelay", + required: true, + property: [ + "telemetry", + "loadDelay" + ] } ], initialize: function (object) { @@ -141,12 +152,13 @@ define([ offset: 0, dataRateInHz: 1, phase: 0, - randomness: 0 + randomness: 0, + loadDelay: 0 }; } }); - openmct.telemetry.addProvider(new GeneratorProvider()); + openmct.telemetry.addProvider(new GeneratorProvider(openmct)); openmct.telemetry.addProvider(new GeneratorMetadataProvider()); openmct.telemetry.addProvider(new SinewaveLimitProvider()); }; diff --git a/example/identity/src/ExampleIdentityService.js b/example/identity/src/ExampleIdentityService.js deleted file mode 100644 index 3efaa8be1f..0000000000 --- a/example/identity/src/ExampleIdentityService.js +++ /dev/null @@ -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. - *****************************************************************************/ - -define( - function () { - "use strict"; - - var DEFAULT_IDENTITY = { - key: "user", - name: "Example User" - }, - DIALOG_STRUCTURE = { - name: "Identify Yourself", - sections: [{ - rows: [ - { - name: "User ID", - control: "textfield", - key: "key", - required: true - }, - { - name: "Human name", - control: "textfield", - key: "name", - required: true - } - ] - }] - }; - - /** - * Example implementation of an identity service. This prompts the - * user to enter a name and user ID; in a more realistic - * implementation, this would be read from a server, possibly - * prompting for a user name and password (or similar) as - * appropriate. - * - * @implements {IdentityService} - * @memberof platform/identity - */ - function ExampleIdentityProvider(dialogService, $q) { - this.dialogService = dialogService; - this.$q = $q; - - this.returnUser = this.returnUser.bind(this); - this.returnUndefined = this.returnUndefined.bind(this); - } - - ExampleIdentityProvider.prototype.getUser = function () { - if (this.user) { - return this.$q.when(this.user); - } else { - return this.dialogService.getUserInput(DIALOG_STRUCTURE, DEFAULT_IDENTITY) - .then(this.returnUser, this.returnUndefined); - } - }; - - /** - * @private - */ - ExampleIdentityProvider.prototype.returnUser = function (user) { - return this.user = user; - }; - - /** - * @private - */ - ExampleIdentityProvider.prototype.returnUndefined = function () { - return undefined; - }; - - return ExampleIdentityProvider; - } -); diff --git a/example/imagery/plugin.js b/example/imagery/plugin.js index 9198ad3133..2f323356dd 100644 --- a/example/imagery/plugin.js +++ b/example/imagery/plugin.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -59,7 +59,8 @@ export default function () { object.configuration = { imageLocation: '', imageLoadDelayInMilliSeconds: DEFAULT_IMAGE_LOAD_DELAY_IN_MILISECONDS, - imageSamples: [] + imageSamples: [], + layers: [] }; object.telemetry = { @@ -90,7 +91,21 @@ export default function () { format: 'image', hints: { image: 1 - } + }, + layers: [ + { + source: 'dist/imagery/example-imagery-layer-16x9.png', + name: '16:9' + }, + { + source: 'dist/imagery/example-imagery-layer-safe.png', + name: 'Safe' + }, + { + source: 'dist/imagery/example-imagery-layer-scale.png', + name: 'Scale' + } + ] }, { name: 'Image Download Name', @@ -153,7 +168,7 @@ function getImageUrlListFromConfig(configuration) { } function getImageLoadDelay(domainObject) { - const imageLoadDelay = domainObject.configuration.imageLoadDelayInMilliSeconds; + const imageLoadDelay = Math.trunc(Number(domainObject.configuration.imageLoadDelayInMilliSeconds)); if (!imageLoadDelay) { openmctInstance.objects.mutate(domainObject, 'configuration.imageLoadDelayInMilliSeconds', DEFAULT_IMAGE_LOAD_DELAY_IN_MILISECONDS); @@ -175,7 +190,9 @@ function getRealtimeProvider() { subscribe: (domainObject, callback) => { const delay = getImageLoadDelay(domainObject); const interval = setInterval(() => { - callback(pointForTimestamp(Date.now(), domainObject.name, getImageSamples(domainObject.configuration), delay)); + const imageSamples = getImageSamples(domainObject.configuration); + const datum = pointForTimestamp(Date.now(), domainObject.name, imageSamples, delay); + callback(datum); }, delay); return () => { @@ -214,8 +231,9 @@ function getLadProvider() { }, request: (domainObject, options) => { const delay = getImageLoadDelay(domainObject); + const datum = pointForTimestamp(Date.now(), domainObject.name, getImageSamples(domainObject.configuration), delay); - return Promise.resolve([pointForTimestamp(Date.now(), domainObject.name, delay)]); + return Promise.resolve([datum]); } }; } diff --git a/example/msl/README.md b/example/msl/README.md deleted file mode 100644 index 0df2e1666b..0000000000 --- a/example/msl/README.md +++ /dev/null @@ -1,20 +0,0 @@ -To use this bundle, add the following paths to /main.js - -'./platform/features/conductor/bundle', -'./example/msl/bundle', - -An example plugin that integrates with public data from the Curiosity rover. -The data shown used by this plugin is published by the Centro de -Astrobiología (CSIC-INTA) at http://cab.inta-csic.es/rems/ - -Fetching data from this source requires a cross-origin request which will -fail on most modern browsers due to restrictions on such requests. As such, -it is proxied through a local proxy defined in app.js. In order to use this -example you will need to run app.js locally. - -This example shows integration with an historical telemetry source, as -opposed to a real-time data source that is streaming back current information -about the state of a system. This example is atypical of a historical data -source in that it fetches all data in one request. The server infrastructure -of an historical telemetry source should ideally allow queries bounded by -time and other data attributes. - diff --git a/example/msl/bundle.js b/example/msl/bundle.js deleted file mode 100644 index 80755d8d95..0000000000 --- a/example/msl/bundle.js +++ /dev/null @@ -1,115 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/RemsTelemetryServerAdapter", - "./src/RemsTelemetryModelProvider", - "./src/RemsTelemetryProvider" -], function ( - RemsTelemetryServerAdapter, - RemsTelemetryModelProvider, - RemsTelemetryProvider -) { - "use strict"; - - return { - name: "example/msl", - definition: { - "name": "Mars Science Laboratory Data Adapter", - "extensions": { - "types": [ - { - "name": "Mars Science Laboratory", - "key": "msl.curiosity", - "cssClass": "icon-object" - }, - { - "name": "Instrument", - "key": "msl.instrument", - "cssClass": "icon-object", - "model": {"composition": []} - }, - { - "name": "Measurement", - "key": "msl.measurement", - "cssClass": "icon-telemetry", - "model": {"telemetry": {}}, - "telemetry": { - "source": "rems.source", - "domains": [ - { - "name": "Time", - "key": "utc", - "format": "utc" - } - ] - } - } - ], - "constants": [ - { - "key": "REMS_WS_URL", - "value": "/proxyUrl?url=http://cab.inta-csic.es/rems/wp-content/plugins/marsweather-widget/api.php" - } - ], - "roots": [ - { - "id": "msl:curiosity" - } - ], - "models": [ - { - "id": "msl:curiosity", - "priority": "preferred", - "model": { - "type": "msl.curiosity", - "name": "Mars Science Laboratory", - "composition": ["msl_tlm:rems"] - } - } - ], - "services": [ - { - "key": "rems.adapter", - "implementation": RemsTelemetryServerAdapter, - "depends": ["$http", "$log", "REMS_WS_URL"] - } - ], - "components": [ - { - "provides": "modelService", - "type": "provider", - "implementation": RemsTelemetryModelProvider, - "depends": ["rems.adapter"] - }, - { - "provides": "telemetryService", - "type": "provider", - "implementation": RemsTelemetryProvider, - "depends": ["rems.adapter", "$q"] - } - ] - } - } - }; -}); - diff --git a/example/msl/data/rems.json b/example/msl/data/rems.json deleted file mode 100644 index 085e01a022..0000000000 --- a/example/msl/data/rems.json +++ /dev/null @@ -1 +0,0 @@ -{"descriptions":{"disclaimer_en":"\t \n\t\t\tThe information contained into this file is provided by Centro de Astrobiologia\n\t\t\t(CAB) and is intended for outreach purposes only. Any other use is discouraged.\n\t\t\tCAB will take no responsibility for any result or publication made base on the \n\t\t\tcontent of this file. To access REMS scientific data, visit PDS at http:\/\/pds.nasa.gov.\n\n\t\t\tThe environmental magnitudes given into this file are obtained from the values\n\t\t\tread by the Rover Environmental Monitoring Station (REMS) on board the Mars Science\n\t\t\tLaboratory (MSL) rover on Mars. This file provides the environmental magnitudes at REMS\n\t\t\tlocation, so MSL rover influences those magnitudes (rover position,\n\t\t\trover temperature, rover orientation, rover shade, dust depositions on the rover, etc.)\n\n\t\t\tREMS does not take measurements continuously and it takes measurements at different\n\t\t\ttimes from one day to another. This fact has influence on the variation of the values\n\t\t\tgiven in this file from one day to another .\n\n\t\t\tFor different reasons (instrument maintenance, instrument calibration, instrument degradation, etc.), \n\t\t\tsome or all of the magnitudes in this file may not be available.\n\t\t","disclaimer_es":"\t\t\n\t\t\tLa informaci\u00f3n contenida en este fichero es facilitada por el Centro de Astrobiolog\u00eda\n\t\t\t(CAB) \u00fanicamente para fines divulgativos. Cualquier otro uso queda desaconsejado.\n\t\t\tCAB no se har\u00e1 responsable de ning\u00fan resultado o publicaci\u00f3n basados en el contenido \n\t\t\tde este fichero. Para acceder a los datos cient\u00edficos de REMS, visite el PDS en\n\t\t\thttp:\/\/pds.nasa.gov.\n\n\t\t\tLas magnitudes ambientales dadas es este fichero se obtienen de los valores le\u00eddos\n\t\t\tpor la Estaci\u00f3n de Monitoreo Ambiental del Rover (REMS, por sus siglas en ingl\u00e9s)\n\t\t\tabordo del Laboratorio Cient\u00edfico de Marte (MSL, por sus siglas en ingl\u00e9s). Este\n\t\t\tfichero da las magnitudes ambientales medidas por REMS, por lo que estas magnitudes \n\t\t\test\u00e1n influenciadas por el rover MSL (posici\u00f3n del rover, temperatura del\n\t\t\trover, orientaci\u00f3n del rover, sombras arrojadas por el rover, deposici\u00f3n de polvo sobre\n\t\t\tel rover, etc.)\n\n\t\t\tREMS no est\u00e1 tomando medidas continuamente y, adem\u00e1s, toma medidas a diferentes horas \n\t\t\tde un d\u00eda para otro. Estos hechos tienen influencia en la variaci\u00f3n de los valores\n\t\t\tdados en este fichero para d\u00edas sucesivos.\n\n\t\t\tPor diferentes razones (mantenimiento del instrumento, calibraci\u00f3n del instrumento, degradaci\u00f3n\n\t\t\tdel instrumento, etc.) puede que alguna o todas las magnitudes de este fichero no est\u00e9n disponibles.\n\t\t","sol_desc_en":"\n\t\t\tThe term sol is used to refer to the duration of a day on Mars. A sol is about 24 hours and 40 minutes. For Curiosity,\n\t\t\tsol 0 corresponds with its landing day on Mars.\n\t\t","sol_desc_es":"\n\t\t\tEl t\u00e9rmino sol hace referencia a la duraci\u00f3n de un d\u00eda en Marte. Un sol dura aproximadamente 24 horas y 40 minutos.\n\t\t\tPara Curiosity, el sol 0 corresponde al d\u00eda de su aterrizaje en Marte.\n\t\t","terrestrial_date_desc_en":"\n\t\t\tOne day on Earth is about 24 hours, while a sol (Martian day) is about 24 hours and 40 minutes.\n\t\t\tSo one single sol starts during one Earth day and finishes during the next Earth day. This \n\t\t\tis the Earth day on current sol at midday.\n\t\t","terrestrial_date_desc_es":"\n\t\t\tUn d\u00eda en la Tierra dura aproximadamente 24 horas, mientras que un sol (d\u00eda marciano) dura aproximadamente\n\t\t\t24 horas y 40 minutos. As\u00ed que un sol empieza durante un d\u00eda terrestre, pero termina durante el siguiente.\n\t\t\tEl d\u00eda terrestre dado es el que se corresponde con el mediod\u00eda del sol actual.\n\t\t","temp_desc_en":"\n\t\t\t\tMars is farther from the Sun than Earth, it makes that Mars is colder than our planet.\n\t\t\t\tMoreover, Martian\u2019s atmosphere, which is extremely tenuous, does not retain the heat; \n\t\t\t\thence the difference between day and night's temperatures is more pronounced than in our planet.\n\t\t\t","temp_desc_es":"\n\t\t\t\tMarte est\u00e1 m\u00e1s lejos del Sol que la Tierra, por lo que Marte es m\u00e1s frio que nuestro planeta.\n\t\t\t\tAdem\u00e1s, lo atm\u00f3sfera marciana, que es muy tenue, no retiene bien el calor, \n\t\t\t\tpor lo que las diferencias de temperatura entre el d\u00eda y la noche son m\u00e1s pronunciadas que\n\t\t\t\ten nuestro planeta.\n\t\t\t","pressure_desc_en":"\n\t\t\t\tPressure is a measure of the total mass in a column of air above us. Because\n\t\t\t\tMartian\u2019s atmosphere is extremely tenuous, pressure on Mars' surface is about\n\t\t\t\t160 times smaller than pressure on Earth. Average pressure on Martian surface \n\t\t\t\tis about 700 Pascals (100000 Pascals on Earth) Curiosity is into Gale crater,\n\t\t\t\twhich is about 5 kilometers (3 miles) depth. For this reason, pressure measured\n\t\t\t\tby REMS is usually higher than average pressure on the entire planet.\n\t\t\t","pressure_desc_es":"\n\t\t\t\tLa presi\u00f3n mide la masa total en una columna de aire sobre nosotros. Debido a que\n\t\t\t\tla atm\u00f3sfera marciana es muy tenue, la presi\u00f3n en la superficie de Marte es unas\n\t\t\t\t160 veces m\u00e1s baja que en la Tierra. La presi\u00f3n media en la superficie de Marte \n\t\t\t\tes de unos 700 Pascales (en la Tierra es de 100000) El rover Curiosity est\u00e1 en el \n\t\t\t\tinterior de un cr\u00e1ter, de unos 5 kil\u00f3metros (3 millas) de profundidad. Por esta\n\t\t\t\traz\u00f3n, la presi\u00f3n medida por REMS normalmente ser\u00e1 mayor que la media en el planeta.\n\t\t\t","abs_humidity_desc_en":"\n\t\t\t\tMartian's atmosphere contains water vapor, and humidity measures the amount \n\t\t\t\tof water vapor present in the air. REMS records the relative humidity, \n\t\t\t\twhich is a measure of the amount of water vapor in the air compared \n\t\t\t\twith the amount of water vapor the air can hold at the temperature it happens\n\t\t\t\tto be when you measure it. Water on Mars is also present as water ice, \n\t\t\t\tfor instance, at Mars poles. Until today, however, proof of liquid water \n\t\t\t\tin present-day Mars remains elusive.\n\t\t\t","abs_humidity_desc_es":"\n\t\t\t\tLa atm\u00f3sfera marciana contiene vapor de agua y la humedad mide \n\t\t\t\tla cantidad de vapor de agua presente en el aire. REMS registra la humedad\n\t\t\t\trelativa, que es una medida de la cantidad de vapor de agua que \n\t\t\t\thay en el aire en comparaci\u00f3n con la m\u00e1xima cantidad de vapor de agua\n\t\t\t\tque puede contener a la misma temperatura. El agua en Marte est\u00e1 presente \n\t\t\t\ttambi\u00e9n en forma de hielo, por ejemplo en los polos. Hasta la fecha, sin embargo, \n\t\t\t\tno se han encontrado pruebas de la existencia de agua l\u00edquida en el presente marciano.\n\t\t\t","wind_desc_en":"\n\t\t\t\tNASA Viking landers and NASA Pathfinder rover showed that the average wind speed\n\t\t\t\ton their location was pretty weak: 1 to 4 meters\/second (4 to 15 kilometers\/hour - \n\t\t\t\t2.5 to 9 miles\/hour). However, during a dust storm, winds can reach 30 meters\/second \n\t\t\t\t(110 kilometers\/hour - 68 miles\/hour) or more.\n\t\t\t","wind_desc_es":"\n\t\t\t\tLas Vikings landers y el rover Pathfinder de la NASA mostraron que la velocidad media\n\t\t\t\tdel viento en su localizaci\u00f3n era bastante baja: de 1 a 4 metros\/segundo (de 4 a \n\t\t\t\t15 kil\u00f3metros por hora - de 2.5 a 9 millas\/hora) Sin embargo, durante una tormenta\n\t\t\t\tde polvo, la velocidad del viento en Marte puede alcanzar los 30 metros\/segundo\n\t\t\t\t(110 kil\u00f3metros por hora - 68 millas\/hora) o m\u00e1s.\n\t\t\t","gts_temp_desc_en":"\n\t\t\t\tMartian's atmosphere is extremely tenuous, about 160 times thinner than Earth's,\n\t\t\t\tso heat from the Sun can easily escape. It makes that there are big differences\n\t\t\t\tbetween ground temperature and air temperature. Imagine you were on the Martian equator \n\t\t\t\tat noon, you would feel like summer at your feet, but winter in your head.\n\t\t\t","gts_temp_desc_es":"\n\t\t\t\tLa atm\u00f3sfera marciana es muy tenue, unas 160 veces m\u00e1s delgada que la de la Tierra,\n\t\t\t\tlo que hace que el calor proveniente del Sol pueda escapar con facilidad. Esto hace\n\t\t\t\tque haya grandes diferencias entre la temperatura del suelo y la del aire. Imagina\n\t\t\t\tpor un momento que est\u00e1s en el ecuador marciano al mediod\u00eda, sentir\u00edas\n\t\t\t\ten tus pies una temperatura veraniega, sin embargo ser\u00eda invierno para tu cabeza.\n\t\t\t","local_uv_irradiance_index_desc_en":"\n\t\t\t\tLocal ultraviolet (UV) irradiance index is an indicator of the intensity of the ultraviolet\n\t\t\t\tradiation from the Sun at Curiosity location. UV radiation is a damaging agent for life. \n\t\t\t\tOn Earth, ozone layer prevents damaging ultraviolet light from reaching the surface, to \n\t\t\t\tthe benefit of both plants and animals. However, on Mars, due to the absence of ozone in the \n\t\t\t\tatmosphere, ultraviolet radiation reaches the Martian surface.\n\t\t\t","local_uv_irradiance_index_desc_es":"\n\t\t\t\tEl \u00edndice de irradiaci\u00f3n ultravioleta local es un indicador de la intensidad de la radiaci\u00f3n\n\t\t\t\tultravioleta (UV) proveniente del Sol en la ubicaci\u00f3n del rover Curiosity. La radiaci\u00f3n UV es \n\t\t\t\tun agente da\u00f1ino para la vida. En la Tierra, la capa de ozono nos protege de ella. Sin embargo\n\t\t\t\ten Marte, debido a la ausencia de ozono en la atm\u00f3sfera, la radiaci\u00f3n ultravioleta alcanza\n\t\t\t\tla superficie.\n\t\t\t","atmo_opacity_desc_en":"\n\t\t\t\tWeather on Mars is more extreme than Earth's. Mars is cooler and with\n\t\t\t\tbigger differences between day and night temperatures. Moreover, \n\t\t\t\tdust storms lash its surface. However, Mars' and Earth's climates have\n\t\t\t\timportant similarities, such as the polar ice caps or seasonal changes.\n\t\t\t\tAs on Earth, on Mars we can have sunny, cloudy or windy days, for example.\n\t\t\t","atmo_opacity_desc_es":"\n\t\t\t\tEl clima en Marte es m\u00e1s extremo que en la Tierra. Marte es m\u00e1s frio y\n\t\t\t\tcon mayores diferencias entre las temperaturas del d\u00eda y de la noche.\n\t\t\t\tAdem\u00e1s, fuertes tormentas de polvo azotan su superficie. Sin embargo,\n\t\t\t\texisten importantes similitudes entre el clima marciano y el terrestre,\n\t\t\t\tcomo la existencia de casquetes polares o los cambios de estaciones.\n\t\t\t\tComo ocurre en la Tierra, en Marte podemos tener d\u00edas soleados, nublados\n\t\t\t\to ventosos, por ejemplo.\n\t\t\t","ls_desc_en":"\n\t\t\t\tA Martian year lasts about two Earth's year, which is the time\n\t\t\t\tMars takes to orbit the Sun. Solar longitude is an angle that\n\t\t\t\tgives the position of Mars on its orbit.\t\t\t\t \n\t\t\t","ls_desc_es":"\n\t\t\t\tUn a\u00f1o marciano dura aproximadamente dos a\u00f1os terrestres, el tiempo\n\t\t\t\tque Marte necesita para dar una vuelta completa alrededor del Sol.\n\t\t\t\tLa longitud solar es un \u00e1ngulo que nos da la posici\u00f3n de Marte en su \n\t\t\t\t\u00f3rbita.\n\t\t\t","season_desc_en":"\n\t\t\t\tA Martian year is divided in 12 months, as Earth's. However, Martian months are\n\t\t\t\tfrom 46 to 67 sols (Martian days) long. The longest one is the month 3 (67 sols),\n\t\t\t\tand the shortest one is the month 9 (46 sols) Martian months mark seasonal changes. \n\t\t\t\tIn the southern hemisphere (Curiosity rover location) the autumn starts in month 1; \n\t\t\t\tthe winter in month 4; the spring in month 7; and the summer in month 10.\n\t\t\t","season_desc_es":"\n\t\t\t\tEl a\u00f1o marciano se divide en 12 meses, como en la Tierra. Sin embargo, los meses\n\t\t\t\ten Marte duran entre 46 y 67 soles (d\u00edas marcianos). El m\u00e1s largo es el mes 3\n\t\t\t\t(67 soles) y el m\u00e1s corto es el mes 9 (46 soles). Los meses marcan los\n\t\t\t\tcambios de estaci\u00f3n. En el hemisferio sur (localizaci\u00f3n del rover Curiosity) el\n\t\t\t\toto\u00f1o comienza en el mes 1; el invierno, en el mes 4; la primavera, en el mes 7; y\n\t\t\t\tel verano, en el mes 10. \n\t\t\t\t\t\t\t\n\t\t\t","sunrise_sunset_desc_en":"\n\t\t\t\tThe duration of a Martian day (sol) is about 24 hours and 40 minutes. The duration\n\t\t\t\tof daylight varies along the Martian year, as on Earth.\n\t\t\t","sunrise_sunset_desc_es":"\n\t\t\t\tUn d\u00eda marciano (sol) dura aproximadamente 24 horas y 40 minutos. La duraci\u00f3n\n\t\t\t\tdel d\u00eda y de la noche var\u00eda a lo largo del a\u00f1o, como ocurre en la Tierra.\n\t\t\t"},"soles":[{"id":"1159","terrestrial_date":"2016-01-19","sol":"1228","ls":"97","season":"Month 4","min_temp":"-88","max_temp":"-28","pressure":"832","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:51","sunset":"17:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-93","max_gts_temp":"1"},{"id":"1158","terrestrial_date":"2016-01-18","sol":"1227","ls":"96","season":"Month 4","min_temp":"-88","max_temp":"-26","pressure":"833","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:51","sunset":"17:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-97","max_gts_temp":"2"},{"id":"1157","terrestrial_date":"2016-01-17","sol":"1226","ls":"96","season":"Month 4","min_temp":"-87","max_temp":"-24","pressure":"835","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:51","sunset":"17:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-97","max_gts_temp":"1"},{"id":"1156","terrestrial_date":"2016-01-16","sol":"1225","ls":"95","season":"Month 4","min_temp":"-87","max_temp":"-28","pressure":"836","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:51","sunset":"17:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-98","max_gts_temp":"-1"},{"id":"1154","terrestrial_date":"2016-01-15","sol":"1224","ls":"95","season":"Month 4","min_temp":"-88","max_temp":"-29","pressure":"837","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"17:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-98","max_gts_temp":"1"},{"id":"1155","terrestrial_date":"2016-01-14","sol":"1223","ls":"94","season":"Month 4","min_temp":"-88","max_temp":"-26","pressure":"839","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"17:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-98","max_gts_temp":"0"},{"id":"1153","terrestrial_date":"2016-01-13","sol":"1222","ls":"94","season":"Month 4","min_temp":"-87","max_temp":"-28","pressure":"840","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"17:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-94","max_gts_temp":"0"},{"id":"1152","terrestrial_date":"2016-01-12","sol":"1221","ls":"93","season":"Month 4","min_temp":"-87","max_temp":"-28","pressure":"841","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"17:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-94","max_gts_temp":"-1"},{"id":"1151","terrestrial_date":"2016-01-11","sol":"1220","ls":"93","season":"Month 4","min_temp":"-87","max_temp":"-28","pressure":"842","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"17:35","local_uv_irradiance_index":"Moderate","min_gts_temp":"-94","max_gts_temp":"-3"},{"id":"1150","terrestrial_date":"2016-01-10","sol":"1219","ls":"93","season":"Month 4","min_temp":"-87","max_temp":"-27","pressure":"844","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"17:35","local_uv_irradiance_index":"Moderate","min_gts_temp":"-92","max_gts_temp":"-4"},{"id":"1149","terrestrial_date":"2016-01-09","sol":"1218","ls":"92","season":"Month 4","min_temp":"-86","max_temp":"-32","pressure":"845","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"17:35","local_uv_irradiance_index":"Moderate","min_gts_temp":"-96","max_gts_temp":"-6"},{"id":"1148","terrestrial_date":"2016-01-08","sol":"1217","ls":"92","season":"Month 4","min_temp":"-88","max_temp":"-27","pressure":"846","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"17:35","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-3"},{"id":"1147","terrestrial_date":"2016-01-07","sol":"1216","ls":"91","season":"Month 4","min_temp":"-86","max_temp":"-28","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"17:35","local_uv_irradiance_index":"Moderate","min_gts_temp":"-85","max_gts_temp":"-6"},{"id":"1146","terrestrial_date":"2016-01-06","sol":"1215","ls":"91","season":"Month 4","min_temp":"-85","max_temp":"-27","pressure":"848","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"17:35","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"-3"},{"id":"1144","terrestrial_date":"2016-01-05","sol":"1214","ls":"90","season":"Month 4","min_temp":"-85","max_temp":"-24","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"17:35","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-12"},{"id":"1145","terrestrial_date":"2016-01-04","sol":"1213","ls":"90","season":"Month 4","min_temp":"-84","max_temp":"-27","pressure":"851","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"17:36","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-12"},{"id":"1143","terrestrial_date":"2016-01-03","sol":"1212","ls":"89","season":"Month 3","min_temp":"-86","max_temp":"-28","pressure":"851","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"17:36","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-13"},{"id":"1142","terrestrial_date":"2016-01-02","sol":"1211","ls":"89","season":"Month 3","min_temp":"-83","max_temp":"-22","pressure":"853","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"17:36","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-12"},{"id":"1140","terrestrial_date":"2016-01-01","sol":"1210","ls":"88","season":"Month 3","min_temp":"-85","max_temp":"-23","pressure":"854","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:54","sunset":"17:36","local_uv_irradiance_index":"Moderate","min_gts_temp":"-84","max_gts_temp":"-12"},{"id":"1141","terrestrial_date":"2015-12-31","sol":"1209","ls":"88","season":"Month 3","min_temp":"-85","max_temp":"-24","pressure":"855","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:54","sunset":"17:36","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-12"},{"id":"1139","terrestrial_date":"2015-12-30","sol":"1208","ls":"88","season":"Month 3","min_temp":"-85","max_temp":"-24","pressure":"857","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:54","sunset":"17:36","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-12"},{"id":"1138","terrestrial_date":"2015-12-29","sol":"1207","ls":"87","season":"Month 3","min_temp":"-85","max_temp":"-25","pressure":"858","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:54","sunset":"17:36","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-13"},{"id":"1137","terrestrial_date":"2015-12-28","sol":"1206","ls":"87","season":"Month 3","min_temp":"-85","max_temp":"-24","pressure":"859","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:54","sunset":"17:37","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-13"},{"id":"1134","terrestrial_date":"2015-12-27","sol":"1205","ls":"86","season":"Month 3","min_temp":"-86","max_temp":"-26","pressure":"861","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:54","sunset":"17:37","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-13"},{"id":"1135","terrestrial_date":"2015-12-26","sol":"1204","ls":"86","season":"Month 3","min_temp":"-84","max_temp":"-25","pressure":"862","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"17:37","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-12"},{"id":"1136","terrestrial_date":"2015-12-25","sol":"1203","ls":"85","season":"Month 3","min_temp":"-85","max_temp":"-23","pressure":"863","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"17:37","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-13"},{"id":"1133","terrestrial_date":"2015-12-24","sol":"1202","ls":"85","season":"Month 3","min_temp":"-85","max_temp":"-22","pressure":"864","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"17:37","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-12"},{"id":"1132","terrestrial_date":"2015-12-23","sol":"1201","ls":"84","season":"Month 3","min_temp":"-85","max_temp":"-29","pressure":"866","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"17:37","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-13"},{"id":"1131","terrestrial_date":"2015-12-22","sol":"1200","ls":"84","season":"Month 3","min_temp":"-84","max_temp":"-27","pressure":"866","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"17:38","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-12"},{"id":"1130","terrestrial_date":"2015-12-21","sol":"1199","ls":"84","season":"Month 3","min_temp":"-86","max_temp":"-29","pressure":"868","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"17:38","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-14"},{"id":"1128","terrestrial_date":"2015-12-20","sol":"1198","ls":"83","season":"Month 3","min_temp":"-86","max_temp":"-23","pressure":"868","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"17:38","local_uv_irradiance_index":"Moderate","min_gts_temp":"-89","max_gts_temp":"-13"},{"id":"1127","terrestrial_date":"2015-12-18","sol":"1197","ls":"83","season":"Month 3","min_temp":"-85","max_temp":"-26","pressure":"869","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:56","sunset":"17:38","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-13"},{"id":"1129","terrestrial_date":"2015-12-17","sol":"1196","ls":"82","season":"Month 3","min_temp":"-86","max_temp":"-27","pressure":"871","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:56","sunset":"17:38","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-7"},{"id":"1126","terrestrial_date":"2015-12-16","sol":"1195","ls":"82","season":"Month 3","min_temp":"-86","max_temp":"-32","pressure":"872","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:56","sunset":"17:39","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-6"},{"id":"1125","terrestrial_date":"2015-12-15","sol":"1194","ls":"81","season":"Month 3","min_temp":"-84","max_temp":"-29","pressure":"873","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:56","sunset":"17:39","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-5"},{"id":"1124","terrestrial_date":"2015-12-14","sol":"1193","ls":"81","season":"Month 3","min_temp":"-84","max_temp":"-28","pressure":"875","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:56","sunset":"17:39","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-10"},{"id":"1123","terrestrial_date":"2015-12-13","sol":"1192","ls":"80","season":"Month 3","min_temp":"-86","max_temp":"-24","pressure":"876","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:56","sunset":"17:39","local_uv_irradiance_index":"High","min_gts_temp":"-87","max_gts_temp":"-10"},{"id":"1122","terrestrial_date":"2015-12-12","sol":"1191","ls":"80","season":"Month 3","min_temp":"-87","max_temp":"-28","pressure":"876","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:57","sunset":"17:39","local_uv_irradiance_index":"High","min_gts_temp":"-83","max_gts_temp":"-11"},{"id":"1121","terrestrial_date":"2015-12-11","sol":"1190","ls":"79","season":"Month 3","min_temp":"-87","max_temp":"-29","pressure":"877","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:57","sunset":"17:39","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"-12"},{"id":"1120","terrestrial_date":"2015-12-10","sol":"1189","ls":"79","season":"Month 3","min_temp":"-87","max_temp":"-24","pressure":"879","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:57","sunset":"17:40","local_uv_irradiance_index":"Moderate","min_gts_temp":"-85","max_gts_temp":"-10"},{"id":"1119","terrestrial_date":"2015-12-09","sol":"1188","ls":"79","season":"Month 3","min_temp":"-90","max_temp":"-31","pressure":"881","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:57","sunset":"17:40","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-11"},{"id":"1118","terrestrial_date":"2015-12-08","sol":"1187","ls":"78","season":"Month 3","min_temp":"-84","max_temp":"-29","pressure":"881","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:57","sunset":"17:40","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-8"},{"id":"1117","terrestrial_date":"2015-12-07","sol":"1186","ls":"78","season":"Month 3","min_temp":"-86","max_temp":"-28","pressure":"881","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:57","sunset":"17:40","local_uv_irradiance_index":"High","min_gts_temp":"-87","max_gts_temp":"-8"},{"id":"1116","terrestrial_date":"2015-12-06","sol":"1185","ls":"77","season":"Month 3","min_temp":"-84","max_temp":"-24","pressure":"881","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:58","sunset":"17:40","local_uv_irradiance_index":"High","min_gts_temp":"-94","max_gts_temp":"-3"},{"id":"1115","terrestrial_date":"2015-12-05","sol":"1184","ls":"77","season":"Month 3","min_temp":"-85","max_temp":"-28","pressure":"882","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:58","sunset":"17:41","local_uv_irradiance_index":"Moderate","min_gts_temp":"-94","max_gts_temp":"-4"},{"id":"1113","terrestrial_date":"2015-12-04","sol":"1183","ls":"76","season":"Month 3","min_temp":"-86","max_temp":"-26","pressure":"883","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:58","sunset":"17:41","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-5"},{"id":"1114","terrestrial_date":"2015-12-03","sol":"1182","ls":"76","season":"Month 3","min_temp":"-85","max_temp":"-29","pressure":"884","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:58","sunset":"17:41","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-6"},{"id":"1112","terrestrial_date":"2015-12-02","sol":"1181","ls":"75","season":"Month 3","min_temp":"-86","max_temp":"-24","pressure":"885","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:58","sunset":"17:41","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-7"},{"id":"1111","terrestrial_date":"2015-12-01","sol":"1180","ls":"75","season":"Month 3","min_temp":"-86","max_temp":"-27","pressure":"886","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:58","sunset":"17:41","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-11"},{"id":"1110","terrestrial_date":"2015-11-30","sol":"1179","ls":"75","season":"Month 3","min_temp":"-85","max_temp":"-25","pressure":"887","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:59","sunset":"17:42","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-12"},{"id":"1109","terrestrial_date":"2015-11-29","sol":"1178","ls":"74","season":"Month 3","min_temp":"-86","max_temp":"-26","pressure":"888","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:59","sunset":"17:42","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-13"},{"id":"1108","terrestrial_date":"2015-11-28","sol":"1177","ls":"74","season":"Month 3","min_temp":"-84","max_temp":"-27","pressure":"888","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:59","sunset":"17:42","local_uv_irradiance_index":"Moderate","min_gts_temp":"-89","max_gts_temp":"-12"},{"id":"1107","terrestrial_date":"2015-11-27","sol":"1176","ls":"73","season":"Month 3","min_temp":"-84","max_temp":"-31","pressure":"890","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:59","sunset":"17:42","local_uv_irradiance_index":"Moderate","min_gts_temp":"-89","max_gts_temp":"-12"},{"id":"1106","terrestrial_date":"2015-11-26","sol":"1175","ls":"73","season":"Month 3","min_temp":"-85","max_temp":"-23","pressure":"890","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:59","sunset":"17:43","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-11"},{"id":"1105","terrestrial_date":"2015-11-25","sol":"1174","ls":"72","season":"Month 3","min_temp":"-85","max_temp":"-25","pressure":"891","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:00","sunset":"17:43","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-8"},{"id":"1103","terrestrial_date":"2015-11-24","sol":"1173","ls":"72","season":"Month 3","min_temp":"-87","max_temp":"-31","pressure":"892","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:00","sunset":"17:43","local_uv_irradiance_index":"Moderate","min_gts_temp":"-83","max_gts_temp":"-14"},{"id":"1104","terrestrial_date":"2015-11-23","sol":"1172","ls":"71","season":"Month 3","min_temp":"-87","max_temp":"-26","pressure":"892","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:00","sunset":"17:43","local_uv_irradiance_index":"High","min_gts_temp":"-91","max_gts_temp":"-13"},{"id":"1099","terrestrial_date":"2015-11-22","sol":"1171","ls":"71","season":"Month 3","min_temp":"-87","max_temp":"-29","pressure":"892","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:00","sunset":"17:44","local_uv_irradiance_index":"High","min_gts_temp":"-90","max_gts_temp":"-15"},{"id":"1101","terrestrial_date":"2015-11-21","sol":"1170","ls":"71","season":"Month 3","min_temp":"-85","max_temp":"-31","pressure":"893","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:00","sunset":"17:44","local_uv_irradiance_index":"High","min_gts_temp":"-89","max_gts_temp":"-15"},{"id":"1100","terrestrial_date":"2015-11-20","sol":"1169","ls":"70","season":"Month 3","min_temp":"-89","max_temp":"-32","pressure":"894","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:00","sunset":"17:44","local_uv_irradiance_index":"High","min_gts_temp":"-89","max_gts_temp":"-16"},{"id":"1102","terrestrial_date":"2015-11-19","sol":"1168","ls":"70","season":"Month 3","min_temp":"-84","max_temp":"-29","pressure":"894","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:01","sunset":"17:44","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-10"},{"id":"1098","terrestrial_date":"2015-11-18","sol":"1167","ls":"69","season":"Month 3","min_temp":"-84","max_temp":"-25","pressure":"894","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:01","sunset":"17:45","local_uv_irradiance_index":"Moderate","min_gts_temp":"-85","max_gts_temp":"-13"},{"id":"1097","terrestrial_date":"2015-11-17","sol":"1166","ls":"69","season":"Month 3","min_temp":"-84","max_temp":"-31","pressure":"895","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:01","sunset":"17:45","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"-12"},{"id":"1096","terrestrial_date":"2015-11-16","sol":"1165","ls":"68","season":"Month 3","min_temp":"-84","max_temp":"-35","pressure":"897","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:01","sunset":"17:45","local_uv_irradiance_index":"Moderate","min_gts_temp":"-89","max_gts_temp":"-61"},{"id":"1095","terrestrial_date":"2015-11-15","sol":"1164","ls":"68","season":"Month 3","min_temp":"-86","max_temp":"-35","pressure":"896","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:01","sunset":"17:45","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-14"},{"id":"1094","terrestrial_date":"2015-11-14","sol":"1163","ls":"67","season":"Month 3","min_temp":"-86","max_temp":"-29","pressure":"896","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:02","sunset":"17:46","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-12"},{"id":"1093","terrestrial_date":"2015-11-13","sol":"1162","ls":"67","season":"Month 3","min_temp":"-84","max_temp":"-27","pressure":"897","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:02","sunset":"17:46","local_uv_irradiance_index":"Moderate","min_gts_temp":"-84","max_gts_temp":"-12"},{"id":"1092","terrestrial_date":"2015-11-12","sol":"1161","ls":"66","season":"Month 3","min_temp":"-83","max_temp":"-26","pressure":"898","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:02","sunset":"17:46","local_uv_irradiance_index":"Moderate","min_gts_temp":"-84","max_gts_temp":"-13"},{"id":"1091","terrestrial_date":"2015-11-10","sol":"1160","ls":"66","season":"Month 3","min_temp":"-80","max_temp":"-28","pressure":"899","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:02","sunset":"17:46","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-15"},{"id":"1090","terrestrial_date":"2015-11-09","sol":"1159","ls":"66","season":"Month 3","min_temp":"-82","max_temp":"-28","pressure":"898","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:02","sunset":"17:47","local_uv_irradiance_index":"Moderate","min_gts_temp":"-84","max_gts_temp":"-14"},{"id":"1089","terrestrial_date":"2015-11-08","sol":"1158","ls":"65","season":"Month 3","min_temp":"-82","max_temp":"-28","pressure":"898","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:03","sunset":"17:47","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-12"},{"id":"1087","terrestrial_date":"2015-11-07","sol":"1157","ls":"65","season":"Month 3","min_temp":"-82","max_temp":"-32","pressure":"900","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:03","sunset":"17:47","local_uv_irradiance_index":"Moderate","min_gts_temp":"-84","max_gts_temp":"-14"},{"id":"1085","terrestrial_date":"2015-11-06","sol":"1156","ls":"64","season":"Month 3","min_temp":"-84","max_temp":"-30","pressure":"900","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:03","sunset":"17:47","local_uv_irradiance_index":"Moderate","min_gts_temp":"-85","max_gts_temp":"-13"},{"id":"1084","terrestrial_date":"2015-11-05","sol":"1155","ls":"64","season":"Month 3","min_temp":"-84","max_temp":"-26","pressure":"900","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:03","sunset":"17:48","local_uv_irradiance_index":"Moderate","min_gts_temp":"-83","max_gts_temp":"-13"},{"id":"1088","terrestrial_date":"2015-11-04","sol":"1154","ls":"63","season":"Month 3","min_temp":"-84","max_temp":"-29","pressure":"900","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:04","sunset":"17:48","local_uv_irradiance_index":"Moderate","min_gts_temp":"-83","max_gts_temp":"-13"},{"id":"1083","terrestrial_date":"2015-11-03","sol":"1153","ls":"63","season":"Month 3","min_temp":"-82","max_temp":"-31","pressure":"900","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:04","sunset":"17:48","local_uv_irradiance_index":"Moderate","min_gts_temp":"-83","max_gts_temp":"-16"},{"id":"1086","terrestrial_date":"2015-11-02","sol":"1152","ls":"62","season":"Month 3","min_temp":"-80","max_temp":"-30","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:04","sunset":"17:49","local_uv_irradiance_index":"Moderate","min_gts_temp":"-85","max_gts_temp":"-17"},{"id":"1082","terrestrial_date":"2015-11-01","sol":"1151","ls":"62","season":"Month 3","min_temp":"-82","max_temp":"-31","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:04","sunset":"17:49","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"-17"},{"id":"1081","terrestrial_date":"2015-10-31","sol":"1150","ls":"62","season":"Month 3","min_temp":"-82","max_temp":"-29","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:04","sunset":"17:49","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-17"},{"id":"1079","terrestrial_date":"2015-10-30","sol":"1149","ls":"61","season":"Month 3","min_temp":"-81","max_temp":"-23","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:05","sunset":"17:49","local_uv_irradiance_index":"Moderate","min_gts_temp":"-83","max_gts_temp":"-17"},{"id":"1080","terrestrial_date":"2015-10-29","sol":"1148","ls":"61","season":"Month 3","min_temp":"-80","max_temp":"-28","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:05","sunset":"17:50","local_uv_irradiance_index":"Moderate","min_gts_temp":"-83","max_gts_temp":"-15"},{"id":"1078","terrestrial_date":"2015-10-28","sol":"1147","ls":"60","season":"Month 3","min_temp":"-80","max_temp":"-29","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:05","sunset":"17:50","local_uv_irradiance_index":"Moderate","min_gts_temp":"-85","max_gts_temp":"-17"},{"id":"1077","terrestrial_date":"2015-10-27","sol":"1146","ls":"60","season":"Month 3","min_temp":"-82","max_temp":"-28","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:05","sunset":"17:50","local_uv_irradiance_index":"Moderate","min_gts_temp":"-83","max_gts_temp":"-16"},{"id":"1076","terrestrial_date":"2015-10-26","sol":"1145","ls":"59","season":"Month 2","min_temp":"-82","max_temp":"-29","pressure":"903","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:06","sunset":"17:51","local_uv_irradiance_index":"Moderate","min_gts_temp":"-84","max_gts_temp":"-12"},{"id":"1075","terrestrial_date":"2015-10-25","sol":"1144","ls":"59","season":"Month 2","min_temp":"-82","max_temp":"-28","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:06","sunset":"17:51","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"-10"},{"id":"1074","terrestrial_date":"2015-10-24","sol":"1143","ls":"58","season":"Month 2","min_temp":"-82","max_temp":"-29","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:06","sunset":"17:51","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"-17"},{"id":"1073","terrestrial_date":"2015-10-23","sol":"1142","ls":"58","season":"Month 2","min_temp":"-82","max_temp":"-23","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:06","sunset":"17:52","local_uv_irradiance_index":"Moderate","min_gts_temp":"-79","max_gts_temp":"-14"},{"id":"1072","terrestrial_date":"2015-10-22","sol":"1141","ls":"57","season":"Month 2","min_temp":"-82","max_temp":"-28","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:06","sunset":"17:52","local_uv_irradiance_index":"Moderate","min_gts_temp":"-80","max_gts_temp":"-12"},{"id":"1071","terrestrial_date":"2015-10-21","sol":"1140","ls":"57","season":"Month 2","min_temp":"-81","max_temp":"-26","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:07","sunset":"17:52","local_uv_irradiance_index":"Moderate","min_gts_temp":"-80","max_gts_temp":"-16"},{"id":"1070","terrestrial_date":"2015-10-20","sol":"1139","ls":"57","season":"Month 2","min_temp":"-80","max_temp":"-25","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:07","sunset":"17:53","local_uv_irradiance_index":"Moderate","min_gts_temp":"-80","max_gts_temp":"-14"},{"id":"1067","terrestrial_date":"2015-10-19","sol":"1138","ls":"56","season":"Month 2","min_temp":"-82","max_temp":"-29","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:07","sunset":"17:53","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"-15"},{"id":"1068","terrestrial_date":"2015-10-18","sol":"1137","ls":"56","season":"Month 2","min_temp":"-81","max_temp":"-26","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:07","sunset":"17:53","local_uv_irradiance_index":"Moderate","min_gts_temp":"-79","max_gts_temp":"-14"},{"id":"1066","terrestrial_date":"2015-10-17","sol":"1136","ls":"55","season":"Month 2","min_temp":"-82","max_temp":"-23","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:08","sunset":"17:53","local_uv_irradiance_index":"Moderate","min_gts_temp":"-74","max_gts_temp":"-14"},{"id":"1069","terrestrial_date":"2015-10-16","sol":"1135","ls":"55","season":"Month 2","min_temp":"-81","max_temp":"-27","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:08","sunset":"17:54","local_uv_irradiance_index":"Moderate","min_gts_temp":"-81","max_gts_temp":"-15"},{"id":"1065","terrestrial_date":"2015-10-15","sol":"1134","ls":"54","season":"Month 2","min_temp":"-80","max_temp":"-27","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:08","sunset":"17:54","local_uv_irradiance_index":"Moderate","min_gts_temp":"-81","max_gts_temp":"-14"},{"id":"1064","terrestrial_date":"2015-10-14","sol":"1133","ls":"54","season":"Month 2","min_temp":"-82","max_temp":"-26","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:08","sunset":"17:54","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"-14"},{"id":"1063","terrestrial_date":"2015-10-13","sol":"1132","ls":"53","season":"Month 2","min_temp":"-82","max_temp":"-27","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:09","sunset":"17:55","local_uv_irradiance_index":"Moderate","min_gts_temp":"-77","max_gts_temp":"-14"},{"id":"1062","terrestrial_date":"2015-10-12","sol":"1131","ls":"53","season":"Month 2","min_temp":"-81","max_temp":"-28","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:09","sunset":"17:55","local_uv_irradiance_index":"Moderate","min_gts_temp":"-77","max_gts_temp":"-14"},{"id":"1061","terrestrial_date":"2015-10-11","sol":"1130","ls":"53","season":"Month 2","min_temp":"-81","max_temp":"-25","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:09","sunset":"17:56","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"-13"},{"id":"1060","terrestrial_date":"2015-10-10","sol":"1129","ls":"52","season":"Month 2","min_temp":"-80","max_temp":"-23","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:09","sunset":"17:56","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"-14"},{"id":"1059","terrestrial_date":"2015-10-09","sol":"1128","ls":"52","season":"Month 2","min_temp":"-79","max_temp":"-27","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:10","sunset":"17:56","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"-13"},{"id":"1058","terrestrial_date":"2015-10-08","sol":"1127","ls":"51","season":"Month 2","min_temp":"-80","max_temp":"-27","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:10","sunset":"17:57","local_uv_irradiance_index":"Moderate","min_gts_temp":"-77","max_gts_temp":"-13"},{"id":"1057","terrestrial_date":"2015-10-07","sol":"1126","ls":"51","season":"Month 2","min_temp":"-79","max_temp":"-21","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:10","sunset":"17:57","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"-12"},{"id":"1056","terrestrial_date":"2015-10-06","sol":"1125","ls":"50","season":"Month 2","min_temp":"-79","max_temp":"-22","pressure":"900","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:10","sunset":"17:57","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"-13"},{"id":"1055","terrestrial_date":"2015-10-04","sol":"1124","ls":"50","season":"Month 2","min_temp":"-80","max_temp":"-26","pressure":"900","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:11","sunset":"17:58","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"-12"},{"id":"1054","terrestrial_date":"2015-10-03","sol":"1123","ls":"49","season":"Month 2","min_temp":"-79","max_temp":"-27","pressure":"900","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:11","sunset":"17:58","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"-12"},{"id":"1053","terrestrial_date":"2015-10-02","sol":"1122","ls":"49","season":"Month 2","min_temp":"-79","max_temp":"-26","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:11","sunset":"17:58","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"-13"},{"id":"1052","terrestrial_date":"2015-10-01","sol":"1121","ls":"48","season":"Month 2","min_temp":"-81","max_temp":"-25","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:12","sunset":"17:59","local_uv_irradiance_index":"Moderate","min_gts_temp":"-77","max_gts_temp":"-13"},{"id":"1051","terrestrial_date":"2015-09-30","sol":"1120","ls":"48","season":"Month 2","min_temp":"-80","max_temp":"-24","pressure":"900","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:12","sunset":"17:59","local_uv_irradiance_index":"Moderate","min_gts_temp":"-75","max_gts_temp":"-11"},{"id":"1050","terrestrial_date":"2015-09-29","sol":"1119","ls":"48","season":"Month 2","min_temp":"-81","max_temp":"-22","pressure":"899","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:12","sunset":"17:59","local_uv_irradiance_index":"Moderate","min_gts_temp":"-76","max_gts_temp":"-12"},{"id":"1049","terrestrial_date":"2015-09-28","sol":"1118","ls":"47","season":"Month 2","min_temp":"-80","max_temp":"-25","pressure":"898","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:12","sunset":"18:00","local_uv_irradiance_index":"Moderate","min_gts_temp":"-76","max_gts_temp":"-13"},{"id":"1048","terrestrial_date":"2015-09-27","sol":"1117","ls":"47","season":"Month 2","min_temp":"-79","max_temp":"-25","pressure":"898","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:13","sunset":"18:00","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"-12"},{"id":"1046","terrestrial_date":"2015-09-26","sol":"1116","ls":"46","season":"Month 2","min_temp":"-79","max_temp":"-26","pressure":"898","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:13","sunset":"18:01","local_uv_irradiance_index":"Moderate","min_gts_temp":"-77","max_gts_temp":"-13"},{"id":"1047","terrestrial_date":"2015-09-25","sol":"1115","ls":"46","season":"Month 2","min_temp":"-80","max_temp":"-21","pressure":"897","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:13","sunset":"18:01","local_uv_irradiance_index":"Moderate","min_gts_temp":"-76","max_gts_temp":"-11"},{"id":"1045","terrestrial_date":"2015-09-24","sol":"1114","ls":"45","season":"Month 2","min_temp":"-79","max_temp":"-25","pressure":"896","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:13","sunset":"18:01","local_uv_irradiance_index":"Moderate","min_gts_temp":"-75","max_gts_temp":"-12"},{"id":"1044","terrestrial_date":"2015-09-23","sol":"1113","ls":"45","season":"Month 2","min_temp":"-79","max_temp":"-24","pressure":"895","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:14","sunset":"18:02","local_uv_irradiance_index":"Moderate","min_gts_temp":"-74","max_gts_temp":"-12"},{"id":"1043","terrestrial_date":"2015-09-22","sol":"1112","ls":"44","season":"Month 2","min_temp":"-80","max_temp":"-24","pressure":"895","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:14","sunset":"18:02","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"-10"},{"id":"1042","terrestrial_date":"2015-09-21","sol":"1111","ls":"44","season":"Month 2","min_temp":"-79","max_temp":"-26","pressure":"896","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:14","sunset":"18:02","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"-13"},{"id":"1041","terrestrial_date":"2015-09-20","sol":"1110","ls":"43","season":"Month 2","min_temp":"-81","max_temp":"-25","pressure":"895","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:15","sunset":"18:03","local_uv_irradiance_index":"Moderate","min_gts_temp":"-82","max_gts_temp":"-8"},{"id":"1039","terrestrial_date":"2015-09-19","sol":"1109","ls":"43","season":"Month 2","min_temp":"-82","max_temp":"-26","pressure":"895","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:15","sunset":"18:03","local_uv_irradiance_index":"Moderate","min_gts_temp":"-79","max_gts_temp":"-7"},{"id":"1038","terrestrial_date":"2015-09-18","sol":"1108","ls":"42","season":"Month 2","min_temp":"-78","max_temp":"-25","pressure":"895","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:15","sunset":"18:04","local_uv_irradiance_index":"Moderate","min_gts_temp":"-81","max_gts_temp":"-6"},{"id":"1040","terrestrial_date":"2015-09-17","sol":"1107","ls":"42","season":"Month 2","min_temp":"-79","max_temp":"-24","pressure":"893","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:15","sunset":"18:04","local_uv_irradiance_index":"Moderate","min_gts_temp":"-80","max_gts_temp":"-7"},{"id":"1037","terrestrial_date":"2015-09-16","sol":"1106","ls":"42","season":"Month 2","min_temp":"-82","max_temp":"-22","pressure":"893","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:16","sunset":"18:04","local_uv_irradiance_index":"Moderate","min_gts_temp":"-82","max_gts_temp":"-8"},{"id":"1036","terrestrial_date":"2015-09-15","sol":"1105","ls":"41","season":"Month 2","min_temp":"-78","max_temp":"-25","pressure":"892","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:16","sunset":"18:05","local_uv_irradiance_index":"Moderate","min_gts_temp":"-83","max_gts_temp":"-9"},{"id":"1035","terrestrial_date":"2015-09-14","sol":"1104","ls":"41","season":"Month 2","min_temp":"-79","max_temp":"-25","pressure":"893","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:16","sunset":"18:05","local_uv_irradiance_index":"Moderate","min_gts_temp":"-84","max_gts_temp":"-6"},{"id":"1033","terrestrial_date":"2015-09-13","sol":"1103","ls":"40","season":"Month 2","min_temp":"-81","max_temp":"-26","pressure":"893","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:17","sunset":"18:06","local_uv_irradiance_index":"Moderate","min_gts_temp":"-81","max_gts_temp":"-5"},{"id":"1032","terrestrial_date":"2015-09-12","sol":"1102","ls":"40","season":"Month 2","min_temp":"-84","max_temp":"-25","pressure":"892","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:17","sunset":"18:06","local_uv_irradiance_index":"Moderate","min_gts_temp":"-83","max_gts_temp":"-6"},{"id":"1034","terrestrial_date":"2015-09-11","sol":"1101","ls":"39","season":"Month 2","min_temp":"-80","max_temp":"-23","pressure":"891","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:17","sunset":"18:06","local_uv_irradiance_index":"Moderate","min_gts_temp":"-81","max_gts_temp":"-4"},{"id":"1031","terrestrial_date":"2015-09-10","sol":"1100","ls":"39","season":"Month 2","min_temp":"-79","max_temp":"-22","pressure":"890","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:17","sunset":"18:07","local_uv_irradiance_index":"Moderate","min_gts_temp":"-85","max_gts_temp":"-8"},{"id":"1030","terrestrial_date":"2015-09-09","sol":"1099","ls":"38","season":"Month 2","min_temp":"-78","max_temp":"-24","pressure":"891","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:18","sunset":"18:07","local_uv_irradiance_index":"Moderate","min_gts_temp":"-80","max_gts_temp":"-7"},{"id":"1029","terrestrial_date":"2015-09-08","sol":"1098","ls":"38","season":"Month 2","min_temp":"-77","max_temp":"-26","pressure":"890","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:18","sunset":"18:08","local_uv_irradiance_index":"Moderate","min_gts_temp":"-79","max_gts_temp":"-10"},{"id":"1028","terrestrial_date":"2015-09-07","sol":"1097","ls":"37","season":"Month 2","min_temp":"-78","max_temp":"-27","pressure":"889","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:18","sunset":"18:08","local_uv_irradiance_index":"Moderate","min_gts_temp":"-79","max_gts_temp":"-8"},{"id":"1025","terrestrial_date":"2015-09-06","sol":"1096","ls":"37","season":"Month 2","min_temp":"-79","max_temp":"-24","pressure":"888","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:19","sunset":"18:08","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"-7"},{"id":"1027","terrestrial_date":"2015-09-05","sol":"1095","ls":"36","season":"Month 2","min_temp":"-79","max_temp":"-24","pressure":"888","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:19","sunset":"18:09","local_uv_irradiance_index":"Moderate","min_gts_temp":"-77","max_gts_temp":"-8"},{"id":"1026","terrestrial_date":"2015-09-04","sol":"1094","ls":"36","season":"Month 2","min_temp":"-80","max_temp":"-20","pressure":"888","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:19","sunset":"18:09","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"-7"},{"id":"1024","terrestrial_date":"2015-09-03","sol":"1093","ls":"36","season":"Month 2","min_temp":"-77","max_temp":"-21","pressure":"887","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:20","sunset":"18:10","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"-4"},{"id":"1023","terrestrial_date":"2015-09-02","sol":"1092","ls":"35","season":"Month 2","min_temp":"-79","max_temp":"-23","pressure":"886","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:20","sunset":"18:10","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"-8"},{"id":"1022","terrestrial_date":"2015-09-01","sol":"1091","ls":"35","season":"Month 2","min_temp":"-77","max_temp":"-21","pressure":"886","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:20","sunset":"18:10","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"-7"},{"id":"1021","terrestrial_date":"2015-08-31","sol":"1090","ls":"34","season":"Month 2","min_temp":"-78","max_temp":"-20","pressure":"886","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:20","sunset":"18:11","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"-6"},{"id":"1018","terrestrial_date":"2015-08-30","sol":"1089","ls":"34","season":"Month 2","min_temp":"-76","max_temp":"-19","pressure":"885","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:21","sunset":"18:11","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"-7"},{"id":"1020","terrestrial_date":"2015-08-29","sol":"1088","ls":"33","season":"Month 2","min_temp":"-77","max_temp":"-19","pressure":"885","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:21","sunset":"18:12","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"-7"},{"id":"1019","terrestrial_date":"2015-08-27","sol":"1087","ls":"33","season":"Month 2","min_temp":"-78","max_temp":"-19","pressure":"885","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:21","sunset":"18:12","local_uv_irradiance_index":"High","min_gts_temp":"-83","max_gts_temp":"-3"},{"id":"1016","terrestrial_date":"2015-08-26","sol":"1086","ls":"32","season":"Month 2","min_temp":"-77","max_temp":"-25","pressure":"885","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:22","sunset":"18:13","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"-2"},{"id":"1017","terrestrial_date":"2015-08-25","sol":"1085","ls":"32","season":"Month 2","min_temp":"-79","max_temp":"-23","pressure":"886","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:22","sunset":"18:13","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"-10"},{"id":"1015","terrestrial_date":"2015-08-24","sol":"1084","ls":"31","season":"Month 2","min_temp":"-78","max_temp":"-22","pressure":"885","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:22","sunset":"18:13","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"-9"},{"id":"1014","terrestrial_date":"2015-08-23","sol":"1083","ls":"31","season":"Month 2","min_temp":"-80","max_temp":"-20","pressure":"883","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:23","sunset":"18:14","local_uv_irradiance_index":"Moderate","min_gts_temp":"-80","max_gts_temp":"-7"},{"id":"1012","terrestrial_date":"2015-08-22","sol":"1082","ls":"30","season":"Month 2","min_temp":"-79","max_temp":"-17","pressure":"882","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:23","sunset":"18:14","local_uv_irradiance_index":"Moderate","min_gts_temp":"-81","max_gts_temp":"-7"},{"id":"1011","terrestrial_date":"2015-08-21","sol":"1081","ls":"30","season":"Month 2","min_temp":"-77","max_temp":"-18","pressure":"882","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:23","sunset":"18:15","local_uv_irradiance_index":"Moderate","min_gts_temp":"-82","max_gts_temp":"-7"},{"id":"1013","terrestrial_date":"2015-08-20","sol":"1080","ls":"29","season":"Month 1","min_temp":"-77","max_temp":"-17","pressure":"882","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:23","sunset":"18:15","local_uv_irradiance_index":"Moderate","min_gts_temp":"-84","max_gts_temp":"-3"},{"id":"1010","terrestrial_date":"2015-08-19","sol":"1079","ls":"29","season":"Month 1","min_temp":"-80","max_temp":"-21","pressure":"882","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:24","sunset":"18:16","local_uv_irradiance_index":"Moderate","min_gts_temp":"-83","max_gts_temp":"-3"},{"id":"1009","terrestrial_date":"2015-08-18","sol":"1078","ls":"29","season":"Month 1","min_temp":"-80","max_temp":"-18","pressure":"881","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:24","sunset":"18:16","local_uv_irradiance_index":"Moderate","min_gts_temp":"-82","max_gts_temp":"1"},{"id":"1008","terrestrial_date":"2015-08-17","sol":"1077","ls":"28","season":"Month 1","min_temp":"-79","max_temp":"-16","pressure":"880","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:24","sunset":"18:16","local_uv_irradiance_index":"Moderate","min_gts_temp":"-81","max_gts_temp":"-2"},{"id":"1007","terrestrial_date":"2015-08-16","sol":"1076","ls":"28","season":"Month 1","min_temp":"-81","max_temp":"-14","pressure":"880","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:25","sunset":"18:17","local_uv_irradiance_index":"Moderate","min_gts_temp":"-81","max_gts_temp":"-1"},{"id":"1006","terrestrial_date":"2015-08-15","sol":"1075","ls":"27","season":"Month 1","min_temp":"-77","max_temp":"-13","pressure":"879","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:25","sunset":"18:17","local_uv_irradiance_index":"Moderate","min_gts_temp":"-82","max_gts_temp":"-3"},{"id":"1005","terrestrial_date":"2015-08-14","sol":"1074","ls":"27","season":"Month 1","min_temp":"-77","max_temp":"-14","pressure":"878","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:25","sunset":"18:18","local_uv_irradiance_index":"High","min_gts_temp":"-87","max_gts_temp":"-2"},{"id":"1004","terrestrial_date":"2015-08-13","sol":"1073","ls":"26","season":"Month 1","min_temp":"-82","max_temp":"-15","pressure":"879","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:26","sunset":"18:18","local_uv_irradiance_index":"Moderate","min_gts_temp":"-77","max_gts_temp":"1"},{"id":"1003","terrestrial_date":"2015-08-12","sol":"1072","ls":"26","season":"Month 1","min_temp":"-78","max_temp":"-15","pressure":"879","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:26","sunset":"18:19","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"2"},{"id":"1002","terrestrial_date":"2015-08-11","sol":"1071","ls":"25","season":"Month 1","min_temp":"-78","max_temp":"-16","pressure":"878","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:26","sunset":"18:19","local_uv_irradiance_index":"High","min_gts_temp":"-68","max_gts_temp":"-4"},{"id":"1001","terrestrial_date":"2015-08-10","sol":"1070","ls":"25","season":"Month 1","min_temp":"-80","max_temp":"-14","pressure":"877","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:27","sunset":"18:19","local_uv_irradiance_index":"High","min_gts_temp":"-26","max_gts_temp":"-5"},{"id":"999","terrestrial_date":"2015-08-09","sol":"1069","ls":"24","season":"Month 1","min_temp":"-77","max_temp":"-22","pressure":"876","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:27","sunset":"18:20","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"-4"},{"id":"1000","terrestrial_date":"2015-08-08","sol":"1068","ls":"24","season":"Month 1","min_temp":"-76","max_temp":"-23","pressure":"874","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:27","sunset":"18:20","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"-3"},{"id":"998","terrestrial_date":"2015-08-07","sol":"1067","ls":"23","season":"Month 1","min_temp":"-79","max_temp":"-24","pressure":"875","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:27","sunset":"18:21","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"1"},{"id":"997","terrestrial_date":"2015-08-06","sol":"1066","ls":"23","season":"Month 1","min_temp":"-78","max_temp":"-14","pressure":"875","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:28","sunset":"18:21","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"0"},{"id":"995","terrestrial_date":"2015-08-05","sol":"1065","ls":"22","season":"Month 1","min_temp":"-78","max_temp":"-19","pressure":"873","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:28","sunset":"18:22","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"2"},{"id":"996","terrestrial_date":"2015-08-04","sol":"1064","ls":"22","season":"Month 1","min_temp":"-77","max_temp":"-22","pressure":"871","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:28","sunset":"18:22","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"2"},{"id":"994","terrestrial_date":"2015-08-03","sol":"1063","ls":"21","season":"Month 1","min_temp":"-79","max_temp":"-19","pressure":"871","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:29","sunset":"18:22","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"1"},{"id":"991","terrestrial_date":"2015-08-02","sol":"1062","ls":"21","season":"Month 1","min_temp":"-77","max_temp":"-24","pressure":"871","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:29","sunset":"18:23","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"2"},{"id":"993","terrestrial_date":"2015-08-01","sol":"1061","ls":"20","season":"Month 1","min_temp":"-77","max_temp":"-23","pressure":"871","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:29","sunset":"18:23","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"3"},{"id":"992","terrestrial_date":"2015-07-31","sol":"1060","ls":"20","season":"Month 1","min_temp":"-78","max_temp":"-16","pressure":"870","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:30","sunset":"18:24","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"2"},{"id":"990","terrestrial_date":"2015-07-30","sol":"1059","ls":"19","season":"Month 1","min_temp":"-78","max_temp":"-20","pressure":"870","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:30","sunset":"18:24","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"2"},{"id":"989","terrestrial_date":"2015-07-29","sol":"1058","ls":"19","season":"Month 1","min_temp":"-78","max_temp":"-22","pressure":"868","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:30","sunset":"18:25","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"1"},{"id":"988","terrestrial_date":"2015-07-28","sol":"1057","ls":"18","season":"Month 1","min_temp":"-78","max_temp":"-22","pressure":"867","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:30","sunset":"18:25","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"2"},{"id":"986","terrestrial_date":"2015-07-27","sol":"1056","ls":"18","season":"Month 1","min_temp":"-76","max_temp":"-21","pressure":"868","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:31","sunset":"18:25","local_uv_irradiance_index":"High","min_gts_temp":"-91","max_gts_temp":"4"},{"id":"987","terrestrial_date":"2015-07-26","sol":"1055","ls":"17","season":"Month 1","min_temp":"-77","max_temp":"-20","pressure":"868","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:31","sunset":"18:26","local_uv_irradiance_index":"High","min_gts_temp":"-90","max_gts_temp":"6"},{"id":"985","terrestrial_date":"2015-07-25","sol":"1054","ls":"17","season":"Month 1","min_temp":"-77","max_temp":"-11","pressure":"867","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:31","sunset":"18:26","local_uv_irradiance_index":"High","min_gts_temp":"-89","max_gts_temp":"6"},{"id":"984","terrestrial_date":"2015-07-24","sol":"1053","ls":"17","season":"Month 1","min_temp":"-79","max_temp":"-11","pressure":"865","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:32","sunset":"18:27","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"8"},{"id":"982","terrestrial_date":"2015-07-23","sol":"1052","ls":"16","season":"Month 1","min_temp":"-78","max_temp":"-12","pressure":"865","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:32","sunset":"18:27","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"1"},{"id":"983","terrestrial_date":"2015-07-21","sol":"1051","ls":"16","season":"Month 1","min_temp":"-76","max_temp":"-20","pressure":"864","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:32","sunset":"18:28","local_uv_irradiance_index":"High","min_gts_temp":"-91","max_gts_temp":"4"},{"id":"981","terrestrial_date":"2015-07-20","sol":"1050","ls":"15","season":"Month 1","min_temp":"-76","max_temp":"-22","pressure":"863","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:33","sunset":"18:28","local_uv_irradiance_index":"High","min_gts_temp":"-92","max_gts_temp":"6"},{"id":"980","terrestrial_date":"2015-07-19","sol":"1049","ls":"15","season":"Month 1","min_temp":"-75","max_temp":"-21","pressure":"864","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:33","sunset":"18:28","local_uv_irradiance_index":"High","min_gts_temp":"-83","max_gts_temp":"5"},{"id":"976","terrestrial_date":"2015-07-18","sol":"1048","ls":"14","season":"Month 1","min_temp":"-78","max_temp":"-15","pressure":"864","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:33","sunset":"18:29","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"2"},{"id":"977","terrestrial_date":"2015-07-17","sol":"1047","ls":"14","season":"Month 1","min_temp":"-77","max_temp":"-14","pressure":"863","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:33","sunset":"18:29","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"2"},{"id":"979","terrestrial_date":"2015-07-16","sol":"1046","ls":"13","season":"Month 1","min_temp":"-77","max_temp":"-12","pressure":"862","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:34","sunset":"18:30","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"2"},{"id":"978","terrestrial_date":"2015-07-15","sol":"1045","ls":"13","season":"Month 1","min_temp":"-80","max_temp":"-13","pressure":"861","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:34","sunset":"18:30","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"1"},{"id":"975","terrestrial_date":"2015-07-14","sol":"1044","ls":"12","season":"Month 1","min_temp":"-79","max_temp":"-20","pressure":"860","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:34","sunset":"18:31","local_uv_irradiance_index":"Moderate","min_gts_temp":"-76","max_gts_temp":"-3"},{"id":"974","terrestrial_date":"2015-07-13","sol":"1043","ls":"12","season":"Month 1","min_temp":"-77","max_temp":"-20","pressure":"860","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:35","sunset":"18:31","local_uv_irradiance_index":"Moderate","min_gts_temp":"-76","max_gts_temp":"-3"},{"id":"973","terrestrial_date":"2015-07-12","sol":"1042","ls":"11","season":"Month 1","min_temp":"-76","max_temp":"-20","pressure":"859","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:35","sunset":"18:31","local_uv_irradiance_index":"Moderate","min_gts_temp":"-83","max_gts_temp":"-4"},{"id":"971","terrestrial_date":"2015-07-11","sol":"1041","ls":"11","season":"Month 1","min_temp":"-77","max_temp":"-22","pressure":"859","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:35","sunset":"18:32","local_uv_irradiance_index":"High","min_gts_temp":"-83","max_gts_temp":"0"},{"id":"972","terrestrial_date":"2015-07-10","sol":"1040","ls":"10","season":"Month 1","min_temp":"-77","max_temp":"-21","pressure":"858","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:35","sunset":"18:32","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"0"},{"id":"970","terrestrial_date":"2015-07-09","sol":"1039","ls":"10","season":"Month 1","min_temp":"-77","max_temp":"-21","pressure":"857","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:36","sunset":"18:33","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"-1"},{"id":"969","terrestrial_date":"2015-07-08","sol":"1038","ls":"9","season":"Month 1","min_temp":"-76","max_temp":"-21","pressure":"858","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:36","sunset":"18:33","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"-1"},{"id":"968","terrestrial_date":"2015-07-07","sol":"1037","ls":"9","season":"Month 1","min_temp":"-80","max_temp":"-21","pressure":"856","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:36","sunset":"18:33","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"-1"},{"id":"967","terrestrial_date":"2015-07-06","sol":"1036","ls":"8","season":"Month 1","min_temp":"-76","max_temp":"-17","pressure":"856","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:36","sunset":"18:34","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"4"},{"id":"966","terrestrial_date":"2015-07-05","sol":"1035","ls":"8","season":"Month 1","min_temp":"-77","max_temp":"-12","pressure":"857","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:37","sunset":"18:34","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"6"},{"id":"964","terrestrial_date":"2015-07-04","sol":"1034","ls":"7","season":"Month 1","min_temp":"-76","max_temp":"-14","pressure":"859","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:37","sunset":"18:35","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"-1"},{"id":"965","terrestrial_date":"2015-07-03","sol":"1033","ls":"7","season":"Month 1","min_temp":"-77","max_temp":"-14","pressure":"856","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:37","sunset":"18:35","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"-2"},{"id":"962","terrestrial_date":"2015-07-02","sol":"1032","ls":"6","season":"Month 1","min_temp":"-74","max_temp":"-16","pressure":"854","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:38","sunset":"18:35","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"0"},{"id":"963","terrestrial_date":"2015-07-01","sol":"1031","ls":"6","season":"Month 1","min_temp":"-76","max_temp":"-15","pressure":"854","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:38","sunset":"18:36","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"0"},{"id":"961","terrestrial_date":"2015-06-30","sol":"1030","ls":"5","season":"Month 1","min_temp":"-77","max_temp":"-15","pressure":"855","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:38","sunset":"18:36","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"-1"},{"id":"959","terrestrial_date":"2015-06-29","sol":"1029","ls":"5","season":"Month 1","min_temp":"-75","max_temp":"-14","pressure":"856","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:38","sunset":"18:37","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"-2"},{"id":"960","terrestrial_date":"2015-06-28","sol":"1028","ls":"4","season":"Month 1","min_temp":"-75","max_temp":"-15","pressure":"853","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:39","sunset":"18:37","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"-2"},{"id":"957","terrestrial_date":"2015-06-26","sol":"1026","ls":"3","season":"Month 1","min_temp":"-78","max_temp":"-16","pressure":"851","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:39","sunset":"18:38","local_uv_irradiance_index":"Low","min_gts_temp":"-78","max_gts_temp":"-1"},{"id":"958","terrestrial_date":"2015-06-25","sol":"1025","ls":"3","season":"Month 1","min_temp":"-74","max_temp":"-14","pressure":"851","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:39","sunset":"18:38","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"-1"},{"id":"945","terrestrial_date":"2015-06-24","sol":"1024","ls":"2","season":"Month 1","min_temp":"-76","max_temp":"-15","pressure":"851","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:40","sunset":"18:39","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"-1"},{"id":"943","terrestrial_date":"2015-06-23","sol":"1023","ls":"2","season":"Month 1","min_temp":"-76","max_temp":"-15","pressure":"851","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:40","sunset":"18:39","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"-1"},{"id":"944","terrestrial_date":"2015-06-22","sol":"1022","ls":"1","season":"Month 1","min_temp":"-75","max_temp":"-12","pressure":"852","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:40","sunset":"18:39","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"-2"},{"id":"942","terrestrial_date":"2015-06-21","sol":"1021","ls":"1","season":"Month 1","min_temp":"-76","max_temp":"-14","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:40","sunset":"18:40","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"-1"},{"id":"938","terrestrial_date":"2015-06-20","sol":"1020","ls":"0","season":"Month 1","min_temp":"-63","max_temp":"-12","pressure":"832","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:41","sunset":"18:40","local_uv_irradiance_index":"High","min_gts_temp":"-65","max_gts_temp":"1"},{"id":"954","terrestrial_date":"2015-06-19","sol":"1019","ls":"0","season":"Month 1","min_temp":"-81","max_temp":"-14","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:41","sunset":"18:41","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"-2"},{"id":"956","terrestrial_date":"2015-06-18","sol":"1018","ls":"359","season":"Month 12","min_temp":"-79","max_temp":"-15","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:41","sunset":"18:41","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"-1"},{"id":"955","terrestrial_date":"2015-06-17","sol":"1017","ls":"359","season":"Month 12","min_temp":"-78","max_temp":"-12","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:41","sunset":"18:41","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"-1"},{"id":"952","terrestrial_date":"2015-06-16","sol":"1016","ls":"358","season":"Month 12","min_temp":"-76","max_temp":"-11","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:41","sunset":"18:42","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"0"},{"id":"951","terrestrial_date":"2015-06-14","sol":"1015","ls":"357","season":"Month 12","min_temp":"-75","max_temp":"-13","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:42","sunset":"18:42","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"1"},{"id":"950","terrestrial_date":"2015-06-13","sol":"1014","ls":"357","season":"Month 12","min_temp":"-77","max_temp":"-14","pressure":"846","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:42","sunset":"18:42","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"0"},{"id":"953","terrestrial_date":"2015-06-12","sol":"1013","ls":"356","season":"Month 12","min_temp":"-80","max_temp":"-12","pressure":"846","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:42","sunset":"18:43","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"0"},{"id":"949","terrestrial_date":"2015-06-11","sol":"1012","ls":"356","season":"Month 12","min_temp":"-81","max_temp":"-16","pressure":"846","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:42","sunset":"18:43","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"-1"},{"id":"948","terrestrial_date":"2015-06-10","sol":"1011","ls":"355","season":"Month 12","min_temp":"-78","max_temp":"-12","pressure":"845","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:42","sunset":"18:43","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"0"},{"id":"947","terrestrial_date":"2015-06-09","sol":"1010","ls":"355","season":"Month 12","min_temp":"-74","max_temp":"-11","pressure":"846","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:43","sunset":"18:44","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"-1"},{"id":"946","terrestrial_date":"2015-06-08","sol":"1009","ls":"354","season":"Month 12","min_temp":"-75","max_temp":"-12","pressure":"845","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:43","sunset":"18:44","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"1"},{"id":"940","terrestrial_date":"2015-06-07","sol":"1008","ls":"354","season":"Month 12","min_temp":"-79","max_temp":"-12","pressure":"844","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:43","sunset":"18:44","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"2"},{"id":"937","terrestrial_date":"2015-06-06","sol":"1007","ls":"353","season":"Month 12","min_temp":"-79","max_temp":"-12","pressure":"845","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:43","sunset":"18:45","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"1"},{"id":"941","terrestrial_date":"2015-06-05","sol":"1006","ls":"353","season":"Month 12","min_temp":"-78","max_temp":"-13","pressure":"844","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:43","sunset":"18:45","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"0"},{"id":"939","terrestrial_date":"2015-06-04","sol":"1005","ls":"352","season":"Month 12","min_temp":"-75","max_temp":"-12","pressure":"843","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:45","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"1"},{"id":"935","terrestrial_date":"2015-06-03","sol":"1004","ls":"352","season":"Month 12","min_temp":"-74","max_temp":"-9","pressure":"842","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:46","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"1"},{"id":"934","terrestrial_date":"2015-06-02","sol":"1003","ls":"351","season":"Month 12","min_temp":"-74","max_temp":"-10","pressure":"843","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:46","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"1"},{"id":"936","terrestrial_date":"2015-06-01","sol":"1002","ls":"351","season":"Month 12","min_temp":"-77","max_temp":"-12","pressure":"844","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:46","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"1"},{"id":"933","terrestrial_date":"2015-05-31","sol":"1001","ls":"350","season":"Month 12","min_temp":"-79","max_temp":"-12","pressure":"843","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:47","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"2"},{"id":"932","terrestrial_date":"2015-05-30","sol":"1000","ls":"350","season":"Month 12","min_temp":"-74","max_temp":"-12","pressure":"841","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:47","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"1"},{"id":"931","terrestrial_date":"2015-05-28","sol":"998","ls":"349","season":"Month 12","min_temp":"-73","max_temp":"-8","pressure":"842","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:48","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"1"},{"id":"930","terrestrial_date":"2015-05-27","sol":"997","ls":"348","season":"Month 12","min_temp":"-74","max_temp":"-13","pressure":"843","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:48","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"1"},{"id":"928","terrestrial_date":"2015-05-26","sol":"996","ls":"348","season":"Month 12","min_temp":"-72","max_temp":"-4","pressure":"842","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:48","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"2"},{"id":"929","terrestrial_date":"2015-05-25","sol":"995","ls":"347","season":"Month 12","min_temp":"-74","max_temp":"-4","pressure":"842","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:48","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"5"},{"id":"925","terrestrial_date":"2015-05-24","sol":"994","ls":"346","season":"Month 12","min_temp":"-79","max_temp":"-5","pressure":"842","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:49","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"3"},{"id":"927","terrestrial_date":"2015-05-23","sol":"993","ls":"346","season":"Month 12","min_temp":"-73","max_temp":"-8","pressure":"840","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:49","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"3"},{"id":"924","terrestrial_date":"2015-05-22","sol":"992","ls":"345","season":"Month 12","min_temp":"-73","max_temp":"-6","pressure":"842","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:49","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"4"},{"id":"926","terrestrial_date":"2015-05-21","sol":"991","ls":"345","season":"Month 12","min_temp":"-73","max_temp":"-3","pressure":"840","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:49","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"4"},{"id":"923","terrestrial_date":"2015-05-20","sol":"990","ls":"344","season":"Month 12","min_temp":"-76","max_temp":"-1","pressure":"839","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:50","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"9"},{"id":"921","terrestrial_date":"2015-05-19","sol":"989","ls":"344","season":"Month 12","min_temp":"-74","max_temp":"-10","pressure":"841","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:50","local_uv_irradiance_index":"Moderate","min_gts_temp":"-79","max_gts_temp":"9"},{"id":"922","terrestrial_date":"2015-05-18","sol":"988","ls":"343","season":"Month 12","min_temp":"-75","max_temp":"-10","pressure":"840","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:50","local_uv_irradiance_index":"Moderate","min_gts_temp":"-77","max_gts_temp":"8"},{"id":"920","terrestrial_date":"2015-05-17","sol":"987","ls":"343","season":"Month 12","min_temp":"-74","max_temp":"-7","pressure":"839","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:50","local_uv_irradiance_index":"Moderate","min_gts_temp":"-77","max_gts_temp":"9"},{"id":"919","terrestrial_date":"2015-05-16","sol":"986","ls":"342","season":"Month 12","min_temp":"-74","max_temp":"-1","pressure":"840","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:51","local_uv_irradiance_index":"Moderate","min_gts_temp":"-79","max_gts_temp":"9"},{"id":"918","terrestrial_date":"2015-05-15","sol":"985","ls":"342","season":"Month 12","min_temp":"-78","max_temp":"-5","pressure":"840","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:51","local_uv_irradiance_index":"Moderate","min_gts_temp":"-81","max_gts_temp":"9"},{"id":"915","terrestrial_date":"2015-05-14","sol":"984","ls":"341","season":"Month 12","min_temp":"-74","max_temp":"-6","pressure":"840","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:51","local_uv_irradiance_index":"Moderate","min_gts_temp":"-83","max_gts_temp":"11"},{"id":"916","terrestrial_date":"2015-05-13","sol":"983","ls":"340","season":"Month 12","min_temp":"-76","max_temp":"-6","pressure":"840","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:51","local_uv_irradiance_index":"Moderate","min_gts_temp":"-77","max_gts_temp":"12"},{"id":"917","terrestrial_date":"2015-05-12","sol":"982","ls":"340","season":"Month 12","min_temp":"-74","max_temp":"-4","pressure":"840","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:52","local_uv_irradiance_index":"Moderate","min_gts_temp":"-76","max_gts_temp":"7"},{"id":"914","terrestrial_date":"2015-05-11","sol":"981","ls":"339","season":"Month 12","min_temp":"-73","max_temp":"-3","pressure":"841","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:52","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"8"},{"id":"911","terrestrial_date":"2015-05-10","sol":"980","ls":"339","season":"Month 12","min_temp":"-73","max_temp":"1","pressure":"840","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:52","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"11"},{"id":"912","terrestrial_date":"2015-05-09","sol":"979","ls":"338","season":"Month 12","min_temp":"-79","max_temp":"-4","pressure":"841","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:52","local_uv_irradiance_index":"Moderate","min_gts_temp":"-80","max_gts_temp":"11"},{"id":"913","terrestrial_date":"2015-05-07","sol":"978","ls":"338","season":"Month 12","min_temp":"-73","max_temp":"-2","pressure":"842","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:52","local_uv_irradiance_index":"Moderate","min_gts_temp":"-78","max_gts_temp":"10"},{"id":"910","terrestrial_date":"2015-05-06","sol":"977","ls":"337","season":"Month 12","min_temp":"-75","max_temp":"-4","pressure":"841","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:53","local_uv_irradiance_index":"Moderate","min_gts_temp":"-77","max_gts_temp":"10"},{"id":"909","terrestrial_date":"2015-05-05","sol":"976","ls":"337","season":"Month 12","min_temp":"-75","max_temp":"-6","pressure":"842","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:53","local_uv_irradiance_index":"Moderate","min_gts_temp":"-77","max_gts_temp":"10"},{"id":"908","terrestrial_date":"2015-05-04","sol":"975","ls":"336","season":"Month 12","min_temp":"-73","max_temp":"-3","pressure":"843","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:53","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"14"},{"id":"907","terrestrial_date":"2015-05-03","sol":"974","ls":"336","season":"Month 12","min_temp":"-73","max_temp":"-3","pressure":"842","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:53","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"14"},{"id":"905","terrestrial_date":"2015-05-02","sol":"973","ls":"335","season":"Month 12","min_temp":"-72","max_temp":"-4","pressure":"844","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:53","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"16"},{"id":"904","terrestrial_date":"2015-05-01","sol":"972","ls":"334","season":"Month 12","min_temp":"-73","max_temp":"-3","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:53","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"14"},{"id":"906","terrestrial_date":"2015-04-30","sol":"971","ls":"334","season":"Month 12","min_temp":"-71","max_temp":"-3","pressure":"848","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:54","local_uv_irradiance_index":"Moderate","min_gts_temp":"-76","max_gts_temp":"14"},{"id":"903","terrestrial_date":"2015-04-29","sol":"970","ls":"333","season":"Month 12","min_temp":"-71","max_temp":"-4","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"11"},{"id":"902","terrestrial_date":"2015-04-28","sol":"969","ls":"333","season":"Month 12","min_temp":"-72","max_temp":"-2","pressure":"844","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"11"},{"id":"901","terrestrial_date":"2015-04-27","sol":"968","ls":"332","season":"Month 12","min_temp":"-71","max_temp":"-3","pressure":"848","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"11"},{"id":"899","terrestrial_date":"2015-04-26","sol":"967","ls":"332","season":"Month 12","min_temp":"-74","max_temp":"-2","pressure":"844","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"12"},{"id":"900","terrestrial_date":"2015-04-25","sol":"966","ls":"331","season":"Month 12","min_temp":"-73","max_temp":"-12","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"9"},{"id":"898","terrestrial_date":"2015-04-24","sol":"965","ls":"331","season":"Month 12","min_temp":"-75","max_temp":"-10","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"8"},{"id":"897","terrestrial_date":"2015-04-23","sol":"964","ls":"330","season":"Month 12","min_temp":"-72","max_temp":"-13","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"9"},{"id":"896","terrestrial_date":"2015-04-22","sol":"963","ls":"329","season":"Month 11","min_temp":"-71","max_temp":"-5","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"10"},{"id":"894","terrestrial_date":"2015-04-21","sol":"962","ls":"329","season":"Month 11","min_temp":"-73","max_temp":"-2","pressure":"845","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"11"},{"id":"895","terrestrial_date":"2015-04-20","sol":"961","ls":"328","season":"Month 11","min_temp":"-71","max_temp":"-3","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"11"},{"id":"893","terrestrial_date":"2015-04-19","sol":"960","ls":"328","season":"Month 11","min_temp":"-76","max_temp":"-8","pressure":"846","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-86","max_gts_temp":"15"},{"id":"891","terrestrial_date":"2015-04-18","sol":"959","ls":"327","season":"Month 11","min_temp":"-78","max_temp":"-4","pressure":"846","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"14"},{"id":"892","terrestrial_date":"2015-04-17","sol":"958","ls":"327","season":"Month 11","min_temp":"-74","max_temp":"-9","pressure":"848","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-83","max_gts_temp":"17"},{"id":"890","terrestrial_date":"2015-04-16","sol":"957","ls":"326","season":"Month 11","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"--","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"--","sunrise":"06:46","sunset":"18:55","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"889","terrestrial_date":"2015-04-13","sol":"954","ls":"324","season":"Month 11","min_temp":"-73","max_temp":"-3","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"11"},{"id":"887","terrestrial_date":"2015-04-12","sol":"953","ls":"324","season":"Month 11","min_temp":"-71","max_temp":"-5","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"12"},{"id":"886","terrestrial_date":"2015-04-11","sol":"952","ls":"323","season":"Month 11","min_temp":"-69","max_temp":"-6","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"14"},{"id":"888","terrestrial_date":"2015-04-10","sol":"951","ls":"323","season":"Month 11","min_temp":"-75","max_temp":"-4","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"14"},{"id":"885","terrestrial_date":"2015-04-09","sol":"950","ls":"322","season":"Month 11","min_temp":"-73","max_temp":"0","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"13"},{"id":"884","terrestrial_date":"2015-04-08","sol":"949","ls":"321","season":"Month 11","min_temp":"-72","max_temp":"-4","pressure":"851","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"9"},{"id":"883","terrestrial_date":"2015-04-07","sol":"948","ls":"321","season":"Month 11","min_temp":"-73","max_temp":"-8","pressure":"854","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"6"},{"id":"882","terrestrial_date":"2015-04-06","sol":"947","ls":"320","season":"Month 11","min_temp":"-70","max_temp":"-3","pressure":"853","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"6"},{"id":"879","terrestrial_date":"2015-04-05","sol":"946","ls":"320","season":"Month 11","min_temp":"-77","max_temp":"-2","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"6"},{"id":"881","terrestrial_date":"2015-04-04","sol":"945","ls":"319","season":"Month 11","min_temp":"-72","max_temp":"-1","pressure":"851","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"5"},{"id":"878","terrestrial_date":"2015-04-03","sol":"944","ls":"318","season":"Month 11","min_temp":"-70","max_temp":"0","pressure":"852","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"8"},{"id":"877","terrestrial_date":"2015-04-02","sol":"943","ls":"318","season":"Month 11","min_temp":"-73","max_temp":"-1","pressure":"854","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-69","max_gts_temp":"5"},{"id":"880","terrestrial_date":"2015-03-31","sol":"942","ls":"317","season":"Month 11","min_temp":"-74","max_temp":"-1","pressure":"857","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"6"},{"id":"875","terrestrial_date":"2015-03-30","sol":"941","ls":"317","season":"Month 11","min_temp":"-74","max_temp":"0","pressure":"857","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:43","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"6"},{"id":"876","terrestrial_date":"2015-03-29","sol":"940","ls":"316","season":"Month 11","min_temp":"-73","max_temp":"-2","pressure":"861","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:43","sunset":"18:54","local_uv_irradiance_index":"Very_High","min_gts_temp":"-76","max_gts_temp":"8"},{"id":"873","terrestrial_date":"2015-03-28","sol":"939","ls":"316","season":"Month 11","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"--","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"--","sunrise":"06:43","sunset":"18:54","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"874","terrestrial_date":"2015-03-26","sol":"937","ls":"314","season":"Month 11","min_temp":"-73","max_temp":"0","pressure":"863","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:42","sunset":"18:54","local_uv_irradiance_index":"Very_High","min_gts_temp":"-77","max_gts_temp":"10"},{"id":"871","terrestrial_date":"2015-03-25","sol":"936","ls":"314","season":"Month 11","min_temp":"-72","max_temp":"-3","pressure":"865","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:42","sunset":"18:54","local_uv_irradiance_index":"Very_High","min_gts_temp":"-75","max_gts_temp":"9"},{"id":"872","terrestrial_date":"2015-03-24","sol":"935","ls":"313","season":"Month 11","min_temp":"-75","max_temp":"-2","pressure":"862","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:42","sunset":"18:54","local_uv_irradiance_index":"Very_High","min_gts_temp":"-74","max_gts_temp":"10"},{"id":"870","terrestrial_date":"2015-03-23","sol":"934","ls":"313","season":"Month 11","min_temp":"-73","max_temp":"1","pressure":"858","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:41","sunset":"18:54","local_uv_irradiance_index":"Very_High","min_gts_temp":"-75","max_gts_temp":"9"},{"id":"869","terrestrial_date":"2015-03-22","sol":"933","ls":"312","season":"Month 11","min_temp":"-71","max_temp":"-4","pressure":"863","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:41","sunset":"18:54","local_uv_irradiance_index":"Very_High","min_gts_temp":"-75","max_gts_temp":"11"},{"id":"866","terrestrial_date":"2015-03-21","sol":"932","ls":"311","season":"Month 11","min_temp":"-71","max_temp":"1","pressure":"859","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:41","sunset":"18:53","local_uv_irradiance_index":"Very_High","min_gts_temp":"-75","max_gts_temp":"10"},{"id":"867","terrestrial_date":"2015-03-20","sol":"931","ls":"311","season":"Month 11","min_temp":"-69","max_temp":"-1","pressure":"864","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:41","sunset":"18:53","local_uv_irradiance_index":"Very_High","min_gts_temp":"-76","max_gts_temp":"10"},{"id":"868","terrestrial_date":"2015-03-19","sol":"930","ls":"310","season":"Month 11","min_temp":"-75","max_temp":"-1","pressure":"865","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:40","sunset":"18:53","local_uv_irradiance_index":"Very_High","min_gts_temp":"-78","max_gts_temp":"10"},{"id":"865","terrestrial_date":"2015-03-18","sol":"929","ls":"310","season":"Month 11","min_temp":"-75","max_temp":"0","pressure":"866","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:40","sunset":"18:53","local_uv_irradiance_index":"Very_High","min_gts_temp":"-76","max_gts_temp":"11"},{"id":"864","terrestrial_date":"2015-03-17","sol":"928","ls":"309","season":"Month 11","min_temp":"-72","max_temp":"0","pressure":"862","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:40","sunset":"18:53","local_uv_irradiance_index":"Very_High","min_gts_temp":"-74","max_gts_temp":"9"},{"id":"863","terrestrial_date":"2015-03-16","sol":"927","ls":"308","season":"Month 11","min_temp":"-76","max_temp":"-2","pressure":"862","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:39","sunset":"18:52","local_uv_irradiance_index":"Very_High","min_gts_temp":"-75","max_gts_temp":"11"},{"id":"862","terrestrial_date":"2015-03-15","sol":"926","ls":"308","season":"Month 11","min_temp":"-76","max_temp":"1","pressure":"864","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:39","sunset":"18:52","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"10"},{"id":"860","terrestrial_date":"2015-03-14","sol":"925","ls":"307","season":"Month 11","min_temp":"-71","max_temp":"-2","pressure":"862","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:38","sunset":"18:52","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"9"},{"id":"861","terrestrial_date":"2015-03-13","sol":"924","ls":"307","season":"Month 11","min_temp":"-74","max_temp":"-1","pressure":"867","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:38","sunset":"18:52","local_uv_irradiance_index":"Very_High","min_gts_temp":"-79","max_gts_temp":"10"},{"id":"859","terrestrial_date":"2015-03-12","sol":"923","ls":"306","season":"Month 11","min_temp":"-72","max_temp":"-5","pressure":"867","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:38","sunset":"18:51","local_uv_irradiance_index":"Very_High","min_gts_temp":"-74","max_gts_temp":"14"},{"id":"858","terrestrial_date":"2015-03-11","sol":"922","ls":"305","season":"Month 11","min_temp":"-73","max_temp":"-2","pressure":"870","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:37","sunset":"18:51","local_uv_irradiance_index":"Very_High","min_gts_temp":"-75","max_gts_temp":"9"},{"id":"857","terrestrial_date":"2015-03-10","sol":"921","ls":"305","season":"Month 11","min_temp":"-70","max_temp":"2","pressure":"867","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:37","sunset":"18:51","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"9"},{"id":"856","terrestrial_date":"2015-03-09","sol":"920","ls":"304","season":"Month 11","min_temp":"-72","max_temp":"-2","pressure":"868","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:37","sunset":"18:51","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"9"},{"id":"855","terrestrial_date":"2015-03-08","sol":"919","ls":"304","season":"Month 11","min_temp":"-74","max_temp":"-3","pressure":"870","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:36","sunset":"18:50","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"9"},{"id":"853","terrestrial_date":"2015-03-07","sol":"918","ls":"303","season":"Month 11","min_temp":"-71","max_temp":"0","pressure":"867","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:36","sunset":"18:50","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"9"},{"id":"854","terrestrial_date":"2015-03-06","sol":"917","ls":"302","season":"Month 11","min_temp":"-76","max_temp":"-1","pressure":"871","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:35","sunset":"18:50","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"9"},{"id":"852","terrestrial_date":"2015-03-05","sol":"916","ls":"302","season":"Month 11","min_temp":"-71","max_temp":"-5","pressure":"872","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:35","sunset":"18:49","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"9"},{"id":"851","terrestrial_date":"2015-03-04","sol":"915","ls":"301","season":"Month 11","min_temp":"-71","max_temp":"-4","pressure":"878","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:34","sunset":"18:49","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"9"},{"id":"850","terrestrial_date":"2015-03-03","sol":"914","ls":"300","season":"Month 11","min_temp":"-71","max_temp":"0","pressure":"874","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:34","sunset":"18:49","local_uv_irradiance_index":"High","min_gts_temp":"-60","max_gts_temp":"8"},{"id":"848","terrestrial_date":"2015-03-02","sol":"913","ls":"300","season":"Month 11","min_temp":"-71","max_temp":"-4","pressure":"871","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:33","sunset":"18:48","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"10"},{"id":"849","terrestrial_date":"2015-03-01","sol":"912","ls":"299","season":"Month 10","min_temp":"-75","max_temp":"-4","pressure":"875","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:33","sunset":"18:48","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"8"},{"id":"846","terrestrial_date":"2015-02-28","sol":"911","ls":"299","season":"Month 10","min_temp":"-70","max_temp":"0","pressure":"874","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:33","sunset":"18:48","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"8"},{"id":"847","terrestrial_date":"2015-02-27","sol":"910","ls":"298","season":"Month 10","min_temp":"-73","max_temp":"0","pressure":"878","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:32","sunset":"18:47","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"9"},{"id":"845","terrestrial_date":"2015-02-26","sol":"909","ls":"297","season":"Month 10","min_temp":"-70","max_temp":"-2","pressure":"878","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:32","sunset":"18:47","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"8"},{"id":"844","terrestrial_date":"2015-02-25","sol":"908","ls":"297","season":"Month 10","min_temp":"-71","max_temp":"-3","pressure":"883","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:31","sunset":"18:46","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"8"},{"id":"842","terrestrial_date":"2015-02-24","sol":"907","ls":"296","season":"Month 10","min_temp":"-75","max_temp":"-1","pressure":"879","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:31","sunset":"18:46","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"8"},{"id":"843","terrestrial_date":"2015-02-22","sol":"906","ls":"296","season":"Month 10","min_temp":"-76","max_temp":"-3","pressure":"878","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:30","sunset":"18:46","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"9"},{"id":"840","terrestrial_date":"2015-02-21","sol":"905","ls":"295","season":"Month 10","min_temp":"-71","max_temp":"-2","pressure":"880","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:29","sunset":"18:45","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"8"},{"id":"841","terrestrial_date":"2015-02-20","sol":"904","ls":"294","season":"Month 10","min_temp":"-71","max_temp":"0","pressure":"878","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:29","sunset":"18:45","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"8"},{"id":"839","terrestrial_date":"2015-02-19","sol":"903","ls":"294","season":"Month 10","min_temp":"-71","max_temp":"-2","pressure":"882","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:28","sunset":"18:44","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"6"},{"id":"838","terrestrial_date":"2015-02-18","sol":"902","ls":"293","season":"Month 10","min_temp":"-68","max_temp":"0","pressure":"883","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:28","sunset":"18:44","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"5"},{"id":"837","terrestrial_date":"2015-02-17","sol":"901","ls":"292","season":"Month 10","min_temp":"-71","max_temp":"-2","pressure":"885","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:27","sunset":"18:43","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"11"},{"id":"836","terrestrial_date":"2015-02-16","sol":"900","ls":"292","season":"Month 10","min_temp":"-70","max_temp":"1","pressure":"883","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:27","sunset":"18:43","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"7"},{"id":"835","terrestrial_date":"2015-02-15","sol":"899","ls":"291","season":"Month 10","min_temp":"-75","max_temp":"-1","pressure":"881","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:26","sunset":"18:42","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"7"},{"id":"834","terrestrial_date":"2015-02-14","sol":"898","ls":"291","season":"Month 10","min_temp":"-72","max_temp":"2","pressure":"883","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:26","sunset":"18:42","local_uv_irradiance_index":"Very_High","min_gts_temp":"-75","max_gts_temp":"7"},{"id":"832","terrestrial_date":"2015-02-13","sol":"897","ls":"290","season":"Month 10","min_temp":"-72","max_temp":"5","pressure":"884","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:25","sunset":"18:41","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"7"},{"id":"833","terrestrial_date":"2015-02-12","sol":"896","ls":"289","season":"Month 10","min_temp":"-70","max_temp":"1","pressure":"888","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:24","sunset":"18:41","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"8"},{"id":"831","terrestrial_date":"2015-02-11","sol":"895","ls":"289","season":"Month 10","min_temp":"-72","max_temp":"-4","pressure":"889","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:24","sunset":"18:40","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"5"},{"id":"830","terrestrial_date":"2015-02-10","sol":"894","ls":"288","season":"Month 10","min_temp":"-75","max_temp":"-4","pressure":"891","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:23","sunset":"18:40","local_uv_irradiance_index":"Moderate","min_gts_temp":"-74","max_gts_temp":"4"},{"id":"829","terrestrial_date":"2015-02-09","sol":"893","ls":"287","season":"Month 10","min_temp":"-72","max_temp":"2","pressure":"890","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:23","sunset":"18:39","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"4"},{"id":"828","terrestrial_date":"2015-02-08","sol":"892","ls":"287","season":"Month 10","min_temp":"-71","max_temp":"-3","pressure":"892","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:22","sunset":"18:39","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"5"},{"id":"827","terrestrial_date":"2015-02-07","sol":"891","ls":"286","season":"Month 10","min_temp":"-72","max_temp":"-3","pressure":"892","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:21","sunset":"18:38","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"5"},{"id":"826","terrestrial_date":"2015-02-06","sol":"890","ls":"286","season":"Month 10","min_temp":"-72","max_temp":"-4","pressure":"892","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:21","sunset":"18:38","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"5"},{"id":"825","terrestrial_date":"2015-02-05","sol":"889","ls":"285","season":"Month 10","min_temp":"-70","max_temp":"2","pressure":"895","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:20","sunset":"18:37","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"4"},{"id":"824","terrestrial_date":"2015-02-04","sol":"888","ls":"284","season":"Month 10","min_temp":"-70","max_temp":"-5","pressure":"896","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:20","sunset":"18:37","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"3"},{"id":"822","terrestrial_date":"2015-02-03","sol":"887","ls":"284","season":"Month 10","min_temp":"-73","max_temp":"-4","pressure":"897","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:19","sunset":"18:36","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"5"},{"id":"823","terrestrial_date":"2015-02-02","sol":"886","ls":"283","season":"Month 10","min_temp":"-73","max_temp":"-6","pressure":"894","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:18","sunset":"18:35","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"4"},{"id":"819","terrestrial_date":"2015-02-01","sol":"885","ls":"282","season":"Month 10","min_temp":"-71","max_temp":"-4","pressure":"895","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:18","sunset":"18:35","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"4"},{"id":"821","terrestrial_date":"2015-01-31","sol":"884","ls":"282","season":"Month 10","min_temp":"-71","max_temp":"-5","pressure":"897","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:17","sunset":"18:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-74","max_gts_temp":"5"},{"id":"820","terrestrial_date":"2015-01-30","sol":"883","ls":"281","season":"Month 10","min_temp":"-74","max_temp":"-4","pressure":"897","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:16","sunset":"18:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-74","max_gts_temp":"3"},{"id":"818","terrestrial_date":"2015-01-29","sol":"882","ls":"280","season":"Month 10","min_temp":"-76","max_temp":"-6","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:16","sunset":"18:33","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"3"},{"id":"817","terrestrial_date":"2015-01-28","sol":"881","ls":"280","season":"Month 10","min_temp":"-73","max_temp":"-4","pressure":"899","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:15","sunset":"18:32","local_uv_irradiance_index":"Moderate","min_gts_temp":"-74","max_gts_temp":"4"},{"id":"816","terrestrial_date":"2015-01-27","sol":"880","ls":"279","season":"Month 10","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"--","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"--","sunrise":"06:14","sunset":"18:32","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"815","terrestrial_date":"2015-01-19","sol":"872","ls":"274","season":"Month 10","min_temp":"-72","max_temp":"1","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:09","sunset":"18:27","local_uv_irradiance_index":"Moderate","min_gts_temp":"-72","max_gts_temp":"5"},{"id":"814","terrestrial_date":"2015-01-18","sol":"871","ls":"273","season":"Month 10","min_temp":"-70","max_temp":"-7","pressure":"903","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:08","sunset":"18:26","local_uv_irradiance_index":"Moderate","min_gts_temp":"-60","max_gts_temp":"3"},{"id":"813","terrestrial_date":"2015-01-17","sol":"870","ls":"273","season":"Month 10","min_temp":"-74","max_temp":"-6","pressure":"909","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:07","sunset":"18:25","local_uv_irradiance_index":"Moderate","min_gts_temp":"-75","max_gts_temp":"4"},{"id":"812","terrestrial_date":"2015-01-15","sol":"869","ls":"272","season":"Month 10","min_temp":"-72","max_temp":"-4","pressure":"906","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:07","sunset":"18:25","local_uv_irradiance_index":"Moderate","min_gts_temp":"-72","max_gts_temp":"5"},{"id":"811","terrestrial_date":"2015-01-14","sol":"868","ls":"271","season":"Month 10","min_temp":"-71","max_temp":"-4","pressure":"908","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:06","sunset":"18:24","local_uv_irradiance_index":"Moderate","min_gts_temp":"-71","max_gts_temp":"3"},{"id":"810","terrestrial_date":"2015-01-13","sol":"867","ls":"271","season":"Month 10","min_temp":"-71","max_temp":"-2","pressure":"910","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:05","sunset":"18:23","local_uv_irradiance_index":"Moderate","min_gts_temp":"-72","max_gts_temp":"4"},{"id":"809","terrestrial_date":"2015-01-12","sol":"866","ls":"270","season":"Month 10","min_temp":"-71","max_temp":"-4","pressure":"911","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:05","sunset":"18:22","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"4"},{"id":"808","terrestrial_date":"2015-01-11","sol":"865","ls":"270","season":"Month 10","min_temp":"-70","max_temp":"-5","pressure":"913","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:04","sunset":"18:22","local_uv_irradiance_index":"Moderate","min_gts_temp":"-75","max_gts_temp":"4"},{"id":"806","terrestrial_date":"2015-01-10","sol":"864","ls":"269","season":"Month 9","min_temp":"-70","max_temp":"-1","pressure":"914","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:03","sunset":"18:21","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"3"},{"id":"805","terrestrial_date":"2015-01-09","sol":"863","ls":"268","season":"Month 9","min_temp":"-72","max_temp":"-13","pressure":"916","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:02","sunset":"18:20","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"3"},{"id":"807","terrestrial_date":"2015-01-08","sol":"862","ls":"268","season":"Month 9","min_temp":"-70","max_temp":"-2","pressure":"917","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:02","sunset":"18:20","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"11"},{"id":"804","terrestrial_date":"2015-01-07","sol":"861","ls":"267","season":"Month 9","min_temp":"-70","max_temp":"-7","pressure":"917","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:01","sunset":"18:19","local_uv_irradiance_index":"Moderate","min_gts_temp":"-71","max_gts_temp":"3"},{"id":"803","terrestrial_date":"2015-01-06","sol":"860","ls":"266","season":"Month 9","min_temp":"-69","max_temp":"-8","pressure":"916","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:00","sunset":"18:18","local_uv_irradiance_index":"Moderate","min_gts_temp":"-71","max_gts_temp":"3"},{"id":"802","terrestrial_date":"2015-01-05","sol":"859","ls":"266","season":"Month 9","min_temp":"-72","max_temp":"-8","pressure":"915","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:00","sunset":"18:18","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"3"},{"id":"800","terrestrial_date":"2015-01-04","sol":"858","ls":"265","season":"Month 9","min_temp":"-73","max_temp":"-6","pressure":"913","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:59","sunset":"18:17","local_uv_irradiance_index":"Moderate","min_gts_temp":"-72","max_gts_temp":"4"},{"id":"801","terrestrial_date":"2015-01-03","sol":"857","ls":"264","season":"Month 9","min_temp":"-76","max_temp":"0","pressure":"911","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:58","sunset":"18:16","local_uv_irradiance_index":"Moderate","min_gts_temp":"-72","max_gts_temp":"3"},{"id":"795","terrestrial_date":"2015-01-02","sol":"856","ls":"264","season":"Month 9","min_temp":"-68","max_temp":"-3","pressure":"912","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:58","sunset":"18:15","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"3"},{"id":"799","terrestrial_date":"2015-01-01","sol":"855","ls":"263","season":"Month 9","min_temp":"-69","max_temp":"0","pressure":"912","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:57","sunset":"18:15","local_uv_irradiance_index":"Low","min_gts_temp":"-70","max_gts_temp":"3"},{"id":"798","terrestrial_date":"2014-12-31","sol":"854","ls":"262","season":"Month 9","min_temp":"-66","max_temp":"3","pressure":"910","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:56","sunset":"18:14","local_uv_irradiance_index":"Low","min_gts_temp":"-70","max_gts_temp":"2"},{"id":"796","terrestrial_date":"2014-12-30","sol":"853","ls":"262","season":"Month 9","min_temp":"-67","max_temp":"0","pressure":"913","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"18:13","local_uv_irradiance_index":"Low","min_gts_temp":"-69","max_gts_temp":"2"},{"id":"797","terrestrial_date":"2014-12-29","sol":"852","ls":"261","season":"Month 9","min_temp":"-73","max_temp":"-3","pressure":"916","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"18:12","local_uv_irradiance_index":"Moderate","min_gts_temp":"-72","max_gts_temp":"4"},{"id":"794","terrestrial_date":"2014-12-28","sol":"851","ls":"260","season":"Month 9","min_temp":"-68","max_temp":"-6","pressure":"919","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:54","sunset":"18:12","local_uv_irradiance_index":"Low","min_gts_temp":"-71","max_gts_temp":"2"},{"id":"789","terrestrial_date":"2014-12-27","sol":"850","ls":"260","season":"Month 9","min_temp":"-68","max_temp":"-1","pressure":"918","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"18:11","local_uv_irradiance_index":"Low","min_gts_temp":"-70","max_gts_temp":"2"},{"id":"787","terrestrial_date":"2014-12-26","sol":"849","ls":"259","season":"Month 9","min_temp":"-69","max_temp":"-1","pressure":"918","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"18:10","local_uv_irradiance_index":"Moderate","min_gts_temp":"-72","max_gts_temp":"2"},{"id":"792","terrestrial_date":"2014-12-25","sol":"848","ls":"258","season":"Month 9","min_temp":"-69","max_temp":"-4","pressure":"918","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"18:10","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"3"},{"id":"791","terrestrial_date":"2014-12-24","sol":"847","ls":"258","season":"Month 9","min_temp":"-69","max_temp":"-8","pressure":"918","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:51","sunset":"18:09","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"4"},{"id":"785","terrestrial_date":"2014-12-23","sol":"846","ls":"257","season":"Month 9","min_temp":"-74","max_temp":"-7","pressure":"925","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:51","sunset":"18:08","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"3"},{"id":"786","terrestrial_date":"2014-12-22","sol":"845","ls":"257","season":"Month 9","min_temp":"-73","max_temp":"0","pressure":"920","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:50","sunset":"18:07","local_uv_irradiance_index":"Moderate","min_gts_temp":"-71","max_gts_temp":"3"},{"id":"788","terrestrial_date":"2014-12-21","sol":"844","ls":"256","season":"Month 9","min_temp":"-68","max_temp":"5","pressure":"917","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:49","sunset":"18:07","local_uv_irradiance_index":"Low","min_gts_temp":"-70","max_gts_temp":"3"},{"id":"793","terrestrial_date":"2014-12-20","sol":"843","ls":"255","season":"Month 9","min_temp":"-69","max_temp":"1","pressure":"916","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:49","sunset":"18:06","local_uv_irradiance_index":"Low","min_gts_temp":"-72","max_gts_temp":"4"},{"id":"790","terrestrial_date":"2014-12-19","sol":"842","ls":"255","season":"Month 9","min_temp":"-69","max_temp":"-6","pressure":"914","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:48","sunset":"18:05","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"5"},{"id":"784","terrestrial_date":"2014-12-18","sol":"841","ls":"254","season":"Month 9","min_temp":"-75","max_temp":"-7","pressure":"914","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:47","sunset":"18:04","local_uv_irradiance_index":"Low","min_gts_temp":"-71","max_gts_temp":"5"},{"id":"783","terrestrial_date":"2014-12-17","sol":"840","ls":"253","season":"Month 9","min_temp":"-74","max_temp":"-4","pressure":"913","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:47","sunset":"18:04","local_uv_irradiance_index":"Low","min_gts_temp":"-71","max_gts_temp":"3"},{"id":"781","terrestrial_date":"2014-12-16","sol":"839","ls":"253","season":"Month 9","min_temp":"-70","max_temp":"-1","pressure":"917","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:46","sunset":"18:03","local_uv_irradiance_index":"Low","min_gts_temp":"-70","max_gts_temp":"2"},{"id":"782","terrestrial_date":"2014-12-15","sol":"838","ls":"252","season":"Month 9","min_temp":"-69","max_temp":"-1","pressure":"917","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:45","sunset":"18:02","local_uv_irradiance_index":"Low","min_gts_temp":"-70","max_gts_temp":"4"},{"id":"779","terrestrial_date":"2014-12-14","sol":"837","ls":"251","season":"Month 9","min_temp":"-69","max_temp":"4","pressure":"916","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:45","sunset":"18:02","local_uv_irradiance_index":"Moderate","min_gts_temp":"-71","max_gts_temp":"6"},{"id":"778","terrestrial_date":"2014-12-13","sol":"836","ls":"251","season":"Month 9","min_temp":"-69","max_temp":"-1","pressure":"916","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:44","sunset":"18:01","local_uv_irradiance_index":"Moderate","min_gts_temp":"-72","max_gts_temp":"5"},{"id":"780","terrestrial_date":"2014-12-12","sol":"835","ls":"250","season":"Month 9","min_temp":"-69","max_temp":"-1","pressure":"917","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:44","sunset":"18:00","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"4"},{"id":"777","terrestrial_date":"2014-12-11","sol":"834","ls":"249","season":"Month 9","min_temp":"-68","max_temp":"-3","pressure":"918","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:43","sunset":"18:00","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"4"},{"id":"776","terrestrial_date":"2014-12-09","sol":"833","ls":"249","season":"Month 9","min_temp":"-69","max_temp":"-1","pressure":"923","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:42","sunset":"17:59","local_uv_irradiance_index":"Moderate","min_gts_temp":"-72","max_gts_temp":"4"},{"id":"775","terrestrial_date":"2014-12-08","sol":"832","ls":"248","season":"Month 9","min_temp":"-68","max_temp":"-2","pressure":"924","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:42","sunset":"17:58","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"4"},{"id":"774","terrestrial_date":"2014-12-07","sol":"831","ls":"247","season":"Month 9","min_temp":"-68","max_temp":"-6","pressure":"920","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:41","sunset":"17:57","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"3"},{"id":"772","terrestrial_date":"2014-12-06","sol":"830","ls":"247","season":"Month 9","min_temp":"-68","max_temp":"-1","pressure":"918","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:41","sunset":"17:57","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"3"},{"id":"773","terrestrial_date":"2014-12-05","sol":"829","ls":"246","season":"Month 9","min_temp":"-71","max_temp":"-2","pressure":"914","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:40","sunset":"17:56","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"3"},{"id":"771","terrestrial_date":"2014-12-04","sol":"828","ls":"245","season":"Month 9","min_temp":"-71","max_temp":"-1","pressure":"914","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:39","sunset":"17:55","local_uv_irradiance_index":"Moderate","min_gts_temp":"-69","max_gts_temp":"3"},{"id":"769","terrestrial_date":"2014-12-03","sol":"827","ls":"245","season":"Month 9","min_temp":"-68","max_temp":"0","pressure":"911","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:39","sunset":"17:55","local_uv_irradiance_index":"Moderate","min_gts_temp":"-68","max_gts_temp":"2"},{"id":"770","terrestrial_date":"2014-12-02","sol":"826","ls":"244","season":"Month 9","min_temp":"-68","max_temp":"0","pressure":"914","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:38","sunset":"17:54","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"3"},{"id":"767","terrestrial_date":"2014-12-01","sol":"825","ls":"243","season":"Month 9","min_temp":"-67","max_temp":"1","pressure":"914","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:38","sunset":"17:53","local_uv_irradiance_index":"Moderate","min_gts_temp":"-69","max_gts_temp":"3"},{"id":"768","terrestrial_date":"2014-11-30","sol":"824","ls":"243","season":"Month 9","min_temp":"-67","max_temp":"2","pressure":"914","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:37","sunset":"17:53","local_uv_irradiance_index":"Moderate","min_gts_temp":"-69","max_gts_temp":"2"},{"id":"766","terrestrial_date":"2014-11-29","sol":"823","ls":"242","season":"Month 9","min_temp":"-69","max_temp":"0","pressure":"912","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:37","sunset":"17:52","local_uv_irradiance_index":"Moderate","min_gts_temp":"-71","max_gts_temp":"2"},{"id":"764","terrestrial_date":"2014-11-28","sol":"822","ls":"242","season":"Month 9","min_temp":"-71","max_temp":"1","pressure":"911","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:36","sunset":"17:51","local_uv_irradiance_index":"Moderate","min_gts_temp":"-71","max_gts_temp":"3"},{"id":"762","terrestrial_date":"2014-11-27","sol":"821","ls":"241","season":"Month 9","min_temp":"-68","max_temp":"2","pressure":"910","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:35","sunset":"17:51","local_uv_irradiance_index":"Moderate","min_gts_temp":"-69","max_gts_temp":"1"},{"id":"765","terrestrial_date":"2014-11-26","sol":"820","ls":"240","season":"Month 9","min_temp":"-68","max_temp":"-1","pressure":"910","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:35","sunset":"17:50","local_uv_irradiance_index":"Moderate","min_gts_temp":"-69","max_gts_temp":"2"},{"id":"763","terrestrial_date":"2014-11-25","sol":"819","ls":"240","season":"Month 9","min_temp":"-71","max_temp":"0","pressure":"913","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:34","sunset":"17:50","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"4"},{"id":"760","terrestrial_date":"2014-11-24","sol":"818","ls":"239","season":"Month 8","min_temp":"-68","max_temp":"0","pressure":"914","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:34","sunset":"17:49","local_uv_irradiance_index":"Moderate","min_gts_temp":"-72","max_gts_temp":"4"},{"id":"761","terrestrial_date":"2014-11-23","sol":"817","ls":"238","season":"Month 8","min_temp":"-69","max_temp":"0","pressure":"913","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:33","sunset":"17:48","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"2"},{"id":"758","terrestrial_date":"2014-11-22","sol":"816","ls":"238","season":"Month 8","min_temp":"-74","max_temp":"-7","pressure":"909","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:33","sunset":"17:48","local_uv_irradiance_index":"Moderate","min_gts_temp":"-69","max_gts_temp":"3"},{"id":"759","terrestrial_date":"2014-11-21","sol":"815","ls":"237","season":"Month 8","min_temp":"-69","max_temp":"-8","pressure":"905","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:32","sunset":"17:47","local_uv_irradiance_index":"Moderate","min_gts_temp":"-69","max_gts_temp":"3"},{"id":"757","terrestrial_date":"2014-11-20","sol":"814","ls":"236","season":"Month 8","min_temp":"-68","max_temp":"-3","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:32","sunset":"17:46","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"3"},{"id":"756","terrestrial_date":"2014-11-19","sol":"813","ls":"236","season":"Month 8","min_temp":"-67","max_temp":"2","pressure":"904","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:31","sunset":"17:46","local_uv_irradiance_index":"Moderate","min_gts_temp":"-69","max_gts_temp":"2"},{"id":"755","terrestrial_date":"2014-11-18","sol":"812","ls":"235","season":"Month 8","min_temp":"-75","max_temp":"3","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:31","sunset":"17:45","local_uv_irradiance_index":"Moderate","min_gts_temp":"-72","max_gts_temp":"4"},{"id":"754","terrestrial_date":"2014-11-17","sol":"811","ls":"234","season":"Month 8","min_temp":"-71","max_temp":"-6","pressure":"900","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:30","sunset":"17:45","local_uv_irradiance_index":"Moderate","min_gts_temp":"-71","max_gts_temp":"4"},{"id":"751","terrestrial_date":"2014-11-16","sol":"810","ls":"234","season":"Month 8","min_temp":"-69","max_temp":"3","pressure":"897","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:30","sunset":"17:44","local_uv_irradiance_index":"Moderate","min_gts_temp":"-71","max_gts_temp":"3"},{"id":"753","terrestrial_date":"2014-11-15","sol":"809","ls":"233","season":"Month 8","min_temp":"-70","max_temp":"-2","pressure":"895","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:30","sunset":"17:43","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"4"},{"id":"752","terrestrial_date":"2014-11-14","sol":"808","ls":"232","season":"Month 8","min_temp":"-67","max_temp":"7","pressure":"893","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:29","sunset":"17:43","local_uv_irradiance_index":"Moderate","min_gts_temp":"-71","max_gts_temp":"3"},{"id":"749","terrestrial_date":"2014-11-13","sol":"807","ls":"232","season":"Month 8","min_temp":"-71","max_temp":"1","pressure":"892","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:29","sunset":"17:42","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"11"},{"id":"750","terrestrial_date":"2014-11-12","sol":"806","ls":"231","season":"Month 8","min_temp":"-69","max_temp":"2","pressure":"893","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:28","sunset":"17:42","local_uv_irradiance_index":"Moderate","min_gts_temp":"-75","max_gts_temp":"12"},{"id":"748","terrestrial_date":"2014-11-11","sol":"805","ls":"231","season":"Month 8","min_temp":"-70","max_temp":"1","pressure":"893","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:28","sunset":"17:41","local_uv_irradiance_index":"Moderate","min_gts_temp":"-75","max_gts_temp":"12"},{"id":"747","terrestrial_date":"2014-11-10","sol":"804","ls":"230","season":"Month 8","min_temp":"-73","max_temp":"0","pressure":"891","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:28","sunset":"17:41","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"12"},{"id":"744","terrestrial_date":"2014-11-09","sol":"803","ls":"229","season":"Month 8","min_temp":"-69","max_temp":"0","pressure":"888","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:27","sunset":"17:40","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"13"},{"id":"745","terrestrial_date":"2014-11-08","sol":"802","ls":"229","season":"Month 8","min_temp":"-69","max_temp":"7","pressure":"887","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:27","sunset":"17:40","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"13"},{"id":"746","terrestrial_date":"2014-11-07","sol":"801","ls":"228","season":"Month 8","min_temp":"-70","max_temp":"3","pressure":"884","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:26","sunset":"17:39","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"13"},{"id":"742","terrestrial_date":"2014-11-06","sol":"800","ls":"227","season":"Month 8","min_temp":"-67","max_temp":"-2","pressure":"884","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:26","sunset":"17:39","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"13"},{"id":"743","terrestrial_date":"2014-11-05","sol":"799","ls":"227","season":"Month 8","min_temp":"-70","max_temp":"-4","pressure":"884","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:26","sunset":"17:38","local_uv_irradiance_index":"Moderate","min_gts_temp":"-74","max_gts_temp":"14"},{"id":"741","terrestrial_date":"2014-11-04","sol":"798","ls":"226","season":"Month 8","min_temp":"-67","max_temp":"0","pressure":"883","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:25","sunset":"17:38","local_uv_irradiance_index":"Moderate","min_gts_temp":"-72","max_gts_temp":"10"},{"id":"740","terrestrial_date":"2014-11-03","sol":"797","ls":"225","season":"Month 8","min_temp":"-65","max_temp":"-1","pressure":"879","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:25","sunset":"17:37","local_uv_irradiance_index":"Moderate","min_gts_temp":"-67","max_gts_temp":"11"},{"id":"737","terrestrial_date":"2014-11-01","sol":"796","ls":"225","season":"Month 8","min_temp":"-65","max_temp":"1","pressure":"877","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:25","sunset":"17:37","local_uv_irradiance_index":"Moderate","min_gts_temp":"-67","max_gts_temp":"5"},{"id":"738","terrestrial_date":"2014-10-31","sol":"795","ls":"224","season":"Month 8","min_temp":"-68","max_temp":"1","pressure":"875","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:24","sunset":"17:36","local_uv_irradiance_index":"Moderate","min_gts_temp":"-68","max_gts_temp":"6"},{"id":"739","terrestrial_date":"2014-10-30","sol":"794","ls":"223","season":"Month 8","min_temp":"-68","max_temp":"-5","pressure":"874","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:24","sunset":"17:36","local_uv_irradiance_index":"Moderate","min_gts_temp":"-68","max_gts_temp":"6"},{"id":"736","terrestrial_date":"2014-10-29","sol":"793","ls":"223","season":"Month 8","min_temp":"-71","max_temp":"0","pressure":"877","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:24","sunset":"17:35","local_uv_irradiance_index":"Moderate","min_gts_temp":"-69","max_gts_temp":"4"},{"id":"735","terrestrial_date":"2014-10-28","sol":"792","ls":"222","season":"Month 8","min_temp":"-71","max_temp":"0","pressure":"873","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:23","sunset":"17:35","local_uv_irradiance_index":"Moderate","min_gts_temp":"-69","max_gts_temp":"3"},{"id":"734","terrestrial_date":"2014-10-27","sol":"791","ls":"222","season":"Month 8","min_temp":"-70","max_temp":"0","pressure":"867","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:23","sunset":"17:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"4"},{"id":"733","terrestrial_date":"2014-10-26","sol":"790","ls":"221","season":"Month 8","min_temp":"-68","max_temp":"2","pressure":"864","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:23","sunset":"17:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-69","max_gts_temp":"7"},{"id":"730","terrestrial_date":"2014-10-25","sol":"789","ls":"220","season":"Month 8","min_temp":"-68","max_temp":"-4","pressure":"862","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:33","local_uv_irradiance_index":"Moderate","min_gts_temp":"-69","max_gts_temp":"5"},{"id":"731","terrestrial_date":"2014-10-24","sol":"788","ls":"220","season":"Month 8","min_temp":"-68","max_temp":"-5","pressure":"862","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:33","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"5"},{"id":"732","terrestrial_date":"2014-10-23","sol":"787","ls":"219","season":"Month 8","min_temp":"-73","max_temp":"-7","pressure":"860","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:33","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"5"},{"id":"729","terrestrial_date":"2014-10-22","sol":"786","ls":"218","season":"Month 8","min_temp":"-74","max_temp":"-4","pressure":"861","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:32","local_uv_irradiance_index":"Moderate","min_gts_temp":"-69","max_gts_temp":"5"},{"id":"728","terrestrial_date":"2014-10-21","sol":"785","ls":"218","season":"Month 8","min_temp":"-74","max_temp":"-1","pressure":"858","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:32","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"6"},{"id":"726","terrestrial_date":"2014-10-20","sol":"784","ls":"217","season":"Month 8","min_temp":"-69","max_temp":"-4","pressure":"857","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:31","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"9"},{"id":"727","terrestrial_date":"2014-10-19","sol":"783","ls":"216","season":"Month 8","min_temp":"-69","max_temp":"6","pressure":"853","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:31","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"8"},{"id":"724","terrestrial_date":"2014-10-18","sol":"782","ls":"216","season":"Month 8","min_temp":"-71","max_temp":"9","pressure":"854","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:31","local_uv_irradiance_index":"Moderate","min_gts_temp":"-70","max_gts_temp":"8"},{"id":"725","terrestrial_date":"2014-10-17","sol":"781","ls":"215","season":"Month 8","min_temp":"-74","max_temp":"-5","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:30","local_uv_irradiance_index":"Moderate","min_gts_temp":"-73","max_gts_temp":"9"},{"id":"723","terrestrial_date":"2014-10-16","sol":"780","ls":"215","season":"Month 8","min_temp":"-72","max_temp":"0","pressure":"852","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:30","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"14"},{"id":"722","terrestrial_date":"2014-10-15","sol":"779","ls":"214","season":"Month 8","min_temp":"-71","max_temp":"6","pressure":"845","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:30","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"14"},{"id":"721","terrestrial_date":"2014-10-14","sol":"778","ls":"213","season":"Month 8","min_temp":"-70","max_temp":"6","pressure":"846","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:29","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"13"},{"id":"720","terrestrial_date":"2014-10-13","sol":"777","ls":"213","season":"Month 8","min_temp":"-72","max_temp":"8","pressure":"841","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:29","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"14"},{"id":"718","terrestrial_date":"2014-10-12","sol":"776","ls":"212","season":"Month 8","min_temp":"-72","max_temp":"5","pressure":"841","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:29","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"13"},{"id":"717","terrestrial_date":"2014-10-11","sol":"775","ls":"211","season":"Month 8","min_temp":"-71","max_temp":"3","pressure":"838","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:28","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"14"},{"id":"719","terrestrial_date":"2014-10-10","sol":"774","ls":"211","season":"Month 8","min_temp":"-73","max_temp":"1","pressure":"838","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:28","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"14"},{"id":"716","terrestrial_date":"2014-10-09","sol":"773","ls":"210","season":"Month 8","min_temp":"-70","max_temp":"8","pressure":"838","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:28","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"14"},{"id":"715","terrestrial_date":"2014-10-08","sol":"772","ls":"210","season":"Month 8","min_temp":"-71","max_temp":"8","pressure":"835","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:27","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"13"},{"id":"714","terrestrial_date":"2014-10-07","sol":"771","ls":"209","season":"Month 7","min_temp":"-71","max_temp":"9","pressure":"836","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:27","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"14"},{"id":"713","terrestrial_date":"2014-10-06","sol":"770","ls":"208","season":"Month 7","min_temp":"-70","max_temp":"2","pressure":"829","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:27","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"14"},{"id":"711","terrestrial_date":"2014-10-05","sol":"769","ls":"208","season":"Month 7","min_temp":"-73","max_temp":"4","pressure":"829","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:26","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"15"},{"id":"712","terrestrial_date":"2014-10-04","sol":"768","ls":"207","season":"Month 7","min_temp":"-73","max_temp":"2","pressure":"826","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:26","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"14"},{"id":"710","terrestrial_date":"2014-10-03","sol":"767","ls":"206","season":"Month 7","min_temp":"-71","max_temp":"9","pressure":"824","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:26","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"14"},{"id":"709","terrestrial_date":"2014-10-02","sol":"766","ls":"206","season":"Month 7","min_temp":"-70","max_temp":"8","pressure":"824","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:26","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"14"},{"id":"708","terrestrial_date":"2014-10-01","sol":"765","ls":"205","season":"Month 7","min_temp":"-73","max_temp":"5","pressure":"820","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:25","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"14"},{"id":"707","terrestrial_date":"2014-09-30","sol":"764","ls":"205","season":"Month 7","min_temp":"-73","max_temp":"4","pressure":"821","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:25","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"14"},{"id":"706","terrestrial_date":"2014-09-29","sol":"763","ls":"204","season":"Month 7","min_temp":"-78","max_temp":"6","pressure":"817","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:25","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"15"},{"id":"703","terrestrial_date":"2014-09-28","sol":"762","ls":"203","season":"Month 7","min_temp":"-75","max_temp":"7","pressure":"814","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:25","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"14"},{"id":"705","terrestrial_date":"2014-09-27","sol":"761","ls":"203","season":"Month 7","min_temp":"-72","max_temp":"7","pressure":"814","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:24","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"14"},{"id":"704","terrestrial_date":"2014-09-25","sol":"760","ls":"202","season":"Month 7","min_temp":"-70","max_temp":"11","pressure":"810","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:24","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"14"},{"id":"702","terrestrial_date":"2014-09-24","sol":"759","ls":"201","season":"Month 7","min_temp":"-77","max_temp":"8","pressure":"809","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:24","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"13"},{"id":"701","terrestrial_date":"2014-09-23","sol":"758","ls":"201","season":"Month 7","min_temp":"-71","max_temp":"3","pressure":"810","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:24","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"14"},{"id":"700","terrestrial_date":"2014-09-22","sol":"757","ls":"200","season":"Month 7","min_temp":"-73","max_temp":"5","pressure":"807","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:24","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"14"},{"id":"699","terrestrial_date":"2014-09-21","sol":"756","ls":"200","season":"Month 7","min_temp":"-73","max_temp":"3","pressure":"806","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:23","local_uv_irradiance_index":"Moderate","min_gts_temp":"-77","max_gts_temp":"15"},{"id":"697","terrestrial_date":"2014-09-20","sol":"755","ls":"199","season":"Month 7","min_temp":"-74","max_temp":"2","pressure":"806","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:23","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"14"},{"id":"696","terrestrial_date":"2014-09-19","sol":"754","ls":"198","season":"Month 7","min_temp":"-73","max_temp":"6","pressure":"802","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:23","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"14"},{"id":"698","terrestrial_date":"2014-09-18","sol":"753","ls":"198","season":"Month 7","min_temp":"-73","max_temp":"8","pressure":"800","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:23","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"12"},{"id":"694","terrestrial_date":"2014-09-17","sol":"752","ls":"197","season":"Month 7","min_temp":"-71","max_temp":"-3","pressure":"798","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:23","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"8"},{"id":"695","terrestrial_date":"2014-09-16","sol":"751","ls":"197","season":"Month 7","min_temp":"-72","max_temp":"-6","pressure":"797","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"13"},{"id":"693","terrestrial_date":"2014-09-15","sol":"750","ls":"196","season":"Month 7","min_temp":"-72","max_temp":"4","pressure":"796","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"8"},{"id":"692","terrestrial_date":"2014-09-14","sol":"749","ls":"195","season":"Month 7","min_temp":"-73","max_temp":"3","pressure":"794","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"7"},{"id":"690","terrestrial_date":"2014-09-13","sol":"748","ls":"195","season":"Month 7","min_temp":"-71","max_temp":"5","pressure":"791","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-69","max_gts_temp":"10"},{"id":"691","terrestrial_date":"2014-09-12","sol":"747","ls":"194","season":"Month 7","min_temp":"-73","max_temp":"6","pressure":"789","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"15"},{"id":"688","terrestrial_date":"2014-09-11","sol":"746","ls":"194","season":"Month 7","min_temp":"-76","max_temp":"2","pressure":"788","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"14"},{"id":"689","terrestrial_date":"2014-09-10","sol":"745","ls":"193","season":"Month 7","min_temp":"-74","max_temp":"1","pressure":"787","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"13"},{"id":"687","terrestrial_date":"2014-09-09","sol":"744","ls":"192","season":"Month 7","min_temp":"-75","max_temp":"-2","pressure":"786","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"10"},{"id":"686","terrestrial_date":"2014-09-08","sol":"743","ls":"192","season":"Month 7","min_temp":"-72","max_temp":"0","pressure":"786","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-67","max_gts_temp":"14"},{"id":"684","terrestrial_date":"2014-09-07","sol":"742","ls":"191","season":"Month 7","min_temp":"-72","max_temp":"0","pressure":"784","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-68","max_gts_temp":"4"},{"id":"683","terrestrial_date":"2014-09-06","sol":"741","ls":"191","season":"Month 7","min_temp":"-70","max_temp":"-1","pressure":"784","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-68","max_gts_temp":"4"},{"id":"685","terrestrial_date":"2014-09-05","sol":"740","ls":"190","season":"Month 7","min_temp":"-76","max_temp":"-2","pressure":"782","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-65","max_gts_temp":"2"},{"id":"682","terrestrial_date":"2014-09-04","sol":"739","ls":"189","season":"Month 7","min_temp":"-77","max_temp":"-1","pressure":"779","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"4"},{"id":"681","terrestrial_date":"2014-09-03","sol":"738","ls":"189","season":"Month 7","min_temp":"-75","max_temp":"0","pressure":"778","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"7"},{"id":"680","terrestrial_date":"2014-09-02","sol":"737","ls":"188","season":"Month 7","min_temp":"-74","max_temp":"3","pressure":"777","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"9"},{"id":"679","terrestrial_date":"2014-09-01","sol":"736","ls":"188","season":"Month 7","min_temp":"-76","max_temp":"0","pressure":"776","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"9"},{"id":"678","terrestrial_date":"2014-08-31","sol":"735","ls":"187","season":"Month 7","min_temp":"-73","max_temp":"1","pressure":"776","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"14"},{"id":"675","terrestrial_date":"2014-08-30","sol":"734","ls":"186","season":"Month 7","min_temp":"-73","max_temp":"-3","pressure":"776","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"10"},{"id":"674","terrestrial_date":"2014-08-29","sol":"733","ls":"186","season":"Month 7","min_temp":"-74","max_temp":"-2","pressure":"773","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"15"},{"id":"677","terrestrial_date":"2014-08-28","sol":"732","ls":"185","season":"Month 7","min_temp":"-75","max_temp":"-8","pressure":"773","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"13"},{"id":"673","terrestrial_date":"2014-08-27","sol":"731","ls":"185","season":"Month 7","min_temp":"-75","max_temp":"-4","pressure":"771","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"12"},{"id":"676","terrestrial_date":"2014-08-26","sol":"730","ls":"184","season":"Month 7","min_temp":"-76","max_temp":"3","pressure":"770","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"12"},{"id":"672","terrestrial_date":"2014-08-25","sol":"729","ls":"183","season":"Month 7","min_temp":"-75","max_temp":"3","pressure":"768","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"13"},{"id":"670","terrestrial_date":"2014-08-24","sol":"728","ls":"183","season":"Month 7","min_temp":"-74","max_temp":"1","pressure":"766","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"14"},{"id":"671","terrestrial_date":"2014-08-23","sol":"727","ls":"182","season":"Month 7","min_temp":"-76","max_temp":"1","pressure":"766","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"14"},{"id":"668","terrestrial_date":"2014-08-22","sol":"726","ls":"182","season":"Month 7","min_temp":"-75","max_temp":"1","pressure":"766","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"14"},{"id":"669","terrestrial_date":"2014-08-21","sol":"725","ls":"181","season":"Month 7","min_temp":"-73","max_temp":"-5","pressure":"764","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"13"},{"id":"667","terrestrial_date":"2014-08-19","sol":"724","ls":"181","season":"Month 7","min_temp":"-74","max_temp":"1","pressure":"763","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"14"},{"id":"666","terrestrial_date":"2014-08-18","sol":"723","ls":"180","season":"Month 7","min_temp":"-73","max_temp":"2","pressure":"761","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"14"},{"id":"665","terrestrial_date":"2014-08-17","sol":"722","ls":"179","season":"Month 6","min_temp":"-74","max_temp":"1","pressure":"760","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"14"},{"id":"664","terrestrial_date":"2014-08-16","sol":"721","ls":"179","season":"Month 6","min_temp":"-73","max_temp":"-5","pressure":"761","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"12"},{"id":"663","terrestrial_date":"2014-08-15","sol":"720","ls":"178","season":"Month 6","min_temp":"-74","max_temp":"-1","pressure":"760","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"13"},{"id":"662","terrestrial_date":"2014-08-14","sol":"719","ls":"178","season":"Month 6","min_temp":"-77","max_temp":"-1","pressure":"758","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"12"},{"id":"660","terrestrial_date":"2014-08-13","sol":"718","ls":"177","season":"Month 6","min_temp":"-77","max_temp":"3","pressure":"757","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"12"},{"id":"661","terrestrial_date":"2014-08-12","sol":"717","ls":"176","season":"Month 6","min_temp":"-76","max_temp":"5","pressure":"756","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"11"},{"id":"659","terrestrial_date":"2014-08-11","sol":"716","ls":"176","season":"Month 6","min_temp":"-75","max_temp":"2","pressure":"755","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"6"},{"id":"657","terrestrial_date":"2014-08-10","sol":"715","ls":"175","season":"Month 6","min_temp":"-73","max_temp":"2","pressure":"755","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:20","local_uv_irradiance_index":"Moderate","min_gts_temp":"-75","max_gts_temp":"3"},{"id":"655","terrestrial_date":"2014-08-09","sol":"714","ls":"175","season":"Month 6","min_temp":"-76","max_temp":"2","pressure":"754","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:20","local_uv_irradiance_index":"Moderate","min_gts_temp":"-79","max_gts_temp":"8"},{"id":"656","terrestrial_date":"2014-08-08","sol":"713","ls":"174","season":"Month 6","min_temp":"-76","max_temp":"-12","pressure":"754","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-87","max_gts_temp":"19"},{"id":"658","terrestrial_date":"2014-08-07","sol":"712","ls":"174","season":"Month 6","min_temp":"-77","max_temp":"-10","pressure":"752","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-87","max_gts_temp":"18"},{"id":"654","terrestrial_date":"2014-08-06","sol":"711","ls":"173","season":"Month 6","min_temp":"-75","max_temp":"-11","pressure":"751","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-86","max_gts_temp":"18"},{"id":"653","terrestrial_date":"2014-08-05","sol":"710","ls":"172","season":"Month 6","min_temp":"-77","max_temp":"-12","pressure":"751","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-88","max_gts_temp":"15"},{"id":"652","terrestrial_date":"2014-08-04","sol":"709","ls":"172","season":"Month 6","min_temp":"-73","max_temp":"-11","pressure":"750","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"16"},{"id":"649","terrestrial_date":"2014-08-03","sol":"708","ls":"171","season":"Month 6","min_temp":"-76","max_temp":"1","pressure":"749","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"5"},{"id":"651","terrestrial_date":"2014-08-02","sol":"707","ls":"171","season":"Month 6","min_temp":"-76","max_temp":"1","pressure":"749","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"5"},{"id":"650","terrestrial_date":"2014-08-01","sol":"706","ls":"170","season":"Month 6","min_temp":"-76","max_temp":"0","pressure":"748","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"8"},{"id":"648","terrestrial_date":"2014-07-31","sol":"705","ls":"170","season":"Month 6","min_temp":"-76","max_temp":"-9","pressure":"746","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:23","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"14"},{"id":"647","terrestrial_date":"2014-07-30","sol":"704","ls":"169","season":"Month 6","min_temp":"-75","max_temp":"-5","pressure":"747","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:23","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-85","max_gts_temp":"12"},{"id":"646","terrestrial_date":"2014-07-29","sol":"703","ls":"169","season":"Month 6","min_temp":"-75","max_temp":"-6","pressure":"747","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:23","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"9"},{"id":"645","terrestrial_date":"2014-07-28","sol":"702","ls":"168","season":"Month 6","min_temp":"-76","max_temp":"-2","pressure":"745","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:23","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"8"},{"id":"644","terrestrial_date":"2014-07-27","sol":"701","ls":"167","season":"Month 6","min_temp":"-77","max_temp":"-1","pressure":"745","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:23","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"8"},{"id":"643","terrestrial_date":"2014-07-26","sol":"700","ls":"167","season":"Month 6","min_temp":"-75","max_temp":"-2","pressure":"745","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:24","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-83","max_gts_temp":"8"},{"id":"642","terrestrial_date":"2014-07-25","sol":"699","ls":"166","season":"Month 6","min_temp":"-76","max_temp":"-2","pressure":"744","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:24","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"9"},{"id":"641","terrestrial_date":"2014-07-24","sol":"698","ls":"166","season":"Month 6","min_temp":"-77","max_temp":"-3","pressure":"743","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:24","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-83","max_gts_temp":"9"},{"id":"640","terrestrial_date":"2014-07-23","sol":"697","ls":"165","season":"Month 6","min_temp":"-74","max_temp":"-4","pressure":"743","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:24","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"8"},{"id":"639","terrestrial_date":"2014-07-22","sol":"696","ls":"165","season":"Month 6","min_temp":"-75","max_temp":"-2","pressure":"743","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:24","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-86","max_gts_temp":"9"},{"id":"638","terrestrial_date":"2014-07-21","sol":"695","ls":"164","season":"Month 6","min_temp":"-76","max_temp":"-1","pressure":"741","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:25","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"11"},{"id":"635","terrestrial_date":"2014-07-20","sol":"694","ls":"164","season":"Month 6","min_temp":"-75","max_temp":"-1","pressure":"740","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:25","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"2"},{"id":"636","terrestrial_date":"2014-07-19","sol":"693","ls":"163","season":"Month 6","min_temp":"-76","max_temp":"-2","pressure":"741","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:25","sunset":"17:20","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"2"},{"id":"637","terrestrial_date":"2014-07-18","sol":"692","ls":"162","season":"Month 6","min_temp":"-76","max_temp":"-4","pressure":"742","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:25","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"6"},{"id":"634","terrestrial_date":"2014-07-17","sol":"691","ls":"162","season":"Month 6","min_temp":"-76","max_temp":"-8","pressure":"741","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:25","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"4"},{"id":"633","terrestrial_date":"2014-07-16","sol":"690","ls":"161","season":"Month 6","min_temp":"-77","max_temp":"-7","pressure":"741","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:26","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"5"},{"id":"632","terrestrial_date":"2014-07-15","sol":"689","ls":"161","season":"Month 6","min_temp":"-76","max_temp":"-6","pressure":"742","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:26","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"6"},{"id":"631","terrestrial_date":"2014-07-14","sol":"688","ls":"160","season":"Month 6","min_temp":"-77","max_temp":"-5","pressure":"741","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:26","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"8"},{"id":"630","terrestrial_date":"2014-07-12","sol":"687","ls":"160","season":"Month 6","min_temp":"-77","max_temp":"-2","pressure":"739","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:26","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"3"},{"id":"629","terrestrial_date":"2014-07-11","sol":"686","ls":"159","season":"Month 6","min_temp":"-76","max_temp":"-4","pressure":"738","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:26","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"2"},{"id":"628","terrestrial_date":"2014-07-10","sol":"685","ls":"159","season":"Month 6","min_temp":"-77","max_temp":"-2","pressure":"740","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:27","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-85","max_gts_temp":"8"},{"id":"627","terrestrial_date":"2014-07-09","sol":"684","ls":"158","season":"Month 6","min_temp":"-75","max_temp":"-9","pressure":"741","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:27","sunset":"17:21","local_uv_irradiance_index":"Moderate","min_gts_temp":"-82","max_gts_temp":"6"},{"id":"626","terrestrial_date":"2014-07-08","sol":"683","ls":"158","season":"Month 6","min_temp":"-79","max_temp":"-7","pressure":"741","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:27","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"8"},{"id":"625","terrestrial_date":"2014-07-07","sol":"682","ls":"157","season":"Month 6","min_temp":"-77","max_temp":"-16","pressure":"739","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:27","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"2"},{"id":"623","terrestrial_date":"2014-07-06","sol":"681","ls":"156","season":"Month 6","min_temp":"-79","max_temp":"-17","pressure":"738","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:28","sunset":"17:21","local_uv_irradiance_index":"Moderate","min_gts_temp":"-80","max_gts_temp":"2"},{"id":"620","terrestrial_date":"2014-07-05","sol":"680","ls":"156","season":"Month 6","min_temp":"-76","max_temp":"-15","pressure":"740","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:28","sunset":"17:21","local_uv_irradiance_index":"Moderate","min_gts_temp":"-80","max_gts_temp":"1"},{"id":"624","terrestrial_date":"2014-07-04","sol":"679","ls":"155","season":"Month 6","min_temp":"-77","max_temp":"-13","pressure":"738","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:28","sunset":"17:21","local_uv_irradiance_index":"Moderate","min_gts_temp":"-80","max_gts_temp":"1"},{"id":"621","terrestrial_date":"2014-07-03","sol":"678","ls":"155","season":"Month 6","min_temp":"-77","max_temp":"-17","pressure":"739","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:28","sunset":"17:22","local_uv_irradiance_index":"Moderate","min_gts_temp":"-79","max_gts_temp":"-1"},{"id":"622","terrestrial_date":"2014-07-02","sol":"677","ls":"154","season":"Month 6","min_temp":"-78","max_temp":"-12","pressure":"737","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:28","sunset":"17:22","local_uv_irradiance_index":"Moderate","min_gts_temp":"-79","max_gts_temp":"0"},{"id":"619","terrestrial_date":"2014-07-01","sol":"676","ls":"154","season":"Month 6","min_temp":"-80","max_temp":"-17","pressure":"738","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:29","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-90","max_gts_temp":"9"},{"id":"618","terrestrial_date":"2014-06-30","sol":"675","ls":"153","season":"Month 6","min_temp":"-80","max_temp":"-12","pressure":"740","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:29","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-91","max_gts_temp":"10"},{"id":"617","terrestrial_date":"2014-06-29","sol":"674","ls":"153","season":"Month 6","min_temp":"-80","max_temp":"-17","pressure":"739","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:29","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-94","max_gts_temp":"7"},{"id":"616","terrestrial_date":"2014-06-28","sol":"673","ls":"152","season":"Month 6","min_temp":"-78","max_temp":"-16","pressure":"738","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:29","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-96","max_gts_temp":"8"},{"id":"615","terrestrial_date":"2014-06-27","sol":"672","ls":"152","season":"Month 6","min_temp":"-78","max_temp":"-16","pressure":"739","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:30","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-83","max_gts_temp":"7"},{"id":"614","terrestrial_date":"2014-06-26","sol":"671","ls":"151","season":"Month 6","min_temp":"-79","max_temp":"-15","pressure":"740","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:30","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"6"},{"id":"612","terrestrial_date":"2014-06-25","sol":"670","ls":"151","season":"Month 6","min_temp":"-79","max_temp":"-7","pressure":"737","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:30","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-85","max_gts_temp":"4"},{"id":"613","terrestrial_date":"2014-06-24","sol":"669","ls":"150","season":"Month 6","min_temp":"-84","max_temp":"-4","pressure":"735","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:30","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-91","max_gts_temp":"8"},{"id":"611","terrestrial_date":"2014-06-23","sol":"668","ls":"150","season":"Month 6","min_temp":"-75","max_temp":"-4","pressure":"734","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:30","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"6"},{"id":"606","terrestrial_date":"2014-06-22","sol":"667","ls":"149","season":"Month 5","min_temp":"-76","max_temp":"-7","pressure":"736","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:31","sunset":"17:22","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"0"},{"id":"608","terrestrial_date":"2014-06-21","sol":"666","ls":"148","season":"Month 5","min_temp":"-77","max_temp":"-7","pressure":"736","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:31","sunset":"17:23","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"0"},{"id":"610","terrestrial_date":"2014-06-20","sol":"665","ls":"148","season":"Month 5","min_temp":"-78","max_temp":"-4","pressure":"735","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:31","sunset":"17:23","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"7"},{"id":"607","terrestrial_date":"2014-06-19","sol":"664","ls":"147","season":"Month 5","min_temp":"-78","max_temp":"-8","pressure":"732","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:31","sunset":"17:23","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"0"},{"id":"609","terrestrial_date":"2014-06-18","sol":"663","ls":"147","season":"Month 5","min_temp":"-77","max_temp":"-10","pressure":"734","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:32","sunset":"17:23","local_uv_irradiance_index":"High","min_gts_temp":"-83","max_gts_temp":"4"},{"id":"605","terrestrial_date":"2014-06-17","sol":"662","ls":"146","season":"Month 5","min_temp":"-77","max_temp":"-11","pressure":"735","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:32","sunset":"17:23","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"4"},{"id":"604","terrestrial_date":"2014-06-16","sol":"661","ls":"146","season":"Month 5","min_temp":"-76","max_temp":"-10","pressure":"735","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:32","sunset":"17:23","local_uv_irradiance_index":"High","min_gts_temp":"-85","max_gts_temp":"1"},{"id":"601","terrestrial_date":"2014-06-15","sol":"660","ls":"145","season":"Month 5","min_temp":"-78","max_temp":"-12","pressure":"735","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:32","sunset":"17:23","local_uv_irradiance_index":"Moderate","min_gts_temp":"-84","max_gts_temp":"1"},{"id":"603","terrestrial_date":"2014-06-14","sol":"659","ls":"145","season":"Month 5","min_temp":"-77","max_temp":"-14","pressure":"738","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:33","sunset":"17:23","local_uv_irradiance_index":"Moderate","min_gts_temp":"-83","max_gts_temp":"1"},{"id":"602","terrestrial_date":"2014-06-13","sol":"658","ls":"144","season":"Month 5","min_temp":"-79","max_temp":"-10","pressure":"739","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:33","sunset":"17:23","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"1"},{"id":"600","terrestrial_date":"2014-06-12","sol":"657","ls":"144","season":"Month 5","min_temp":"-79","max_temp":"-6","pressure":"739","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:33","sunset":"17:23","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"1"},{"id":"599","terrestrial_date":"2014-06-11","sol":"656","ls":"143","season":"Month 5","min_temp":"-80","max_temp":"-13","pressure":"740","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:33","sunset":"17:23","local_uv_irradiance_index":"Moderate","min_gts_temp":"-83","max_gts_temp":"-3"},{"id":"598","terrestrial_date":"2014-06-10","sol":"655","ls":"143","season":"Month 5","min_temp":"-77","max_temp":"-14","pressure":"741","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:33","sunset":"17:24","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"0"},{"id":"597","terrestrial_date":"2014-06-09","sol":"654","ls":"142","season":"Month 5","min_temp":"-79","max_temp":"-8","pressure":"742","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:34","sunset":"17:24","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"-4"},{"id":"596","terrestrial_date":"2014-06-08","sol":"653","ls":"142","season":"Month 5","min_temp":"-80","max_temp":"-13","pressure":"744","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:34","sunset":"17:24","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"-4"},{"id":"594","terrestrial_date":"2014-06-07","sol":"652","ls":"141","season":"Month 5","min_temp":"-79","max_temp":"-12","pressure":"745","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:34","sunset":"17:24","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"-3"},{"id":"595","terrestrial_date":"2014-06-05","sol":"651","ls":"141","season":"Month 5","min_temp":"-80","max_temp":"-8","pressure":"743","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:34","sunset":"17:24","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"2"},{"id":"593","terrestrial_date":"2014-06-04","sol":"650","ls":"140","season":"Month 5","min_temp":"-80","max_temp":"-11","pressure":"743","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:35","sunset":"17:24","local_uv_irradiance_index":"High","min_gts_temp":"-85","max_gts_temp":"0"},{"id":"592","terrestrial_date":"2014-06-03","sol":"649","ls":"140","season":"Month 5","min_temp":"-79","max_temp":"-14","pressure":"745","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:35","sunset":"17:24","local_uv_irradiance_index":"High","min_gts_temp":"-85","max_gts_temp":"-3"},{"id":"591","terrestrial_date":"2014-06-02","sol":"648","ls":"139","season":"Month 5","min_temp":"-80","max_temp":"-20","pressure":"746","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:35","sunset":"17:24","local_uv_irradiance_index":"High","min_gts_temp":"-85","max_gts_temp":"-2"},{"id":"590","terrestrial_date":"2014-06-01","sol":"647","ls":"139","season":"Month 5","min_temp":"-79","max_temp":"-19","pressure":"746","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:35","sunset":"17:24","local_uv_irradiance_index":"High","min_gts_temp":"-85","max_gts_temp":"-2"},{"id":"587","terrestrial_date":"2014-05-31","sol":"646","ls":"138","season":"Month 5","min_temp":"-81","max_temp":"-16","pressure":"746","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:35","sunset":"17:24","local_uv_irradiance_index":"High","min_gts_temp":"-87","max_gts_temp":"0"},{"id":"589","terrestrial_date":"2014-05-30","sol":"645","ls":"138","season":"Month 5","min_temp":"-81","max_temp":"-8","pressure":"746","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:36","sunset":"17:25","local_uv_irradiance_index":"High","min_gts_temp":"-86","max_gts_temp":"0"},{"id":"588","terrestrial_date":"2014-05-29","sol":"644","ls":"137","season":"Month 5","min_temp":"-80","max_temp":"-10","pressure":"748","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:36","sunset":"17:25","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"0"},{"id":"586","terrestrial_date":"2014-05-28","sol":"643","ls":"137","season":"Month 5","min_temp":"-80","max_temp":"-14","pressure":"748","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:36","sunset":"17:25","local_uv_irradiance_index":"High","min_gts_temp":"-88","max_gts_temp":"-2"},{"id":"585","terrestrial_date":"2014-05-27","sol":"642","ls":"136","season":"Month 5","min_temp":"-80","max_temp":"-19","pressure":"749","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:36","sunset":"17:25","local_uv_irradiance_index":"High","min_gts_temp":"-85","max_gts_temp":"-2"},{"id":"584","terrestrial_date":"2014-05-26","sol":"641","ls":"136","season":"Month 5","min_temp":"-81","max_temp":"-19","pressure":"749","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:37","sunset":"17:25","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"-2"},{"id":"583","terrestrial_date":"2014-05-25","sol":"640","ls":"135","season":"Month 5","min_temp":"-81","max_temp":"-11","pressure":"750","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:37","sunset":"17:25","local_uv_irradiance_index":"Moderate","min_gts_temp":"-81","max_gts_temp":"-7"},{"id":"581","terrestrial_date":"2014-05-24","sol":"639","ls":"135","season":"Month 5","min_temp":"-80","max_temp":"-16","pressure":"750","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:37","sunset":"17:25","local_uv_irradiance_index":"Moderate","min_gts_temp":"-80","max_gts_temp":"-9"},{"id":"579","terrestrial_date":"2014-05-23","sol":"638","ls":"134","season":"Month 5","min_temp":"-80","max_temp":"-12","pressure":"751","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:37","sunset":"17:25","local_uv_irradiance_index":"Moderate","min_gts_temp":"-82","max_gts_temp":"-9"},{"id":"580","terrestrial_date":"2014-05-22","sol":"637","ls":"134","season":"Month 5","min_temp":"-80","max_temp":"-18","pressure":"752","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:37","sunset":"17:25","local_uv_irradiance_index":"Moderate","min_gts_temp":"-85","max_gts_temp":"-5"},{"id":"582","terrestrial_date":"2014-05-21","sol":"636","ls":"133","season":"Month 5","min_temp":"-81","max_temp":"-19","pressure":"752","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:38","sunset":"17:25","local_uv_irradiance_index":"High","min_gts_temp":"-89","max_gts_temp":"0"},{"id":"578","terrestrial_date":"2014-05-20","sol":"635","ls":"133","season":"Month 5","min_temp":"-82","max_temp":"-10","pressure":"754","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:38","sunset":"17:26","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"-3"},{"id":"577","terrestrial_date":"2014-05-19","sol":"634","ls":"132","season":"Month 5","min_temp":"-82","max_temp":"-20","pressure":"753","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:38","sunset":"17:26","local_uv_irradiance_index":"High","min_gts_temp":"-85","max_gts_temp":"4"},{"id":"574","terrestrial_date":"2014-05-18","sol":"633","ls":"132","season":"Month 5","min_temp":"-81","max_temp":"-15","pressure":"754","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:38","sunset":"17:26","local_uv_irradiance_index":"High","min_gts_temp":"-85","max_gts_temp":"-3"},{"id":"575","terrestrial_date":"2014-05-17","sol":"632","ls":"131","season":"Month 5","min_temp":"-79","max_temp":"-18","pressure":"755","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:38","sunset":"17:26","local_uv_irradiance_index":"High","min_gts_temp":"-86","max_gts_temp":"-4"},{"id":"576","terrestrial_date":"2014-05-16","sol":"631","ls":"131","season":"Month 5","min_temp":"-81","max_temp":"-18","pressure":"756","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:39","sunset":"17:26","local_uv_irradiance_index":"High","min_gts_temp":"-88","max_gts_temp":"-1"},{"id":"573","terrestrial_date":"2014-05-15","sol":"630","ls":"130","season":"Month 5","min_temp":"-82","max_temp":"-20","pressure":"757","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:39","sunset":"17:26","local_uv_irradiance_index":"High","min_gts_temp":"-93","max_gts_temp":"-2"},{"id":"572","terrestrial_date":"2014-05-14","sol":"629","ls":"130","season":"Month 5","min_temp":"-84","max_temp":"-24","pressure":"759","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:39","sunset":"17:26","local_uv_irradiance_index":"Moderate","min_gts_temp":"-94","max_gts_temp":"-3"},{"id":"571","terrestrial_date":"2014-05-13","sol":"628","ls":"129","season":"Month 5","min_temp":"-81","max_temp":"-23","pressure":"759","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:39","sunset":"17:26","local_uv_irradiance_index":"Moderate","min_gts_temp":"-90","max_gts_temp":"-2"},{"id":"570","terrestrial_date":"2014-05-12","sol":"627","ls":"129","season":"Month 5","min_temp":"-82","max_temp":"-19","pressure":"759","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:39","sunset":"17:26","local_uv_irradiance_index":"Moderate","min_gts_temp":"-93","max_gts_temp":"-3"},{"id":"567","terrestrial_date":"2014-05-11","sol":"626","ls":"128","season":"Month 5","min_temp":"-82","max_temp":"-21","pressure":"760","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:40","sunset":"17:26","local_uv_irradiance_index":"Moderate","min_gts_temp":"-94","max_gts_temp":"-3"},{"id":"569","terrestrial_date":"2014-05-10","sol":"625","ls":"128","season":"Month 5","min_temp":"-81","max_temp":"-24","pressure":"761","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:40","sunset":"17:27","local_uv_irradiance_index":"Moderate","min_gts_temp":"-95","max_gts_temp":"-4"},{"id":"568","terrestrial_date":"2014-05-09","sol":"624","ls":"127","season":"Month 5","min_temp":"-82","max_temp":"-19","pressure":"762","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:40","sunset":"17:27","local_uv_irradiance_index":"Moderate","min_gts_temp":"-92","max_gts_temp":"-2"},{"id":"566","terrestrial_date":"2014-05-08","sol":"623","ls":"127","season":"Month 5","min_temp":"-83","max_temp":"-21","pressure":"763","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:40","sunset":"17:27","local_uv_irradiance_index":"Moderate","min_gts_temp":"-92","max_gts_temp":"-2"},{"id":"565","terrestrial_date":"2014-05-07","sol":"622","ls":"126","season":"Month 5","min_temp":"-84","max_temp":"-19","pressure":"763","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:40","sunset":"17:27","local_uv_irradiance_index":"Moderate","min_gts_temp":"-92","max_gts_temp":"-3"},{"id":"564","terrestrial_date":"2014-05-06","sol":"621","ls":"126","season":"Month 5","min_temp":"-82","max_temp":"-21","pressure":"765","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:41","sunset":"17:27","local_uv_irradiance_index":"Moderate","min_gts_temp":"-93","max_gts_temp":"-3"},{"id":"563","terrestrial_date":"2014-05-05","sol":"620","ls":"125","season":"Month 5","min_temp":"-83","max_temp":"-19","pressure":"765","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:41","sunset":"17:27","local_uv_irradiance_index":"Moderate","min_gts_temp":"-96","max_gts_temp":"-4"},{"id":"560","terrestrial_date":"2014-05-04","sol":"619","ls":"125","season":"Month 5","min_temp":"-82","max_temp":"-22","pressure":"766","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:41","sunset":"17:27","local_uv_irradiance_index":"Low","min_gts_temp":"-96","max_gts_temp":"-3"},{"id":"562","terrestrial_date":"2014-05-03","sol":"618","ls":"124","season":"Month 5","min_temp":"-83","max_temp":"-23","pressure":"769","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:41","sunset":"17:27","local_uv_irradiance_index":"Moderate","min_gts_temp":"-94","max_gts_temp":"-3"},{"id":"561","terrestrial_date":"2014-05-02","sol":"617","ls":"124","season":"Month 5","min_temp":"-84","max_temp":"-19","pressure":"769","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:41","sunset":"17:27","local_uv_irradiance_index":"Moderate","min_gts_temp":"-94","max_gts_temp":"-4"},{"id":"559","terrestrial_date":"2014-05-01","sol":"616","ls":"123","season":"Month 5","min_temp":"-82","max_temp":"-19","pressure":"769","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:42","sunset":"17:27","local_uv_irradiance_index":"Moderate","min_gts_temp":"-93","max_gts_temp":"-4"},{"id":"558","terrestrial_date":"2014-04-29","sol":"615","ls":"123","season":"Month 5","min_temp":"-83","max_temp":"-23","pressure":"771","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:42","sunset":"17:28","local_uv_irradiance_index":"Moderate","min_gts_temp":"-93","max_gts_temp":"-4"},{"id":"557","terrestrial_date":"2014-04-28","sol":"614","ls":"122","season":"Month 5","min_temp":"-82","max_temp":"-19","pressure":"772","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:42","sunset":"17:28","local_uv_irradiance_index":"Moderate","min_gts_temp":"-95","max_gts_temp":"-5"},{"id":"556","terrestrial_date":"2014-04-27","sol":"613","ls":"122","season":"Month 5","min_temp":"-84","max_temp":"-21","pressure":"773","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:42","sunset":"17:28","local_uv_irradiance_index":"Moderate","min_gts_temp":"-98","max_gts_temp":"-4"},{"id":"553","terrestrial_date":"2014-04-26","sol":"612","ls":"121","season":"Month 5","min_temp":"-85","max_temp":"-27","pressure":"774","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:42","sunset":"17:28","local_uv_irradiance_index":"Moderate","min_gts_temp":"-95","max_gts_temp":"-4"},{"id":"555","terrestrial_date":"2014-04-25","sol":"611","ls":"121","season":"Month 5","min_temp":"-84","max_temp":"-23","pressure":"775","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:43","sunset":"17:28","local_uv_irradiance_index":"Moderate","min_gts_temp":"-93","max_gts_temp":"-5"},{"id":"554","terrestrial_date":"2014-04-24","sol":"610","ls":"120","season":"Month 5","min_temp":"-85","max_temp":"-20","pressure":"776","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:43","sunset":"17:28","local_uv_irradiance_index":"Moderate","min_gts_temp":"-94","max_gts_temp":"-5"},{"id":"552","terrestrial_date":"2014-04-23","sol":"609","ls":"120","season":"Month 5","min_temp":"-84","max_temp":"-20","pressure":"777","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:43","sunset":"17:28","local_uv_irradiance_index":"Moderate","min_gts_temp":"-93","max_gts_temp":"-5"},{"id":"551","terrestrial_date":"2014-04-22","sol":"608","ls":"119","season":"Month 4","min_temp":"-85","max_temp":"-21","pressure":"777","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:43","sunset":"17:28","local_uv_irradiance_index":"Moderate","min_gts_temp":"-97","max_gts_temp":"-4"},{"id":"550","terrestrial_date":"2014-04-21","sol":"607","ls":"119","season":"Month 4","min_temp":"-83","max_temp":"-28","pressure":"779","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:43","sunset":"17:28","local_uv_irradiance_index":"Low","min_gts_temp":"-96","max_gts_temp":"-5"},{"id":"549","terrestrial_date":"2014-04-20","sol":"606","ls":"118","season":"Month 4","min_temp":"-84","max_temp":"-27","pressure":"780","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:43","sunset":"17:28","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-8"},{"id":"547","terrestrial_date":"2014-04-19","sol":"605","ls":"118","season":"Month 4","min_temp":"-83","max_temp":"-22","pressure":"781","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:44","sunset":"17:29","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-9"},{"id":"546","terrestrial_date":"2014-04-18","sol":"604","ls":"118","season":"Month 4","min_temp":"-84","max_temp":"-17","pressure":"782","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:44","sunset":"17:29","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-9"},{"id":"548","terrestrial_date":"2014-04-17","sol":"603","ls":"117","season":"Month 4","min_temp":"-83","max_temp":"-15","pressure":"783","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:44","sunset":"17:29","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"-9"},{"id":"545","terrestrial_date":"2014-04-16","sol":"602","ls":"117","season":"Month 4","min_temp":"-82","max_temp":"-22","pressure":"784","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:44","sunset":"17:29","local_uv_irradiance_index":"Moderate","min_gts_temp":"-82","max_gts_temp":"-11"},{"id":"544","terrestrial_date":"2014-04-15","sol":"601","ls":"116","season":"Month 4","min_temp":"-82","max_temp":"-25","pressure":"785","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:44","sunset":"17:29","local_uv_irradiance_index":"Moderate","min_gts_temp":"-74","max_gts_temp":"-8"},{"id":"541","terrestrial_date":"2014-04-14","sol":"600","ls":"116","season":"Month 4","min_temp":"-81","max_temp":"-25","pressure":"787","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:45","sunset":"17:29","local_uv_irradiance_index":"Moderate","min_gts_temp":"-81","max_gts_temp":"-14"},{"id":"543","terrestrial_date":"2014-04-13","sol":"599","ls":"115","season":"Month 4","min_temp":"-84","max_temp":"-20","pressure":"788","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:45","sunset":"17:29","local_uv_irradiance_index":"Moderate","min_gts_temp":"-82","max_gts_temp":"-9"},{"id":"542","terrestrial_date":"2014-04-12","sol":"598","ls":"115","season":"Month 4","min_temp":"-84","max_temp":"-25","pressure":"787","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:45","sunset":"17:29","local_uv_irradiance_index":"Moderate","min_gts_temp":"-82","max_gts_temp":"-10"},{"id":"540","terrestrial_date":"2014-04-11","sol":"597","ls":"114","season":"Month 4","min_temp":"-84","max_temp":"-26","pressure":"790","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:45","sunset":"17:29","local_uv_irradiance_index":"Moderate","min_gts_temp":"-83","max_gts_temp":"-11"},{"id":"539","terrestrial_date":"2014-04-10","sol":"596","ls":"114","season":"Month 4","min_temp":"-82","max_temp":"-24","pressure":"791","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:45","sunset":"17:29","local_uv_irradiance_index":"Moderate","min_gts_temp":"-84","max_gts_temp":"-10"},{"id":"538","terrestrial_date":"2014-04-09","sol":"595","ls":"113","season":"Month 4","min_temp":"-83","max_temp":"-25","pressure":"792","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:45","sunset":"17:30","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-12"},{"id":"537","terrestrial_date":"2014-04-08","sol":"594","ls":"113","season":"Month 4","min_temp":"-83","max_temp":"-22","pressure":"793","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:46","sunset":"17:30","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-15"},{"id":"536","terrestrial_date":"2014-04-07","sol":"593","ls":"112","season":"Month 4","min_temp":"-83","max_temp":"-23","pressure":"795","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:46","sunset":"17:30","local_uv_irradiance_index":"High","min_gts_temp":"-87","max_gts_temp":"-11"},{"id":"534","terrestrial_date":"2014-04-06","sol":"592","ls":"112","season":"Month 4","min_temp":"-83","max_temp":"-26","pressure":"795","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:46","sunset":"17:30","local_uv_irradiance_index":"Moderate","min_gts_temp":"-85","max_gts_temp":"-11"},{"id":"533","terrestrial_date":"2014-04-05","sol":"591","ls":"111","season":"Month 4","min_temp":"-82","max_temp":"-26","pressure":"795","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:46","sunset":"17:30","local_uv_irradiance_index":"Moderate","min_gts_temp":"-84","max_gts_temp":"-11"},{"id":"535","terrestrial_date":"2014-04-04","sol":"590","ls":"111","season":"Month 4","min_temp":"-82","max_temp":"-26","pressure":"797","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:46","sunset":"17:30","local_uv_irradiance_index":"Moderate","min_gts_temp":"-85","max_gts_temp":"-11"},{"id":"531","terrestrial_date":"2014-04-03","sol":"589","ls":"110","season":"Month 4","min_temp":"-85","max_temp":"-27","pressure":"798","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:46","sunset":"17:30","local_uv_irradiance_index":"Moderate","min_gts_temp":"-94","max_gts_temp":"-10"},{"id":"532","terrestrial_date":"2014-04-02","sol":"588","ls":"110","season":"Month 4","min_temp":"-84","max_temp":"-24","pressure":"799","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:47","sunset":"17:30","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-8"},{"id":"530","terrestrial_date":"2014-04-01","sol":"587","ls":"109","season":"Month 4","min_temp":"-85","max_temp":"-28","pressure":"801","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:47","sunset":"17:30","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-9"},{"id":"529","terrestrial_date":"2014-03-31","sol":"586","ls":"109","season":"Month 4","min_temp":"-82","max_temp":"-24","pressure":"802","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:47","sunset":"17:30","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-8"},{"id":"527","terrestrial_date":"2014-03-30","sol":"585","ls":"109","season":"Month 4","min_temp":"-83","max_temp":"-26","pressure":"802","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:47","sunset":"17:31","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-9"},{"id":"528","terrestrial_date":"2014-03-29","sol":"584","ls":"108","season":"Month 4","min_temp":"-82","max_temp":"-25","pressure":"804","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:47","sunset":"17:31","local_uv_irradiance_index":"Moderate","min_gts_temp":"-89","max_gts_temp":"-8"},{"id":"526","terrestrial_date":"2014-03-28","sol":"583","ls":"108","season":"Month 4","min_temp":"-82","max_temp":"-27","pressure":"806","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:47","sunset":"17:31","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-10"},{"id":"525","terrestrial_date":"2014-03-27","sol":"582","ls":"107","season":"Month 4","min_temp":"-84","max_temp":"-27","pressure":"807","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:47","sunset":"17:31","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-9"},{"id":"524","terrestrial_date":"2014-03-26","sol":"581","ls":"107","season":"Month 4","min_temp":"-84","max_temp":"-28","pressure":"808","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:48","sunset":"17:31","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-9"},{"id":"523","terrestrial_date":"2014-03-25","sol":"580","ls":"106","season":"Month 4","min_temp":"-83","max_temp":"-24","pressure":"810","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:48","sunset":"17:31","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-10"},{"id":"522","terrestrial_date":"2014-03-24","sol":"579","ls":"106","season":"Month 4","min_temp":"-82","max_temp":"-22","pressure":"811","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:48","sunset":"17:31","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-8"},{"id":"521","terrestrial_date":"2014-03-22","sol":"578","ls":"105","season":"Month 4","min_temp":"-83","max_temp":"-23","pressure":"812","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:48","sunset":"17:31","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-9"},{"id":"519","terrestrial_date":"2014-03-21","sol":"577","ls":"105","season":"Month 4","min_temp":"-84","max_temp":"-27","pressure":"813","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:48","sunset":"17:31","local_uv_irradiance_index":"Moderate","min_gts_temp":"-90","max_gts_temp":"-11"},{"id":"520","terrestrial_date":"2014-03-20","sol":"576","ls":"104","season":"Month 4","min_temp":"-84","max_temp":"-26","pressure":"815","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:48","sunset":"17:32","local_uv_irradiance_index":"Moderate","min_gts_temp":"-90","max_gts_temp":"-8"},{"id":"518","terrestrial_date":"2014-03-19","sol":"575","ls":"104","season":"Month 4","min_temp":"-82","max_temp":"-26","pressure":"816","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:49","sunset":"17:32","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-10"},{"id":"517","terrestrial_date":"2014-03-18","sol":"574","ls":"103","season":"Month 4","min_temp":"-85","max_temp":"-23","pressure":"817","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:49","sunset":"17:32","local_uv_irradiance_index":"High","min_gts_temp":"-87","max_gts_temp":"-9"},{"id":"516","terrestrial_date":"2014-03-17","sol":"573","ls":"103","season":"Month 4","min_temp":"-84","max_temp":"-27","pressure":"819","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:49","sunset":"17:32","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-10"},{"id":"514","terrestrial_date":"2014-03-16","sol":"572","ls":"102","season":"Month 4","min_temp":"-85","max_temp":"-27","pressure":"820","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:49","sunset":"17:32","local_uv_irradiance_index":"Moderate","min_gts_temp":"-89","max_gts_temp":"-8"},{"id":"515","terrestrial_date":"2014-03-15","sol":"571","ls":"102","season":"Month 4","min_temp":"-84","max_temp":"-26","pressure":"821","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:49","sunset":"17:32","local_uv_irradiance_index":"Moderate","min_gts_temp":"-92","max_gts_temp":"-8"},{"id":"513","terrestrial_date":"2014-03-14","sol":"570","ls":"102","season":"Month 4","min_temp":"-84","max_temp":"-26","pressure":"823","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:49","sunset":"17:32","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-7"},{"id":"512","terrestrial_date":"2014-03-13","sol":"569","ls":"101","season":"Month 4","min_temp":"-85","max_temp":"-27","pressure":"825","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:49","sunset":"17:32","local_uv_irradiance_index":"Moderate","min_gts_temp":"-90","max_gts_temp":"-7"},{"id":"511","terrestrial_date":"2014-03-12","sol":"568","ls":"101","season":"Month 4","min_temp":"-86","max_temp":"-28","pressure":"825","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:50","sunset":"17:32","local_uv_irradiance_index":"High","min_gts_temp":"-86","max_gts_temp":"-9"},{"id":"510","terrestrial_date":"2014-03-11","sol":"567","ls":"100","season":"Month 4","min_temp":"-86","max_temp":"-28","pressure":"827","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:50","sunset":"17:33","local_uv_irradiance_index":"High","min_gts_temp":"-87","max_gts_temp":"-13"},{"id":"507","terrestrial_date":"2014-03-10","sol":"566","ls":"100","season":"Month 4","min_temp":"-86","max_temp":"-27","pressure":"829","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:50","sunset":"17:33","local_uv_irradiance_index":"High","min_gts_temp":"-86","max_gts_temp":"-13"},{"id":"508","terrestrial_date":"2014-03-09","sol":"565","ls":"99","season":"Month 4","min_temp":"-85","max_temp":"-27","pressure":"830","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:50","sunset":"17:33","local_uv_irradiance_index":"High","min_gts_temp":"-91","max_gts_temp":"-8"},{"id":"509","terrestrial_date":"2014-03-08","sol":"564","ls":"99","season":"Month 4","min_temp":"-86","max_temp":"-27","pressure":"831","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:50","sunset":"17:33","local_uv_irradiance_index":"Moderate","min_gts_temp":"-92","max_gts_temp":"-11"},{"id":"505","terrestrial_date":"2014-03-07","sol":"563","ls":"98","season":"Month 4","min_temp":"-87","max_temp":"-31","pressure":"833","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:50","sunset":"17:33","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-9"},{"id":"506","terrestrial_date":"2014-03-06","sol":"562","ls":"98","season":"Month 4","min_temp":"-85","max_temp":"-23","pressure":"834","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:51","sunset":"17:33","local_uv_irradiance_index":"Moderate","min_gts_temp":"-92","max_gts_temp":"-11"},{"id":"504","terrestrial_date":"2014-03-05","sol":"561","ls":"97","season":"Month 4","min_temp":"-85","max_temp":"-23","pressure":"835","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:51","sunset":"17:33","local_uv_irradiance_index":"Moderate","min_gts_temp":"-89","max_gts_temp":"-5"},{"id":"503","terrestrial_date":"2014-03-04","sol":"560","ls":"97","season":"Month 4","min_temp":"-86","max_temp":"-23","pressure":"836","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:51","sunset":"17:33","local_uv_irradiance_index":"Moderate","min_gts_temp":"-97","max_gts_temp":"-6"},{"id":"502","terrestrial_date":"2014-03-03","sol":"559","ls":"96","season":"Month 4","min_temp":"-85","max_temp":"-27","pressure":"838","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:51","sunset":"17:34","local_uv_irradiance_index":"High","min_gts_temp":"-93","max_gts_temp":"-3"},{"id":"500","terrestrial_date":"2014-03-02","sol":"558","ls":"96","season":"Month 4","min_temp":"-85","max_temp":"-26","pressure":"839","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:51","sunset":"17:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-93","max_gts_temp":"-6"},{"id":"498","terrestrial_date":"2014-03-01","sol":"557","ls":"96","season":"Month 4","min_temp":"-85","max_temp":"-29","pressure":"840","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:51","sunset":"17:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-92","max_gts_temp":"-6"},{"id":"501","terrestrial_date":"2014-02-28","sol":"556","ls":"95","season":"Month 4","min_temp":"-85","max_temp":"-29","pressure":"842","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:51","sunset":"17:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-93","max_gts_temp":"-7"},{"id":"499","terrestrial_date":"2014-02-27","sol":"555","ls":"95","season":"Month 4","min_temp":"-85","max_temp":"-31","pressure":"843","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"17:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-95","max_gts_temp":"-9"},{"id":"497","terrestrial_date":"2014-02-26","sol":"554","ls":"94","season":"Month 4","min_temp":"-84","max_temp":"-22","pressure":"843","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"17:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-96","max_gts_temp":"-7"},{"id":"496","terrestrial_date":"2014-02-25","sol":"553","ls":"94","season":"Month 4","min_temp":"-84","max_temp":"-26","pressure":"845","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"17:34","local_uv_irradiance_index":"Moderate","min_gts_temp":"-90","max_gts_temp":"-10"},{"id":"493","terrestrial_date":"2014-02-24","sol":"552","ls":"93","season":"Month 4","min_temp":"-86","max_temp":"-29","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"17:34","local_uv_irradiance_index":"High","min_gts_temp":"-91","max_gts_temp":"-11"},{"id":"495","terrestrial_date":"2014-02-23","sol":"551","ls":"93","season":"Month 4","min_temp":"-85","max_temp":"-28","pressure":"848","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"17:35","local_uv_irradiance_index":"Moderate","min_gts_temp":"-92","max_gts_temp":"-11"},{"id":"494","terrestrial_date":"2014-02-22","sol":"550","ls":"92","season":"Month 4","min_temp":"-85","max_temp":"-27","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"17:35","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-9"},{"id":"492","terrestrial_date":"2014-02-21","sol":"549","ls":"92","season":"Month 4","min_temp":"-87","max_temp":"-23","pressure":"851","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"17:35","local_uv_irradiance_index":"Moderate","min_gts_temp":"-89","max_gts_temp":"-12"},{"id":"491","terrestrial_date":"2014-02-20","sol":"548","ls":"91","season":"Month 4","min_temp":"-86","max_temp":"-28","pressure":"852","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"17:35","local_uv_irradiance_index":"High","min_gts_temp":"-87","max_gts_temp":"-10"},{"id":"488","terrestrial_date":"2014-02-19","sol":"547","ls":"91","season":"Month 4","min_temp":"-85","max_temp":"-29","pressure":"853","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"17:35","local_uv_irradiance_index":"High","min_gts_temp":"-89","max_gts_temp":"-10"},{"id":"489","terrestrial_date":"2014-02-18","sol":"546","ls":"91","season":"Month 4","min_temp":"-85","max_temp":"-34","pressure":"855","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"17:35","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-13"},{"id":"490","terrestrial_date":"2014-02-17","sol":"545","ls":"90","season":"Month 4","min_temp":"-85","max_temp":"-29","pressure":"856","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"17:35","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-14"},{"id":"487","terrestrial_date":"2014-02-16","sol":"544","ls":"90","season":"Month 4","min_temp":"-86","max_temp":"-27","pressure":"857","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"17:36","local_uv_irradiance_index":"Moderate","min_gts_temp":"-84","max_gts_temp":"-16"},{"id":"486","terrestrial_date":"2014-02-15","sol":"543","ls":"89","season":"Month 3","min_temp":"-84","max_temp":"-26","pressure":"858","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"17:36","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-16"},{"id":"477","terrestrial_date":"2014-02-13","sol":"542","ls":"89","season":"Month 3","min_temp":"-85","max_temp":"-28","pressure":"859","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"17:36","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-11"},{"id":"482","terrestrial_date":"2014-02-12","sol":"541","ls":"88","season":"Month 3","min_temp":"-84","max_temp":"-27","pressure":"861","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:54","sunset":"17:36","local_uv_irradiance_index":"Moderate","min_gts_temp":"-93","max_gts_temp":"-11"},{"id":"481","terrestrial_date":"2014-02-11","sol":"540","ls":"88","season":"Month 3","min_temp":"-84","max_temp":"-29","pressure":"862","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:54","sunset":"17:36","local_uv_irradiance_index":"Moderate","min_gts_temp":"-85","max_gts_temp":"-18"},{"id":"476","terrestrial_date":"2014-02-10","sol":"539","ls":"87","season":"Month 3","min_temp":"-85","max_temp":"-23","pressure":"864","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:54","sunset":"17:36","local_uv_irradiance_index":"Moderate","min_gts_temp":"-82","max_gts_temp":"-17"},{"id":"472","terrestrial_date":"2014-02-09","sol":"538","ls":"87","season":"Month 3","min_temp":"-85","max_temp":"-25","pressure":"865","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:54","sunset":"17:37","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-8"},{"id":"469","terrestrial_date":"2014-02-08","sol":"537","ls":"86","season":"Month 3","min_temp":"-83","max_temp":"-28","pressure":"865","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:54","sunset":"17:37","local_uv_irradiance_index":"Moderate","min_gts_temp":"-92","max_gts_temp":"-11"},{"id":"470","terrestrial_date":"2014-02-07","sol":"536","ls":"86","season":"Month 3","min_temp":"-83","max_temp":"-29","pressure":"867","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:54","sunset":"17:37","local_uv_irradiance_index":"Moderate","min_gts_temp":"-94","max_gts_temp":"-12"},{"id":"468","terrestrial_date":"2014-02-06","sol":"535","ls":"86","season":"Month 3","min_temp":"-88","max_temp":"-29","pressure":"868","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"17:37","local_uv_irradiance_index":"Moderate","min_gts_temp":"-99","max_gts_temp":"-7"},{"id":"467","terrestrial_date":"2014-02-05","sol":"534","ls":"85","season":"Month 3","min_temp":"-86","max_temp":"-29","pressure":"869","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"17:37","local_uv_irradiance_index":"Moderate","min_gts_temp":"-101","max_gts_temp":"-6"},{"id":"466","terrestrial_date":"2014-02-04","sol":"533","ls":"85","season":"Month 3","min_temp":"-87","max_temp":"-30","pressure":"871","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"17:37","local_uv_irradiance_index":"Moderate","min_gts_temp":"-98","max_gts_temp":"-7"},{"id":"464","terrestrial_date":"2014-02-03","sol":"532","ls":"84","season":"Month 3","min_temp":"-88","max_temp":"-23","pressure":"872","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"17:37","local_uv_irradiance_index":"Moderate","min_gts_temp":"-97","max_gts_temp":"-10"},{"id":"465","terrestrial_date":"2014-02-02","sol":"531","ls":"84","season":"Month 3","min_temp":"-87","max_temp":"-22","pressure":"872","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"17:38","local_uv_irradiance_index":"Moderate","min_gts_temp":"-99","max_gts_temp":"-12"},{"id":"463","terrestrial_date":"2014-02-01","sol":"530","ls":"83","season":"Month 3","min_temp":"-87","max_temp":"-28","pressure":"873","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"17:38","local_uv_irradiance_index":"Moderate","min_gts_temp":"-99","max_gts_temp":"-11"},{"id":"462","terrestrial_date":"2014-01-31","sol":"529","ls":"83","season":"Month 3","min_temp":"-87","max_temp":"-23","pressure":"875","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"17:38","local_uv_irradiance_index":"Moderate","min_gts_temp":"-100","max_gts_temp":"-11"},{"id":"461","terrestrial_date":"2014-01-30","sol":"528","ls":"82","season":"Month 3","min_temp":"-86","max_temp":"-26","pressure":"876","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:56","sunset":"17:38","local_uv_irradiance_index":"Moderate","min_gts_temp":"-93","max_gts_temp":"-9"},{"id":"460","terrestrial_date":"2014-01-29","sol":"527","ls":"82","season":"Month 3","min_temp":"-86","max_temp":"-23","pressure":"877","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:56","sunset":"17:38","local_uv_irradiance_index":"High","min_gts_temp":"-94","max_gts_temp":"-7"},{"id":"459","terrestrial_date":"2014-01-28","sol":"526","ls":"82","season":"Month 3","min_temp":"-87","max_temp":"-24","pressure":"878","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:56","sunset":"17:39","local_uv_irradiance_index":"Moderate","min_gts_temp":"-90","max_gts_temp":"-10"},{"id":"456","terrestrial_date":"2014-01-27","sol":"525","ls":"81","season":"Month 3","min_temp":"-87","max_temp":"-29","pressure":"878","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:56","sunset":"17:39","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-11"},{"id":"457","terrestrial_date":"2014-01-26","sol":"524","ls":"81","season":"Month 3","min_temp":"-85","max_temp":"-27","pressure":"880","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:56","sunset":"17:39","local_uv_irradiance_index":"Moderate","min_gts_temp":"-96","max_gts_temp":"-7"},{"id":"458","terrestrial_date":"2014-01-25","sol":"523","ls":"80","season":"Month 3","min_temp":"-85","max_temp":"-25","pressure":"881","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:56","sunset":"17:39","local_uv_irradiance_index":"Moderate","min_gts_temp":"-98","max_gts_temp":"-7"},{"id":"455","terrestrial_date":"2014-01-24","sol":"522","ls":"80","season":"Month 3","min_temp":"-85","max_temp":"-26","pressure":"881","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:57","sunset":"17:39","local_uv_irradiance_index":"Moderate","min_gts_temp":"-98","max_gts_temp":"-7"},{"id":"454","terrestrial_date":"2014-01-23","sol":"521","ls":"79","season":"Month 3","min_temp":"-87","max_temp":"-26","pressure":"882","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:57","sunset":"17:39","local_uv_irradiance_index":"Moderate","min_gts_temp":"-95","max_gts_temp":"-7"},{"id":"453","terrestrial_date":"2014-01-22","sol":"520","ls":"79","season":"Month 3","min_temp":"-86","max_temp":"-24","pressure":"884","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:57","sunset":"17:40","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-9"},{"id":"452","terrestrial_date":"2014-01-21","sol":"519","ls":"78","season":"Month 3","min_temp":"-86","max_temp":"-25","pressure":"884","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:57","sunset":"17:40","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-11"},{"id":"451","terrestrial_date":"2014-01-20","sol":"518","ls":"78","season":"Month 3","min_temp":"-85","max_temp":"-29","pressure":"885","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:57","sunset":"17:40","local_uv_irradiance_index":"Moderate","min_gts_temp":"-100","max_gts_temp":"-9"},{"id":"448","terrestrial_date":"2014-01-19","sol":"517","ls":"77","season":"Month 3","min_temp":"-86","max_temp":"-27","pressure":"885","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:57","sunset":"17:40","local_uv_irradiance_index":"Moderate","min_gts_temp":"-98","max_gts_temp":"-6"},{"id":"449","terrestrial_date":"2014-01-18","sol":"516","ls":"77","season":"Month 3","min_temp":"-86","max_temp":"-25","pressure":"888","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:58","sunset":"17:40","local_uv_irradiance_index":"Moderate","min_gts_temp":"-99","max_gts_temp":"-6"},{"id":"450","terrestrial_date":"2014-01-17","sol":"515","ls":"77","season":"Month 3","min_temp":"-86","max_temp":"-23","pressure":"888","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:58","sunset":"17:41","local_uv_irradiance_index":"Moderate","min_gts_temp":"-89","max_gts_temp":"-6"},{"id":"447","terrestrial_date":"2014-01-16","sol":"514","ls":"76","season":"Month 3","min_temp":"-86","max_temp":"-29","pressure":"888","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:58","sunset":"17:41","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-10"},{"id":"445","terrestrial_date":"2014-01-15","sol":"513","ls":"76","season":"Month 3","min_temp":"-86","max_temp":"-29","pressure":"889","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:58","sunset":"17:41","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-10"},{"id":"444","terrestrial_date":"2014-01-14","sol":"512","ls":"75","season":"Month 3","min_temp":"-86","max_temp":"-24","pressure":"890","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:58","sunset":"17:41","local_uv_irradiance_index":"Moderate","min_gts_temp":"-94","max_gts_temp":"-9"},{"id":"446","terrestrial_date":"2014-01-13","sol":"511","ls":"75","season":"Month 3","min_temp":"-85","max_temp":"-31","pressure":"892","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:58","sunset":"17:42","local_uv_irradiance_index":"Moderate","min_gts_temp":"-93","max_gts_temp":"-10"},{"id":"441","terrestrial_date":"2014-01-12","sol":"510","ls":"74","season":"Month 3","min_temp":"-85","max_temp":"-31","pressure":"892","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:59","sunset":"17:42","local_uv_irradiance_index":"Moderate","min_gts_temp":"-92","max_gts_temp":"-13"},{"id":"443","terrestrial_date":"2014-01-11","sol":"509","ls":"74","season":"Month 3","min_temp":"-86","max_temp":"-30","pressure":"895","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:59","sunset":"17:42","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-13"},{"id":"442","terrestrial_date":"2014-01-10","sol":"508","ls":"73","season":"Month 3","min_temp":"-83","max_temp":"-29","pressure":"893","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:59","sunset":"17:42","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-11"},{"id":"440","terrestrial_date":"2014-01-09","sol":"507","ls":"73","season":"Month 3","min_temp":"-85","max_temp":"-25","pressure":"894","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:59","sunset":"17:42","local_uv_irradiance_index":"Moderate","min_gts_temp":"-90","max_gts_temp":"-10"},{"id":"439","terrestrial_date":"2014-01-08","sol":"506","ls":"73","season":"Month 3","min_temp":"-86","max_temp":"-27","pressure":"894","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:59","sunset":"17:43","local_uv_irradiance_index":"Moderate","min_gts_temp":"-90","max_gts_temp":"-9"},{"id":"438","terrestrial_date":"2014-01-06","sol":"505","ls":"72","season":"Month 3","min_temp":"-85","max_temp":"-29","pressure":"895","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:00","sunset":"17:43","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-8"},{"id":"436","terrestrial_date":"2014-01-05","sol":"504","ls":"72","season":"Month 3","min_temp":"-85","max_temp":"-29","pressure":"895","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:00","sunset":"17:43","local_uv_irradiance_index":"Moderate","min_gts_temp":"-90","max_gts_temp":"-9"},{"id":"483","terrestrial_date":"2014-01-04","sol":"503","ls":"71","season":"Month 3","min_temp":"-86","max_temp":"-28","pressure":"897","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:00","sunset":"17:43","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-10"},{"id":"437","terrestrial_date":"2014-01-03","sol":"502","ls":"71","season":"Month 3","min_temp":"-87","max_temp":"-30","pressure":"898","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:00","sunset":"17:44","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-8"},{"id":"435","terrestrial_date":"2014-01-02","sol":"501","ls":"70","season":"Month 3","min_temp":"-86","max_temp":"-28","pressure":"898","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:00","sunset":"17:44","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-10"},{"id":"430","terrestrial_date":"2014-01-01","sol":"500","ls":"70","season":"Month 3","min_temp":"-85","max_temp":"-23","pressure":"897","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:01","sunset":"17:44","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-11"},{"id":"432","terrestrial_date":"2013-12-31","sol":"499","ls":"69","season":"Month 3","min_temp":"-84","max_temp":"-30","pressure":"899","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:01","sunset":"17:44","local_uv_irradiance_index":"Moderate","min_gts_temp":"-90","max_gts_temp":"-9"},{"id":"424","terrestrial_date":"2013-12-30","sol":"498","ls":"69","season":"Month 3","min_temp":"-86","max_temp":"-28","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:01","sunset":"17:45","local_uv_irradiance_index":"Moderate","min_gts_temp":"-90","max_gts_temp":"-10"},{"id":"425","terrestrial_date":"2013-12-29","sol":"497","ls":"69","season":"Month 3","min_temp":"-86","max_temp":"-30","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:01","sunset":"17:45","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-9"},{"id":"428","terrestrial_date":"2013-12-28","sol":"496","ls":"68","season":"Month 3","min_temp":"-85","max_temp":"-26","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:01","sunset":"17:45","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-10"},{"id":"431","terrestrial_date":"2013-12-27","sol":"495","ls":"68","season":"Month 3","min_temp":"-86","max_temp":"-26","pressure":"900","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:02","sunset":"17:45","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-10"},{"id":"429","terrestrial_date":"2013-12-26","sol":"494","ls":"67","season":"Month 3","min_temp":"-85","max_temp":"-24","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:02","sunset":"17:46","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-9"},{"id":"426","terrestrial_date":"2013-12-25","sol":"493","ls":"67","season":"Month 3","min_temp":"-84","max_temp":"-32","pressure":"902","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:02","sunset":"17:46","local_uv_irradiance_index":"Moderate","min_gts_temp":"-89","max_gts_temp":"-14"},{"id":"427","terrestrial_date":"2013-12-24","sol":"492","ls":"66","season":"Month 3","min_temp":"-86","max_temp":"-30","pressure":"903","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:02","sunset":"17:46","local_uv_irradiance_index":"Moderate","min_gts_temp":"-92","max_gts_temp":"-13"},{"id":"433","terrestrial_date":"2013-12-23","sol":"491","ls":"66","season":"Month 3","min_temp":"-86","max_temp":"-30","pressure":"903","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:02","sunset":"17:46","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-13"},{"id":"434","terrestrial_date":"2013-12-22","sol":"490","ls":"65","season":"Month 3","min_temp":"-87","max_temp":"-31","pressure":"903","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:03","sunset":"17:47","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-10"},{"id":"423","terrestrial_date":"2013-12-21","sol":"489","ls":"65","season":"Month 3","min_temp":"-85","max_temp":"-29","pressure":"904","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:03","sunset":"17:47","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-11"},{"id":"480","terrestrial_date":"2013-12-20","sol":"488","ls":"64","season":"Month 3","min_temp":"-87","max_temp":"-23","pressure":"903","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:03","sunset":"17:47","local_uv_irradiance_index":"Moderate","min_gts_temp":"-89","max_gts_temp":"-11"},{"id":"473","terrestrial_date":"2013-12-19","sol":"487","ls":"64","season":"Month 3","min_temp":"-85","max_temp":"-30","pressure":"904","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:03","sunset":"17:47","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-8"},{"id":"422","terrestrial_date":"2013-12-18","sol":"486","ls":"64","season":"Month 3","min_temp":"-84","max_temp":"-31","pressure":"904","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:03","sunset":"17:48","local_uv_irradiance_index":"Moderate","min_gts_temp":"-91","max_gts_temp":"-12"},{"id":"484","terrestrial_date":"2013-12-17","sol":"485","ls":"63","season":"Month 3","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:04","sunset":"17:48","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"485","terrestrial_date":"2013-12-10","sol":"478","ls":"60","season":"Month 3","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:05","sunset":"17:50","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"421","terrestrial_date":"2013-12-09","sol":"477","ls":"60","season":"Month 3","min_temp":"-85","max_temp":"-26","pressure":"907","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:05","sunset":"17:50","local_uv_irradiance_index":"Moderate","min_gts_temp":"-93","max_gts_temp":"-9"},{"id":"418","terrestrial_date":"2013-12-08","sol":"476","ls":"59","season":"Month 2","min_temp":"-85","max_temp":"-25","pressure":"906","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:06","sunset":"17:51","local_uv_irradiance_index":"Moderate","min_gts_temp":"-90","max_gts_temp":"-7"},{"id":"419","terrestrial_date":"2013-12-07","sol":"475","ls":"59","season":"Month 2","min_temp":"-85","max_temp":"-28","pressure":"906","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:06","sunset":"17:51","local_uv_irradiance_index":"Moderate","min_gts_temp":"-93","max_gts_temp":"-9"},{"id":"420","terrestrial_date":"2013-12-06","sol":"474","ls":"58","season":"Month 2","min_temp":"-84","max_temp":"-26","pressure":"906","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:06","sunset":"17:51","local_uv_irradiance_index":"Moderate","min_gts_temp":"-95","max_gts_temp":"-9"},{"id":"417","terrestrial_date":"2013-12-05","sol":"473","ls":"58","season":"Month 2","min_temp":"-86","max_temp":"-29","pressure":"907","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:06","sunset":"17:52","local_uv_irradiance_index":"Moderate","min_gts_temp":"-90","max_gts_temp":"-8"},{"id":"416","terrestrial_date":"2013-12-04","sol":"472","ls":"57","season":"Month 2","min_temp":"-84","max_temp":"-31","pressure":"907","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:07","sunset":"17:52","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-10"},{"id":"479","terrestrial_date":"2013-12-03","sol":"471","ls":"57","season":"Month 2","min_temp":"-84","max_temp":"-26","pressure":"906","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:07","sunset":"17:52","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-12"},{"id":"475","terrestrial_date":"2013-12-02","sol":"470","ls":"56","season":"Month 2","min_temp":"-85","max_temp":"-26","pressure":"907","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:07","sunset":"17:53","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-9"},{"id":"413","terrestrial_date":"2013-11-30","sol":"469","ls":"56","season":"Month 2","min_temp":"-85","max_temp":"-29","pressure":"908","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:07","sunset":"17:53","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-12"},{"id":"414","terrestrial_date":"2013-11-29","sol":"468","ls":"55","season":"Month 2","min_temp":"-85","max_temp":"-29","pressure":"907","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:07","sunset":"17:53","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-14"},{"id":"411","terrestrial_date":"2013-11-28","sol":"467","ls":"55","season":"Month 2","min_temp":"-84","max_temp":"-28","pressure":"907","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:08","sunset":"17:54","local_uv_irradiance_index":"Moderate","min_gts_temp":"-85","max_gts_temp":"-11"},{"id":"415","terrestrial_date":"2013-11-27","sol":"466","ls":"55","season":"Month 2","min_temp":"-85","max_temp":"-26","pressure":"907","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:08","sunset":"17:54","local_uv_irradiance_index":"Moderate","min_gts_temp":"-84","max_gts_temp":"-12"},{"id":"412","terrestrial_date":"2013-11-26","sol":"465","ls":"54","season":"Month 2","min_temp":"-85","max_temp":"-23","pressure":"906","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:08","sunset":"17:54","local_uv_irradiance_index":"High","min_gts_temp":"-85","max_gts_temp":"-13"},{"id":"410","terrestrial_date":"2013-11-25","sol":"464","ls":"54","season":"Month 2","min_temp":"-84","max_temp":"-26","pressure":"906","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:08","sunset":"17:55","local_uv_irradiance_index":"High","min_gts_temp":"-87","max_gts_temp":"-13"},{"id":"409","terrestrial_date":"2013-11-24","sol":"463","ls":"53","season":"Month 2","min_temp":"-86","max_temp":"-26","pressure":"906","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:09","sunset":"17:55","local_uv_irradiance_index":"High","min_gts_temp":"-88","max_gts_temp":"-13"},{"id":"408","terrestrial_date":"2013-11-23","sol":"462","ls":"53","season":"Month 2","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:09","sunset":"17:55","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"474","terrestrial_date":"2013-11-18","sol":"457","ls":"51","season":"Month 2","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:10","sunset":"17:57","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"406","terrestrial_date":"2013-11-17","sol":"456","ls":"50","season":"Month 2","min_temp":"-83","max_temp":"-26","pressure":"905","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:11","sunset":"17:57","local_uv_irradiance_index":"High","min_gts_temp":"-89","max_gts_temp":"-12"},{"id":"407","terrestrial_date":"2013-11-16","sol":"455","ls":"50","season":"Month 2","min_temp":"-84","max_temp":"-28","pressure":"905","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:11","sunset":"17:58","local_uv_irradiance_index":"Moderate","min_gts_temp":"-82","max_gts_temp":"-12"},{"id":"478","terrestrial_date":"2013-11-15","sol":"454","ls":"49","season":"Month 2","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:11","sunset":"17:58","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"471","terrestrial_date":"2013-11-05","sol":"444","ls":"45","season":"Month 2","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:14","sunset":"18:02","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"403","terrestrial_date":"2013-11-04","sol":"443","ls":"44","season":"Month 2","min_temp":"-81","max_temp":"-24","pressure":"901","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:14","sunset":"18:02","local_uv_irradiance_index":"Moderate","min_gts_temp":"-79","max_gts_temp":"-11"},{"id":"405","terrestrial_date":"2013-11-03","sol":"442","ls":"44","season":"Month 2","min_temp":"-80","max_temp":"-22","pressure":"900","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:14","sunset":"18:03","local_uv_irradiance_index":"Moderate","min_gts_temp":"-79","max_gts_temp":"-9"},{"id":"402","terrestrial_date":"2013-11-02","sol":"441","ls":"43","season":"Month 2","min_temp":"-81","max_temp":"-23","pressure":"900","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:15","sunset":"18:03","local_uv_irradiance_index":"Moderate","min_gts_temp":"-79","max_gts_temp":"-10"},{"id":"404","terrestrial_date":"2013-11-01","sol":"440","ls":"43","season":"Month 2","min_temp":"-80","max_temp":"-25","pressure":"899","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:15","sunset":"18:03","local_uv_irradiance_index":"Moderate","min_gts_temp":"-79","max_gts_temp":"-11"},{"id":"401","terrestrial_date":"2013-10-31","sol":"439","ls":"42","season":"Month 2","min_temp":"-80","max_temp":"-21","pressure":"899","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:15","sunset":"18:04","local_uv_irradiance_index":"Moderate","min_gts_temp":"-79","max_gts_temp":"-15"},{"id":"400","terrestrial_date":"2013-10-30","sol":"438","ls":"42","season":"Month 2","min_temp":"-79","max_temp":"-26","pressure":"899","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:15","sunset":"18:04","local_uv_irradiance_index":"Moderate","min_gts_temp":"-84","max_gts_temp":"-14"},{"id":"399","terrestrial_date":"2013-10-29","sol":"437","ls":"41","season":"Month 2","min_temp":"-82","max_temp":"-27","pressure":"898","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:16","sunset":"18:05","local_uv_irradiance_index":"Moderate","min_gts_temp":"-89","max_gts_temp":"-10"},{"id":"398","terrestrial_date":"2013-10-28","sol":"436","ls":"41","season":"Month 2","min_temp":"-81","max_temp":"-24","pressure":"897","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:16","sunset":"18:05","local_uv_irradiance_index":"Moderate","min_gts_temp":"-81","max_gts_temp":"-7"},{"id":"396","terrestrial_date":"2013-10-27","sol":"435","ls":"40","season":"Month 2","min_temp":"-81","max_temp":"-24","pressure":"896","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:16","sunset":"18:05","local_uv_irradiance_index":"Moderate","min_gts_temp":"-82","max_gts_temp":"-9"},{"id":"397","terrestrial_date":"2013-10-26","sol":"434","ls":"40","season":"Month 2","min_temp":"-80","max_temp":"-25","pressure":"896","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:17","sunset":"18:06","local_uv_irradiance_index":"Moderate","min_gts_temp":"-82","max_gts_temp":"-11"},{"id":"394","terrestrial_date":"2013-10-24","sol":"433","ls":"40","season":"Month 2","min_temp":"-81","max_temp":"-26","pressure":"895","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:17","sunset":"18:06","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-9"},{"id":"395","terrestrial_date":"2013-10-23","sol":"432","ls":"39","season":"Month 2","min_temp":"-80","max_temp":"-26","pressure":"896","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:17","sunset":"18:06","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-9"},{"id":"389","terrestrial_date":"2013-10-22","sol":"431","ls":"39","season":"Month 2","min_temp":"-83","max_temp":"-24","pressure":"895","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:18","sunset":"18:07","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-9"},{"id":"390","terrestrial_date":"2013-10-21","sol":"430","ls":"38","season":"Month 2","min_temp":"-82","max_temp":"-24","pressure":"896","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:18","sunset":"18:07","local_uv_irradiance_index":"Moderate","min_gts_temp":"-88","max_gts_temp":"-5"},{"id":"387","terrestrial_date":"2013-10-20","sol":"429","ls":"38","season":"Month 2","min_temp":"-82","max_temp":"-21","pressure":"895","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:18","sunset":"18:08","local_uv_irradiance_index":"Moderate","min_gts_temp":"-86","max_gts_temp":"-5"},{"id":"391","terrestrial_date":"2013-10-19","sol":"428","ls":"37","season":"Month 2","min_temp":"-81","max_temp":"-23","pressure":"894","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:18","sunset":"18:08","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-6"},{"id":"392","terrestrial_date":"2013-10-18","sol":"427","ls":"37","season":"Month 2","min_temp":"-81","max_temp":"-21","pressure":"893","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:19","sunset":"18:09","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-4"},{"id":"388","terrestrial_date":"2013-10-17","sol":"426","ls":"36","season":"Month 2","min_temp":"-79","max_temp":"-22","pressure":"893","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:19","sunset":"18:09","local_uv_irradiance_index":"Moderate","min_gts_temp":"-89","max_gts_temp":"-5"},{"id":"393","terrestrial_date":"2013-10-16","sol":"425","ls":"36","season":"Month 2","min_temp":"-81","max_temp":"-21","pressure":"893","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:19","sunset":"18:09","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-5"},{"id":"386","terrestrial_date":"2013-10-15","sol":"424","ls":"35","season":"Month 2","min_temp":"-81","max_temp":"-22","pressure":"893","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:20","sunset":"18:10","local_uv_irradiance_index":"Moderate","min_gts_temp":"-82","max_gts_temp":"-4"},{"id":"383","terrestrial_date":"2013-10-14","sol":"423","ls":"35","season":"Month 2","min_temp":"-81","max_temp":"-24","pressure":"892","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:20","sunset":"18:10","local_uv_irradiance_index":"Moderate","min_gts_temp":"-81","max_gts_temp":"-11"},{"id":"384","terrestrial_date":"2013-10-13","sol":"422","ls":"34","season":"Month 2","min_temp":"-82","max_temp":"-20","pressure":"891","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:20","sunset":"18:11","local_uv_irradiance_index":"Moderate","min_gts_temp":"-85","max_gts_temp":"-7"},{"id":"382","terrestrial_date":"2013-10-12","sol":"421","ls":"34","season":"Month 2","min_temp":"-81","max_temp":"-24","pressure":"890","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:20","sunset":"18:11","local_uv_irradiance_index":"High","min_gts_temp":"-85","max_gts_temp":"-7"},{"id":"385","terrestrial_date":"2013-10-11","sol":"420","ls":"33","season":"Month 2","min_temp":"-80","max_temp":"-19","pressure":"892","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:21","sunset":"18:11","local_uv_irradiance_index":"High","min_gts_temp":"-86","max_gts_temp":"-7"},{"id":"381","terrestrial_date":"2013-10-10","sol":"419","ls":"33","season":"Month 2","min_temp":"-82","max_temp":"-24","pressure":"891","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:21","sunset":"18:12","local_uv_irradiance_index":"High","min_gts_temp":"-87","max_gts_temp":"-5"},{"id":"380","terrestrial_date":"2013-10-09","sol":"418","ls":"33","season":"Month 2","min_temp":"-79","max_temp":"-21","pressure":"891","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:21","sunset":"18:12","local_uv_irradiance_index":"High","min_gts_temp":"-87","max_gts_temp":"-3"},{"id":"379","terrestrial_date":"2013-10-08","sol":"417","ls":"32","season":"Month 2","min_temp":"-82","max_temp":"-21","pressure":"890","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:22","sunset":"18:13","local_uv_irradiance_index":"High","min_gts_temp":"-87","max_gts_temp":"-2"},{"id":"378","terrestrial_date":"2013-10-07","sol":"416","ls":"32","season":"Month 2","min_temp":"-79","max_temp":"-18","pressure":"889","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:22","sunset":"18:13","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"-6"},{"id":"377","terrestrial_date":"2013-10-06","sol":"415","ls":"31","season":"Month 2","min_temp":"-82","max_temp":"-19","pressure":"890","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:22","sunset":"18:14","local_uv_irradiance_index":"High","min_gts_temp":"-86","max_gts_temp":"-4"},{"id":"375","terrestrial_date":"2013-10-05","sol":"414","ls":"31","season":"Month 2","min_temp":"-79","max_temp":"-18","pressure":"889","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:23","sunset":"18:14","local_uv_irradiance_index":"High","min_gts_temp":"-87","max_gts_temp":"-4"},{"id":"376","terrestrial_date":"2013-10-04","sol":"413","ls":"30","season":"Month 2","min_temp":"-82","max_temp":"-22","pressure":"888","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:23","sunset":"18:14","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"-2"},{"id":"371","terrestrial_date":"2013-10-03","sol":"412","ls":"30","season":"Month 2","min_temp":"-80","max_temp":"-22","pressure":"888","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:23","sunset":"18:15","local_uv_irradiance_index":"Moderate","min_gts_temp":"-79","max_gts_temp":"-2"},{"id":"372","terrestrial_date":"2013-10-02","sol":"411","ls":"29","season":"Month 1","min_temp":"-82","max_temp":"-17","pressure":"887","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:24","sunset":"18:15","local_uv_irradiance_index":"Moderate","min_gts_temp":"-81","max_gts_temp":"-8"},{"id":"373","terrestrial_date":"2013-10-01","sol":"410","ls":"29","season":"Month 1","min_temp":"-81","max_temp":"-14","pressure":"888","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:24","sunset":"18:16","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"-7"},{"id":"368","terrestrial_date":"2013-09-30","sol":"409","ls":"28","season":"Month 1","min_temp":"-81","max_temp":"-17","pressure":"887","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:24","sunset":"18:16","local_uv_irradiance_index":"Moderate","min_gts_temp":"-95","max_gts_temp":"-2"},{"id":"369","terrestrial_date":"2013-09-29","sol":"408","ls":"28","season":"Month 1","min_temp":"-80","max_temp":"-16","pressure":"887","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:24","sunset":"18:17","local_uv_irradiance_index":"Moderate","min_gts_temp":"-95","max_gts_temp":"0"},{"id":"370","terrestrial_date":"2013-09-28","sol":"407","ls":"27","season":"Month 1","min_temp":"-81","max_temp":"-17","pressure":"887","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:25","sunset":"18:17","local_uv_irradiance_index":"Moderate","min_gts_temp":"-94","max_gts_temp":"0"},{"id":"374","terrestrial_date":"2013-09-27","sol":"406","ls":"27","season":"Month 1","min_temp":"-81","max_temp":"-15","pressure":"886","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:25","sunset":"18:17","local_uv_irradiance_index":"High","min_gts_temp":"-86","max_gts_temp":"1"},{"id":"366","terrestrial_date":"2013-09-26","sol":"405","ls":"26","season":"Month 1","min_temp":"-82","max_temp":"-25","pressure":"885","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:25","sunset":"18:18","local_uv_irradiance_index":"Moderate","min_gts_temp":"-87","max_gts_temp":"-6"},{"id":"367","terrestrial_date":"2013-09-25","sol":"404","ls":"26","season":"Month 1","min_temp":"-78","max_temp":"-20","pressure":"884","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:26","sunset":"18:18","local_uv_irradiance_index":"Moderate","min_gts_temp":"-85","max_gts_temp":"-6"},{"id":"364","terrestrial_date":"2013-09-24","sol":"403","ls":"25","season":"Month 1","min_temp":"-80","max_temp":"-18","pressure":"883","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:26","sunset":"18:19","local_uv_irradiance_index":"High","min_gts_temp":"-89","max_gts_temp":"-5"},{"id":"365","terrestrial_date":"2013-09-23","sol":"402","ls":"25","season":"Month 1","min_temp":"-78","max_temp":"-18","pressure":"883","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:26","sunset":"18:19","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"-3"},{"id":"362","terrestrial_date":"2013-09-22","sol":"401","ls":"25","season":"Month 1","min_temp":"-79","max_temp":"-15","pressure":"882","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:27","sunset":"18:20","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"-5"},{"id":"363","terrestrial_date":"2013-09-21","sol":"400","ls":"24","season":"Month 1","min_temp":"-79","max_temp":"-16","pressure":"881","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:27","sunset":"18:20","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"-6"},{"id":"361","terrestrial_date":"2013-09-20","sol":"399","ls":"24","season":"Month 1","min_temp":"-79","max_temp":"-16","pressure":"881","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:27","sunset":"18:20","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"-4"},{"id":"360","terrestrial_date":"2013-09-19","sol":"398","ls":"23","season":"Month 1","min_temp":"-79","max_temp":"-14","pressure":"880","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:28","sunset":"18:21","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"-6"},{"id":"359","terrestrial_date":"2013-09-18","sol":"397","ls":"23","season":"Month 1","min_temp":"-75","max_temp":"-15","pressure":"880","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:28","sunset":"18:21","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"-7"},{"id":"358","terrestrial_date":"2013-09-16","sol":"396","ls":"22","season":"Month 1","min_temp":"-78","max_temp":"-15","pressure":"879","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:28","sunset":"18:22","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"-5"},{"id":"357","terrestrial_date":"2013-09-15","sol":"395","ls":"22","season":"Month 1","min_temp":"-77","max_temp":"-20","pressure":"878","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:28","sunset":"18:22","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"-5"},{"id":"356","terrestrial_date":"2013-09-14","sol":"394","ls":"21","season":"Month 1","min_temp":"-79","max_temp":"-18","pressure":"878","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:29","sunset":"18:23","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"-4"},{"id":"355","terrestrial_date":"2013-09-13","sol":"393","ls":"21","season":"Month 1","min_temp":"-76","max_temp":"-18","pressure":"877","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:29","sunset":"18:23","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"-5"},{"id":"354","terrestrial_date":"2013-09-12","sol":"392","ls":"20","season":"Month 1","min_temp":"-77","max_temp":"-17","pressure":"876","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:29","sunset":"18:23","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"-4"},{"id":"353","terrestrial_date":"2013-09-11","sol":"391","ls":"20","season":"Month 1","min_temp":"-78","max_temp":"-17","pressure":"875","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:30","sunset":"18:24","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"-9"},{"id":"352","terrestrial_date":"2013-09-10","sol":"390","ls":"19","season":"Month 1","min_temp":"-78","max_temp":"-16","pressure":"875","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:30","sunset":"18:24","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"-1"},{"id":"348","terrestrial_date":"2013-09-09","sol":"389","ls":"19","season":"Month 1","min_temp":"-79","max_temp":"-14","pressure":"875","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:30","sunset":"18:25","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"-7"},{"id":"350","terrestrial_date":"2013-09-08","sol":"388","ls":"18","season":"Month 1","min_temp":"-79","max_temp":"-13","pressure":"875","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:31","sunset":"18:25","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"-6"},{"id":"349","terrestrial_date":"2013-09-07","sol":"387","ls":"18","season":"Month 1","min_temp":"-78","max_temp":"-13","pressure":"874","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:31","sunset":"18:26","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"-7"},{"id":"347","terrestrial_date":"2013-09-06","sol":"386","ls":"17","season":"Month 1","min_temp":"-77","max_temp":"-12","pressure":"873","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:31","sunset":"18:26","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"-7"},{"id":"351","terrestrial_date":"2013-09-05","sol":"385","ls":"17","season":"Month 1","min_temp":"-78","max_temp":"-12","pressure":"874","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:31","sunset":"18:26","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"-3"},{"id":"346","terrestrial_date":"2013-09-04","sol":"384","ls":"16","season":"Month 1","min_temp":"-77","max_temp":"-12","pressure":"875","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:32","sunset":"18:27","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"-4"},{"id":"342","terrestrial_date":"2013-09-03","sol":"383","ls":"16","season":"Month 1","min_temp":"-77","max_temp":"-12","pressure":"874","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:32","sunset":"18:27","local_uv_irradiance_index":"Moderate","min_gts_temp":"-81","max_gts_temp":"-6"},{"id":"344","terrestrial_date":"2013-09-02","sol":"382","ls":"15","season":"Month 1","min_temp":"-76","max_temp":"-14","pressure":"872","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:32","sunset":"18:28","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"-4"},{"id":"345","terrestrial_date":"2013-09-01","sol":"381","ls":"15","season":"Month 1","min_temp":"-79","max_temp":"-13","pressure":"871","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:33","sunset":"18:28","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"-4"},{"id":"343","terrestrial_date":"2013-08-31","sol":"380","ls":"14","season":"Month 1","min_temp":"-79","max_temp":"-13","pressure":"870","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:33","sunset":"18:29","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"-4"},{"id":"341","terrestrial_date":"2013-08-30","sol":"379","ls":"14","season":"Month 1","min_temp":"-79","max_temp":"-12","pressure":"870","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:33","sunset":"18:29","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"-2"},{"id":"339","terrestrial_date":"2013-08-29","sol":"378","ls":"13","season":"Month 1","min_temp":"-78","max_temp":"-18","pressure":"869","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:33","sunset":"18:29","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"-4"},{"id":"338","terrestrial_date":"2013-08-28","sol":"377","ls":"13","season":"Month 1","min_temp":"-78","max_temp":"-12","pressure":"870","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:34","sunset":"18:30","local_uv_irradiance_index":"High","min_gts_temp":"-86","max_gts_temp":"2"},{"id":"340","terrestrial_date":"2013-08-27","sol":"376","ls":"12","season":"Month 1","min_temp":"-78","max_temp":"-11","pressure":"870","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:34","sunset":"18:30","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"-2"},{"id":"336","terrestrial_date":"2013-08-26","sol":"375","ls":"12","season":"Month 1","min_temp":"-77","max_temp":"-9","pressure":"867","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:34","sunset":"18:31","local_uv_irradiance_index":"High","min_gts_temp":"-83","max_gts_temp":"-1"},{"id":"337","terrestrial_date":"2013-08-25","sol":"374","ls":"11","season":"Month 1","min_temp":"-79","max_temp":"-9","pressure":"866","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:35","sunset":"18:31","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"0"},{"id":"335","terrestrial_date":"2013-08-24","sol":"373","ls":"11","season":"Month 1","min_temp":"-79","max_temp":"-8","pressure":"866","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:35","sunset":"18:32","local_uv_irradiance_index":"High","min_gts_temp":"-85","max_gts_temp":"0"},{"id":"334","terrestrial_date":"2013-08-23","sol":"372","ls":"10","season":"Month 1","min_temp":"-76","max_temp":"-9","pressure":"866","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:35","sunset":"18:32","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"2"},{"id":"333","terrestrial_date":"2013-08-22","sol":"371","ls":"10","season":"Month 1","min_temp":"-77","max_temp":"-11","pressure":"865","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:35","sunset":"18:32","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"-3"},{"id":"332","terrestrial_date":"2013-08-21","sol":"370","ls":"9","season":"Month 1","min_temp":"-79","max_temp":"-9","pressure":"865","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:36","sunset":"18:33","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"-1"},{"id":"331","terrestrial_date":"2013-08-20","sol":"369","ls":"9","season":"Month 1","min_temp":"-75","max_temp":"-9","pressure":"865","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:36","sunset":"18:33","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"3"},{"id":"330","terrestrial_date":"2013-08-19","sol":"368","ls":"8","season":"Month 1","min_temp":"-77","max_temp":"-9","pressure":"863","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:36","sunset":"18:34","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"-3"},{"id":"328","terrestrial_date":"2013-08-18","sol":"367","ls":"8","season":"Month 1","min_temp":"-76","max_temp":"-9","pressure":"862","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:37","sunset":"18:34","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"-2"},{"id":"326","terrestrial_date":"2013-08-17","sol":"366","ls":"7","season":"Month 1","min_temp":"-76","max_temp":"-12","pressure":"861","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:37","sunset":"18:34","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"-3"},{"id":"329","terrestrial_date":"2013-08-16","sol":"365","ls":"7","season":"Month 1","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:37","sunset":"18:35","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"327","terrestrial_date":"2013-08-08","sol":"358","ls":"3","season":"Month 1","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:39","sunset":"18:38","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"325","terrestrial_date":"2013-08-07","sol":"357","ls":"3","season":"Month 1","min_temp":"-76","max_temp":"-7","pressure":"857","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:39","sunset":"18:38","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"-1"},{"id":"324","terrestrial_date":"2013-08-06","sol":"356","ls":"2","season":"Month 1","min_temp":"-77","max_temp":"-7","pressure":"856","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:39","sunset":"18:38","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"-1"},{"id":"321","terrestrial_date":"2013-08-05","sol":"355","ls":"2","season":"Month 1","min_temp":"-75","max_temp":"-7","pressure":"857","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:40","sunset":"18:39","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"0"},{"id":"322","terrestrial_date":"2013-08-04","sol":"354","ls":"1","season":"Month 1","min_temp":"-75","max_temp":"-13","pressure":"857","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:40","sunset":"18:39","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"-1"},{"id":"323","terrestrial_date":"2013-08-03","sol":"353","ls":"1","season":"Month 1","min_temp":"-75","max_temp":"-8","pressure":"855","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:40","sunset":"18:39","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"-4"},{"id":"319","terrestrial_date":"2013-08-02","sol":"352","ls":"0","season":"Month 1","min_temp":"-77","max_temp":"-9","pressure":"854","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:40","sunset":"18:40","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"-3"},{"id":"320","terrestrial_date":"2013-08-01","sol":"351","ls":"0","season":"Month 1","min_temp":"-75","max_temp":"-12","pressure":"853","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:41","sunset":"18:40","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"-4"},{"id":"318","terrestrial_date":"2013-07-31","sol":"350","ls":"359","season":"Month 12","min_temp":"-76","max_temp":"-6","pressure":"853","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:41","sunset":"18:41","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"0"},{"id":"316","terrestrial_date":"2013-07-30","sol":"349","ls":"359","season":"Month 12","min_temp":"-75","max_temp":"-12","pressure":"852","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:41","sunset":"18:41","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"-1"},{"id":"315","terrestrial_date":"2013-07-29","sol":"348","ls":"358","season":"Month 12","min_temp":"-74","max_temp":"-8","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:41","sunset":"18:41","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"1"},{"id":"313","terrestrial_date":"2013-07-28","sol":"347","ls":"358","season":"Month 12","min_temp":"-77","max_temp":"-9","pressure":"851","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:41","sunset":"18:42","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"2"},{"id":"314","terrestrial_date":"2013-07-27","sol":"346","ls":"357","season":"Month 12","min_temp":"-76","max_temp":"-7","pressure":"853","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:42","sunset":"18:42","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"1"},{"id":"312","terrestrial_date":"2013-07-26","sol":"345","ls":"357","season":"Month 12","min_temp":"-77","max_temp":"-7","pressure":"853","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:42","sunset":"18:42","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"3"},{"id":"317","terrestrial_date":"2013-07-25","sol":"344","ls":"356","season":"Month 12","min_temp":"-77","max_temp":"-6","pressure":"851","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:42","sunset":"18:43","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"2"},{"id":"311","terrestrial_date":"2013-07-24","sol":"343","ls":"356","season":"Month 12","min_temp":"-77","max_temp":"-11","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:42","sunset":"18:43","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"2"},{"id":"309","terrestrial_date":"2013-07-23","sol":"342","ls":"355","season":"Month 12","min_temp":"-76","max_temp":"-7","pressure":"851","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:42","sunset":"18:43","local_uv_irradiance_index":"High","min_gts_temp":"-84","max_gts_temp":"2"},{"id":"310","terrestrial_date":"2013-07-22","sol":"341","ls":"355","season":"Month 12","min_temp":"-77","max_temp":"-6","pressure":"852","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:43","sunset":"18:44","local_uv_irradiance_index":"High","min_gts_temp":"-83","max_gts_temp":"4"},{"id":"307","terrestrial_date":"2013-07-21","sol":"340","ls":"354","season":"Month 12","min_temp":"-76","max_temp":"-6","pressure":"851","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:43","sunset":"18:44","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"5"},{"id":"308","terrestrial_date":"2013-07-20","sol":"339","ls":"354","season":"Month 12","min_temp":"-76","max_temp":"-8","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:43","sunset":"18:44","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"3"},{"id":"306","terrestrial_date":"2013-07-19","sol":"338","ls":"353","season":"Month 12","min_temp":"-78","max_temp":"-7","pressure":"848","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:43","sunset":"18:45","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"3"},{"id":"304","terrestrial_date":"2013-07-18","sol":"337","ls":"353","season":"Month 12","min_temp":"-75","max_temp":"-7","pressure":"853","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:43","sunset":"18:45","local_uv_irradiance_index":"High","min_gts_temp":"-83","max_gts_temp":"3"},{"id":"305","terrestrial_date":"2013-07-17","sol":"336","ls":"352","season":"Month 12","min_temp":"-76","max_temp":"-8","pressure":"852","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:45","local_uv_irradiance_index":"High","min_gts_temp":"-83","max_gts_temp":"4"},{"id":"302","terrestrial_date":"2013-07-16","sol":"335","ls":"352","season":"Month 12","min_temp":"-76","max_temp":"-7","pressure":"852","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:46","local_uv_irradiance_index":"High","min_gts_temp":"-81","max_gts_temp":"5"},{"id":"301","terrestrial_date":"2013-07-15","sol":"334","ls":"351","season":"Month 12","min_temp":"-74","max_temp":"-6","pressure":"852","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:46","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"1"},{"id":"299","terrestrial_date":"2013-07-14","sol":"333","ls":"350","season":"Month 12","min_temp":"-75","max_temp":"-5","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:46","local_uv_irradiance_index":"High","min_gts_temp":"-83","max_gts_temp":"2"},{"id":"300","terrestrial_date":"2013-07-13","sol":"332","ls":"350","season":"Month 12","min_temp":"-76","max_temp":"-5","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:47","local_uv_irradiance_index":"High","min_gts_temp":"-83","max_gts_temp":"4"},{"id":"303","terrestrial_date":"2013-07-12","sol":"331","ls":"349","season":"Month 12","min_temp":"-74","max_temp":"-3","pressure":"848","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:47","local_uv_irradiance_index":"High","min_gts_temp":"-85","max_gts_temp":"6"},{"id":"297","terrestrial_date":"2013-07-11","sol":"330","ls":"349","season":"Month 12","min_temp":"-75","max_temp":"-5","pressure":"848","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:47","local_uv_irradiance_index":"High","min_gts_temp":"-85","max_gts_temp":"8"},{"id":"298","terrestrial_date":"2013-07-10","sol":"329","ls":"348","season":"Month 12","min_temp":"-74","max_temp":"-7","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:48","local_uv_irradiance_index":"High","min_gts_temp":"-87","max_gts_temp":"7"},{"id":"293","terrestrial_date":"2013-07-09","sol":"328","ls":"348","season":"Month 12","min_temp":"-75","max_temp":"-5","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:48","local_uv_irradiance_index":"High","min_gts_temp":"-86","max_gts_temp":"8"},{"id":"296","terrestrial_date":"2013-07-08","sol":"327","ls":"347","season":"Month 12","min_temp":"-74","max_temp":"-8","pressure":"848","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:48","local_uv_irradiance_index":"Very_High","min_gts_temp":"-79","max_gts_temp":"7"},{"id":"295","terrestrial_date":"2013-07-07","sol":"326","ls":"347","season":"Month 12","min_temp":"-72","max_temp":"-5","pressure":"848","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:48","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"5"},{"id":"294","terrestrial_date":"2013-07-06","sol":"325","ls":"346","season":"Month 12","min_temp":"-74","max_temp":"-8","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:49","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"8"},{"id":"292","terrestrial_date":"2013-07-04","sol":"324","ls":"346","season":"Month 12","min_temp":"-75","max_temp":"-8","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:49","local_uv_irradiance_index":"High","min_gts_temp":"-79","max_gts_temp":"8"},{"id":"290","terrestrial_date":"2013-07-03","sol":"323","ls":"345","season":"Month 12","min_temp":"-74","max_temp":"-6","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:49","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"4"},{"id":"291","terrestrial_date":"2013-07-02","sol":"322","ls":"345","season":"Month 12","min_temp":"-74","max_temp":"-5","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:50","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"3"},{"id":"289","terrestrial_date":"2013-07-01","sol":"321","ls":"344","season":"Month 12","min_temp":"-75","max_temp":"-5","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:50","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"3"},{"id":"269","terrestrial_date":"2013-06-30","sol":"320","ls":"344","season":"Month 12","min_temp":"-74","max_temp":"-4","pressure":"846","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:50","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"3"},{"id":"271","terrestrial_date":"2013-06-29","sol":"319","ls":"343","season":"Month 12","min_temp":"-73","max_temp":"-3","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:50","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"4"},{"id":"270","terrestrial_date":"2013-06-28","sol":"318","ls":"342","season":"Month 12","min_temp":"-74","max_temp":"-4","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:51","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"3"},{"id":"268","terrestrial_date":"2013-06-27","sol":"317","ls":"342","season":"Month 12","min_temp":"-72","max_temp":"-4","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:51","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"2"},{"id":"288","terrestrial_date":"2013-06-26","sol":"316","ls":"341","season":"Month 12","min_temp":"-74","max_temp":"-16","pressure":"851","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:51","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"2"},{"id":"273","terrestrial_date":"2013-06-25","sol":"315","ls":"341","season":"Month 12","min_temp":"-74","max_temp":"-10","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:51","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"3"},{"id":"272","terrestrial_date":"2013-06-24","sol":"314","ls":"340","season":"Month 12","min_temp":"-73","max_temp":"-11","pressure":"848","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:51","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"2"},{"id":"274","terrestrial_date":"2013-06-23","sol":"313","ls":"340","season":"Month 12","min_temp":"-77","max_temp":"-14","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:52","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"2"},{"id":"285","terrestrial_date":"2013-06-22","sol":"312","ls":"339","season":"Month 12","min_temp":"-71","max_temp":"-11","pressure":"845","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:52","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"-1"},{"id":"277","terrestrial_date":"2013-06-21","sol":"311","ls":"339","season":"Month 12","min_temp":"-74","max_temp":"-8","pressure":"846","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:52","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"-2"},{"id":"278","terrestrial_date":"2013-06-20","sol":"310","ls":"338","season":"Month 12","min_temp":"-71","max_temp":"-9","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:52","local_uv_irradiance_index":"High","min_gts_temp":"-69","max_gts_temp":"-2"},{"id":"284","terrestrial_date":"2013-06-19","sol":"309","ls":"338","season":"Month 12","min_temp":"-72","max_temp":"-7","pressure":"848","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:52","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"-2"},{"id":"282","terrestrial_date":"2013-06-18","sol":"308","ls":"337","season":"Month 12","min_temp":"-73","max_temp":"-5","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:53","local_uv_irradiance_index":"High","min_gts_temp":"-69","max_gts_temp":"-1"},{"id":"279","terrestrial_date":"2013-06-17","sol":"307","ls":"336","season":"Month 12","min_temp":"-72","max_temp":"-8","pressure":"851","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:53","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"-3"},{"id":"280","terrestrial_date":"2013-06-16","sol":"306","ls":"336","season":"Month 12","min_temp":"-71","max_temp":"-8","pressure":"848","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:53","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"2"},{"id":"275","terrestrial_date":"2013-06-15","sol":"305","ls":"335","season":"Month 12","min_temp":"-71","max_temp":"-5","pressure":"847","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:53","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"1"},{"id":"281","terrestrial_date":"2013-06-14","sol":"304","ls":"335","season":"Month 12","min_temp":"-70","max_temp":"-6","pressure":"848","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:53","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"1"},{"id":"283","terrestrial_date":"2013-06-13","sol":"303","ls":"334","season":"Month 12","min_temp":"-71","max_temp":"-8","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:53","local_uv_irradiance_index":"High","min_gts_temp":"-74","max_gts_temp":"1"},{"id":"287","terrestrial_date":"2013-06-12","sol":"302","ls":"334","season":"Month 12","min_temp":"-71","max_temp":"-3","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-69","max_gts_temp":"1"},{"id":"276","terrestrial_date":"2013-06-11","sol":"301","ls":"333","season":"Month 12","min_temp":"-73","max_temp":"-6","pressure":"852","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"-4"},{"id":"286","terrestrial_date":"2013-06-10","sol":"300","ls":"333","season":"Month 12","min_temp":"-72","max_temp":"-8","pressure":"852","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"-2"},{"id":"267","terrestrial_date":"2013-06-09","sol":"299","ls":"332","season":"Month 12","min_temp":"-71","max_temp":"-8","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-75","max_gts_temp":"-1"},{"id":"266","terrestrial_date":"2013-06-08","sol":"298","ls":"331","season":"Month 12","min_temp":"-72","max_temp":"-3","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-76","max_gts_temp":"3"},{"id":"265","terrestrial_date":"2013-06-07","sol":"297","ls":"331","season":"Month 12","min_temp":"-72","max_temp":"-7","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:47","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"2"},{"id":"264","terrestrial_date":"2013-06-06","sol":"296","ls":"330","season":"Month 12","min_temp":"-71","max_temp":"-9","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"1"},{"id":"263","terrestrial_date":"2013-06-05","sol":"295","ls":"330","season":"Month 12","min_temp":"-71","max_temp":"-15","pressure":"853","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"-1"},{"id":"262","terrestrial_date":"2013-06-04","sol":"294","ls":"329","season":"Month 11","min_temp":"-72","max_temp":"-4","pressure":"852","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"-1"},{"id":"261","terrestrial_date":"2013-06-03","sol":"293","ls":"329","season":"Month 11","min_temp":"-72","max_temp":"-4","pressure":"850","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"0"},{"id":"260","terrestrial_date":"2013-06-02","sol":"292","ls":"328","season":"Month 11","min_temp":"-71","max_temp":"-1","pressure":"853","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"-1"},{"id":"259","terrestrial_date":"2013-06-01","sol":"291","ls":"327","season":"Month 11","min_temp":"-70","max_temp":"-4","pressure":"853","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"0"},{"id":"258","terrestrial_date":"2013-05-31","sol":"290","ls":"327","season":"Month 11","min_temp":"-71","max_temp":"-3","pressure":"853","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"0"},{"id":"257","terrestrial_date":"2013-05-30","sol":"289","ls":"326","season":"Month 11","min_temp":"-71","max_temp":"-3","pressure":"854","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"0"},{"id":"256","terrestrial_date":"2013-05-29","sol":"288","ls":"326","season":"Month 11","min_temp":"-69","max_temp":"-4","pressure":"854","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"0"},{"id":"255","terrestrial_date":"2013-05-27","sol":"287","ls":"325","season":"Month 11","min_temp":"-72","max_temp":"-3","pressure":"856","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-69","max_gts_temp":"-1"},{"id":"254","terrestrial_date":"2013-05-26","sol":"286","ls":"325","season":"Month 11","min_temp":"-71","max_temp":"-2","pressure":"854","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:46","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"-1"},{"id":"253","terrestrial_date":"2013-05-25","sol":"285","ls":"324","season":"Month 11","min_temp":"-70","max_temp":"-5","pressure":"852","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"0"},{"id":"252","terrestrial_date":"2013-05-24","sol":"284","ls":"323","season":"Month 11","min_temp":"-71","max_temp":"-3","pressure":"854","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"-1"},{"id":"251","terrestrial_date":"2013-05-23","sol":"283","ls":"323","season":"Month 11","min_temp":"-68","max_temp":"-4","pressure":"854","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"1"},{"id":"250","terrestrial_date":"2013-05-22","sol":"282","ls":"322","season":"Month 11","min_temp":"-72","max_temp":"-2","pressure":"856","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"-1"},{"id":"249","terrestrial_date":"2013-05-21","sol":"281","ls":"322","season":"Month 11","min_temp":"-68","max_temp":"-2","pressure":"855","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"0"},{"id":"248","terrestrial_date":"2013-05-20","sol":"280","ls":"321","season":"Month 11","min_temp":"-69","max_temp":"-4","pressure":"856","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"0"},{"id":"247","terrestrial_date":"2013-05-19","sol":"279","ls":"321","season":"Month 11","min_temp":"-68","max_temp":"-5","pressure":"856","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:45","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-68","max_gts_temp":"-1"},{"id":"246","terrestrial_date":"2013-05-18","sol":"278","ls":"320","season":"Month 11","min_temp":"-67","max_temp":"-3","pressure":"855","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-67","max_gts_temp":"-1"},{"id":"245","terrestrial_date":"2013-05-17","sol":"277","ls":"319","season":"Month 11","min_temp":"-69","max_temp":"-4","pressure":"857","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-69","max_gts_temp":"-1"},{"id":"244","terrestrial_date":"2013-05-16","sol":"276","ls":"319","season":"Month 11","min_temp":"-69","max_temp":"-4","pressure":"858","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:55","local_uv_irradiance_index":"Moderate","min_gts_temp":"-71","max_gts_temp":"0"},{"id":"169","terrestrial_date":"2013-05-15","sol":"275","ls":"318","season":"Month 11","min_temp":"-67","max_temp":"-6","pressure":"860","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-68","max_gts_temp":"-1"},{"id":"168","terrestrial_date":"2013-05-14","sol":"274","ls":"318","season":"Month 11","min_temp":"-70","max_temp":"-2","pressure":"861","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:44","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-68","max_gts_temp":"-1"},{"id":"167","terrestrial_date":"2013-05-13","sol":"273","ls":"317","season":"Month 11","min_temp":"-70","max_temp":"-4","pressure":"861","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:43","sunset":"18:55","local_uv_irradiance_index":"High","min_gts_temp":"-67","max_gts_temp":"-1"},{"id":"166","terrestrial_date":"2013-05-12","sol":"272","ls":"316","season":"Month 11","min_temp":"-71","max_temp":"-5","pressure":"864","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:43","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"-1"},{"id":"165","terrestrial_date":"2013-05-11","sol":"271","ls":"316","season":"Month 11","min_temp":"-69","max_temp":"-6","pressure":"864","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:43","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"2"},{"id":"164","terrestrial_date":"2013-05-10","sol":"270","ls":"315","season":"Month 11","min_temp":"-67","max_temp":"-6","pressure":"863","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:43","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"2"},{"id":"162","terrestrial_date":"2013-05-09","sol":"269","ls":"315","season":"Month 11","min_temp":"-72","max_temp":"-3","pressure":"866","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:42","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"2"},{"id":"161","terrestrial_date":"2013-05-08","sol":"268","ls":"314","season":"Month 11","min_temp":"-70","max_temp":"-5","pressure":"864","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:42","sunset":"18:54","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"2"},{"id":"160","terrestrial_date":"2013-05-07","sol":"267","ls":"313","season":"Month 11","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:42","sunset":"18:54","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"159","terrestrial_date":"2013-05-02","sol":"262","ls":"311","season":"Month 11","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:40","sunset":"18:53","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"158","terrestrial_date":"2013-05-01","sol":"261","ls":"310","season":"Month 11","min_temp":"-70","max_temp":"-4","pressure":"868","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:40","sunset":"18:53","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"4"},{"id":"157","terrestrial_date":"2013-04-30","sol":"260","ls":"309","season":"Month 11","min_temp":"-70","max_temp":"-11","pressure":"875","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:40","sunset":"18:53","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"2"},{"id":"155","terrestrial_date":"2013-04-29","sol":"259","ls":"309","season":"Month 11","min_temp":"-72","max_temp":"-3","pressure":"871","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:39","sunset":"18:52","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"6"},{"id":"154","terrestrial_date":"2013-04-28","sol":"258","ls":"308","season":"Month 11","min_temp":"-71","max_temp":"-9","pressure":"869","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:39","sunset":"18:52","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"6"},{"id":"153","terrestrial_date":"2013-04-27","sol":"257","ls":"308","season":"Month 11","min_temp":"-70","max_temp":"-1","pressure":"871","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:39","sunset":"18:52","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"5"},{"id":"152","terrestrial_date":"2013-04-26","sol":"256","ls":"307","season":"Month 11","min_temp":"-70","max_temp":"-2","pressure":"871","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:38","sunset":"18:52","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"5"},{"id":"151","terrestrial_date":"2013-04-25","sol":"255","ls":"306","season":"Month 11","min_temp":"-69","max_temp":"-5","pressure":"877","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:38","sunset":"18:51","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"6"},{"id":"150","terrestrial_date":"2013-04-24","sol":"254","ls":"306","season":"Month 11","min_temp":"-70","max_temp":"-3","pressure":"871","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:37","sunset":"18:51","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"4"},{"id":"149","terrestrial_date":"2013-04-23","sol":"253","ls":"305","season":"Month 11","min_temp":"-71","max_temp":"-6","pressure":"877","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:37","sunset":"18:51","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"5"},{"id":"148","terrestrial_date":"2013-04-22","sol":"252","ls":"304","season":"Month 11","min_temp":"-71","max_temp":"-8","pressure":"874","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:37","sunset":"18:51","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"5"},{"id":"147","terrestrial_date":"2013-04-20","sol":"251","ls":"304","season":"Month 11","min_temp":"-71","max_temp":"-2","pressure":"876","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:36","sunset":"18:50","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"7"},{"id":"146","terrestrial_date":"2013-04-19","sol":"250","ls":"303","season":"Month 11","min_temp":"-70","max_temp":"-2","pressure":"877","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:36","sunset":"18:50","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"5"},{"id":"144","terrestrial_date":"2013-04-18","sol":"249","ls":"303","season":"Month 11","min_temp":"-69","max_temp":"-3","pressure":"873","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:35","sunset":"18:50","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"5"},{"id":"143","terrestrial_date":"2013-04-17","sol":"248","ls":"302","season":"Month 11","min_temp":"-70","max_temp":"-4","pressure":"877","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:35","sunset":"18:49","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"3"},{"id":"142","terrestrial_date":"2013-04-16","sol":"247","ls":"301","season":"Month 11","min_temp":"-71","max_temp":"-4","pressure":"873","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:35","sunset":"18:49","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"6"},{"id":"141","terrestrial_date":"2013-04-15","sol":"246","ls":"301","season":"Month 11","min_temp":"-73","max_temp":"-1","pressure":"878","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:34","sunset":"18:49","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"6"},{"id":"140","terrestrial_date":"2013-04-14","sol":"245","ls":"300","season":"Month 11","min_temp":"-70","max_temp":"-6","pressure":"879","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:34","sunset":"18:48","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"6"},{"id":"139","terrestrial_date":"2013-04-13","sol":"244","ls":"300","season":"Month 11","min_temp":"-69","max_temp":"-6","pressure":"878","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:33","sunset":"18:48","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"7"},{"id":"138","terrestrial_date":"2013-04-12","sol":"243","ls":"299","season":"Month 10","min_temp":"-71","max_temp":"-7","pressure":"884","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:33","sunset":"18:48","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"6"},{"id":"137","terrestrial_date":"2013-04-11","sol":"242","ls":"298","season":"Month 10","min_temp":"-70","max_temp":"-7","pressure":"881","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:32","sunset":"18:47","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"6"},{"id":"136","terrestrial_date":"2013-04-10","sol":"241","ls":"298","season":"Month 10","min_temp":"-72","max_temp":"0","pressure":"884","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:32","sunset":"18:47","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"5"},{"id":"135","terrestrial_date":"2013-04-09","sol":"240","ls":"297","season":"Month 10","min_temp":"-70","max_temp":"-6","pressure":"884","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:31","sunset":"18:47","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"5"},{"id":"133","terrestrial_date":"2013-04-08","sol":"239","ls":"297","season":"Month 10","min_temp":"-73","max_temp":"-1","pressure":"885","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:31","sunset":"18:46","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"5"},{"id":"132","terrestrial_date":"2013-04-07","sol":"238","ls":"296","season":"Month 10","min_temp":"-70","max_temp":"-2","pressure":"885","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:30","sunset":"18:46","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"7"},{"id":"131","terrestrial_date":"2013-04-06","sol":"237","ls":"295","season":"Month 10","min_temp":"-71","max_temp":"-5","pressure":"883","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:30","sunset":"18:45","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"6"},{"id":"130","terrestrial_date":"2013-04-05","sol":"236","ls":"295","season":"Month 10","min_temp":"-72","max_temp":"-3","pressure":"886","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:29","sunset":"18:45","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"6"},{"id":"129","terrestrial_date":"2013-04-04","sol":"235","ls":"294","season":"Month 10","min_temp":"-70","max_temp":"0","pressure":"886","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:29","sunset":"18:44","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"7"},{"id":"128","terrestrial_date":"2013-04-03","sol":"234","ls":"293","season":"Month 10","min_temp":"-69","max_temp":"-4","pressure":"890","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:28","sunset":"18:44","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"5"},{"id":"127","terrestrial_date":"2013-04-02","sol":"233","ls":"293","season":"Month 10","min_temp":"-69","max_temp":"-3","pressure":"889","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:28","sunset":"18:44","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"6"},{"id":"126","terrestrial_date":"2013-04-01","sol":"232","ls":"292","season":"Month 10","min_temp":"-69","max_temp":"-5","pressure":"888","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:27","sunset":"18:43","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"6"},{"id":"125","terrestrial_date":"2013-03-31","sol":"231","ls":"292","season":"Month 10","min_temp":"-71","max_temp":"-6","pressure":"890","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:26","sunset":"18:43","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"7"},{"id":"124","terrestrial_date":"2013-03-30","sol":"230","ls":"291","season":"Month 10","min_temp":"-69","max_temp":"0","pressure":"888","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:26","sunset":"18:42","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"7"},{"id":"122","terrestrial_date":"2013-03-29","sol":"229","ls":"290","season":"Month 10","min_temp":"-69","max_temp":"-4","pressure":"894","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:25","sunset":"18:42","local_uv_irradiance_index":"Very_High","min_gts_temp":"-70","max_gts_temp":"7"},{"id":"121","terrestrial_date":"2013-03-28","sol":"228","ls":"290","season":"Month 10","min_temp":"-71","max_temp":"-3","pressure":"894","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:25","sunset":"18:41","local_uv_irradiance_index":"Very_High","min_gts_temp":"-71","max_gts_temp":"7"},{"id":"120","terrestrial_date":"2013-03-27","sol":"227","ls":"289","season":"Month 10","min_temp":"-70","max_temp":"4","pressure":"892","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:24","sunset":"18:41","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"7"},{"id":"119","terrestrial_date":"2013-03-26","sol":"226","ls":"288","season":"Month 10","min_temp":"-70","max_temp":"1","pressure":"894","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:24","sunset":"18:40","local_uv_irradiance_index":"Very_High","min_gts_temp":"-71","max_gts_temp":"6"},{"id":"118","terrestrial_date":"2013-03-25","sol":"225","ls":"288","season":"Month 10","min_temp":"-71","max_temp":"-8","pressure":"894","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:23","sunset":"18:40","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"7"},{"id":"117","terrestrial_date":"2013-03-24","sol":"224","ls":"287","season":"Month 10","min_temp":"-69","max_temp":"-1","pressure":"894","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:22","sunset":"18:39","local_uv_irradiance_index":"Very_High","min_gts_temp":"-71","max_gts_temp":"6"},{"id":"116","terrestrial_date":"2013-03-23","sol":"223","ls":"287","season":"Month 10","min_temp":"-71","max_temp":"-5","pressure":"895","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:22","sunset":"18:39","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"6"},{"id":"115","terrestrial_date":"2013-03-22","sol":"222","ls":"286","season":"Month 10","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:21","sunset":"18:38","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"113","terrestrial_date":"2013-03-15","sol":"215","ls":"281","season":"Month 10","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:17","sunset":"18:34","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"111","terrestrial_date":"2013-02-27","sol":"200","ls":"272","season":"Month 10","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:06","sunset":"18:24","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"109","terrestrial_date":"2013-02-26","sol":"199","ls":"271","season":"Month 10","min_temp":"-66","max_temp":"0","pressure":"917","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:06","sunset":"18:24","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"4"},{"id":"108","terrestrial_date":"2013-02-25","sol":"198","ls":"271","season":"Month 10","min_temp":"-67","max_temp":"-2","pressure":"914","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:05","sunset":"18:23","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"6"},{"id":"107","terrestrial_date":"2013-02-24","sol":"197","ls":"270","season":"Month 10","min_temp":"-68","max_temp":"-3","pressure":"915","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:04","sunset":"18:22","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"5"},{"id":"106","terrestrial_date":"2013-02-23","sol":"196","ls":"269","season":"Month 9","min_temp":"-66","max_temp":"-3","pressure":"916","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:04","sunset":"18:21","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"7"},{"id":"105","terrestrial_date":"2013-02-22","sol":"195","ls":"269","season":"Month 9","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:03","sunset":"18:21","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"104","terrestrial_date":"2013-02-19","sol":"192","ls":"267","season":"Month 9","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:01","sunset":"18:19","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"103","terrestrial_date":"2013-02-18","sol":"191","ls":"266","season":"Month 9","min_temp":"-67","max_temp":"-3","pressure":"921","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"06:00","sunset":"18:18","local_uv_irradiance_index":"Very_High","min_gts_temp":"-71","max_gts_temp":"7"},{"id":"102","terrestrial_date":"2013-02-17","sol":"190","ls":"265","season":"Month 9","min_temp":"-66","max_temp":"-3","pressure":"917","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:59","sunset":"18:17","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"7"},{"id":"100","terrestrial_date":"2013-02-16","sol":"189","ls":"265","season":"Month 9","min_temp":"-68","max_temp":"-2","pressure":"920","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:59","sunset":"18:16","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"6"},{"id":"99","terrestrial_date":"2013-02-15","sol":"188","ls":"264","season":"Month 9","min_temp":"-68","max_temp":"-3","pressure":"920","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:58","sunset":"18:16","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"7"},{"id":"98","terrestrial_date":"2013-02-14","sol":"187","ls":"263","season":"Month 9","min_temp":"-67","max_temp":"-1","pressure":"921","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:57","sunset":"18:15","local_uv_irradiance_index":"High","min_gts_temp":"-73","max_gts_temp":"7"},{"id":"97","terrestrial_date":"2013-02-13","sol":"186","ls":"263","season":"Month 9","min_temp":"-66","max_temp":"-2","pressure":"922","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:57","sunset":"18:14","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"5"},{"id":"96","terrestrial_date":"2013-02-12","sol":"185","ls":"262","season":"Month 9","min_temp":"-67","max_temp":"-5","pressure":"923","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:56","sunset":"18:14","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"7"},{"id":"95","terrestrial_date":"2013-02-11","sol":"184","ls":"261","season":"Month 9","min_temp":"-67","max_temp":"0","pressure":"923","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"18:13","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"6"},{"id":"94","terrestrial_date":"2013-02-10","sol":"183","ls":"261","season":"Month 9","min_temp":"-67","max_temp":"-1","pressure":"921","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:55","sunset":"18:12","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"6"},{"id":"93","terrestrial_date":"2013-02-09","sol":"182","ls":"260","season":"Month 9","min_temp":"-67","max_temp":"0","pressure":"921","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:54","sunset":"18:11","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"7"},{"id":"92","terrestrial_date":"2013-02-08","sol":"181","ls":"260","season":"Month 9","min_temp":"-67","max_temp":"1","pressure":"918","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:53","sunset":"18:11","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"6"},{"id":"91","terrestrial_date":"2013-02-07","sol":"180","ls":"259","season":"Month 9","min_temp":"-67","max_temp":"-1","pressure":"919","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"18:10","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"6"},{"id":"89","terrestrial_date":"2013-02-06","sol":"179","ls":"258","season":"Month 9","min_temp":"-66","max_temp":"-7","pressure":"920","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:52","sunset":"18:09","local_uv_irradiance_index":"High","min_gts_temp":"-72","max_gts_temp":"5"},{"id":"88","terrestrial_date":"2013-02-04","sol":"178","ls":"258","season":"Month 9","min_temp":"-66","max_temp":"-2","pressure":"920","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:51","sunset":"18:09","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"6"},{"id":"87","terrestrial_date":"2013-02-03","sol":"177","ls":"257","season":"Month 9","min_temp":"-66","max_temp":"-1","pressure":"921","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:50","sunset":"18:08","local_uv_irradiance_index":"High","min_gts_temp":"-69","max_gts_temp":"5"},{"id":"86","terrestrial_date":"2013-02-02","sol":"176","ls":"256","season":"Month 9","min_temp":"-67","max_temp":"0","pressure":"920","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:50","sunset":"18:07","local_uv_irradiance_index":"High","min_gts_temp":"-69","max_gts_temp":"6"},{"id":"85","terrestrial_date":"2013-02-01","sol":"175","ls":"256","season":"Month 9","min_temp":"-68","max_temp":"-4","pressure":"921","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:49","sunset":"18:06","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"6"},{"id":"84","terrestrial_date":"2013-01-31","sol":"174","ls":"255","season":"Month 9","min_temp":"-67","max_temp":"0","pressure":"921","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:48","sunset":"18:06","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"7"},{"id":"83","terrestrial_date":"2013-01-30","sol":"173","ls":"254","season":"Month 9","min_temp":"-67","max_temp":"0","pressure":"920","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:48","sunset":"18:05","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"6"},{"id":"82","terrestrial_date":"2013-01-29","sol":"172","ls":"254","season":"Month 9","min_temp":"-67","max_temp":"0","pressure":"923","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:47","sunset":"18:04","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"6"},{"id":"81","terrestrial_date":"2013-01-28","sol":"171","ls":"253","season":"Month 9","min_temp":"-67","max_temp":"1","pressure":"925","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:46","sunset":"18:03","local_uv_irradiance_index":"High","min_gts_temp":"-71","max_gts_temp":"6"},{"id":"80","terrestrial_date":"2013-01-27","sol":"170","ls":"252","season":"Month 9","min_temp":"-66","max_temp":"0","pressure":"925","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:46","sunset":"18:03","local_uv_irradiance_index":"High","min_gts_temp":"-69","max_gts_temp":"5"},{"id":"78","terrestrial_date":"2013-01-26","sol":"169","ls":"252","season":"Month 9","min_temp":"-66","max_temp":"0","pressure":"922","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:45","sunset":"18:02","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"6"},{"id":"77","terrestrial_date":"2013-01-25","sol":"168","ls":"251","season":"Month 9","min_temp":"-66","max_temp":"1","pressure":"923","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:45","sunset":"18:01","local_uv_irradiance_index":"High","min_gts_temp":"-69","max_gts_temp":"7"},{"id":"76","terrestrial_date":"2013-01-24","sol":"167","ls":"250","season":"Month 9","min_temp":"-65","max_temp":"-3","pressure":"920","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:44","sunset":"18:01","local_uv_irradiance_index":"High","min_gts_temp":"-69","max_gts_temp":"6"},{"id":"75","terrestrial_date":"2013-01-23","sol":"166","ls":"250","season":"Month 9","min_temp":"-65","max_temp":"-5","pressure":"922","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:43","sunset":"18:00","local_uv_irradiance_index":"High","min_gts_temp":"-67","max_gts_temp":"4"},{"id":"74","terrestrial_date":"2013-01-22","sol":"165","ls":"249","season":"Month 9","min_temp":"-65","max_temp":"-3","pressure":"922","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:43","sunset":"17:59","local_uv_irradiance_index":"High","min_gts_temp":"-69","max_gts_temp":"4"},{"id":"73","terrestrial_date":"2013-01-21","sol":"164","ls":"248","season":"Month 9","min_temp":"-64","max_temp":"-1","pressure":"919","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:42","sunset":"17:59","local_uv_irradiance_index":"High","min_gts_temp":"-68","max_gts_temp":"7"},{"id":"72","terrestrial_date":"2013-01-20","sol":"163","ls":"248","season":"Month 9","min_temp":"-65","max_temp":"-1","pressure":"919","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:41","sunset":"17:58","local_uv_irradiance_index":"Very_High","min_gts_temp":"-71","max_gts_temp":"6"},{"id":"71","terrestrial_date":"2013-01-19","sol":"162","ls":"247","season":"Month 9","min_temp":"-65","max_temp":"-1","pressure":"919","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:41","sunset":"17:57","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"7"},{"id":"70","terrestrial_date":"2013-01-18","sol":"161","ls":"246","season":"Month 9","min_temp":"-65","max_temp":"-2","pressure":"919","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:40","sunset":"17:56","local_uv_irradiance_index":"Very_High","min_gts_temp":"-71","max_gts_temp":"5"},{"id":"69","terrestrial_date":"2013-01-17","sol":"160","ls":"246","season":"Month 9","min_temp":"-67","max_temp":"-3","pressure":"919","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:40","sunset":"17:56","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"6"},{"id":"67","terrestrial_date":"2013-01-16","sol":"159","ls":"245","season":"Month 9","min_temp":"-65","max_temp":"-4","pressure":"918","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:39","sunset":"17:55","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"5"},{"id":"66","terrestrial_date":"2013-01-15","sol":"158","ls":"245","season":"Month 9","min_temp":"-66","max_temp":"-3","pressure":"922","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:39","sunset":"17:54","local_uv_irradiance_index":"Very_High","min_gts_temp":"-70","max_gts_temp":"4"},{"id":"65","terrestrial_date":"2013-01-14","sol":"157","ls":"244","season":"Month 9","min_temp":"-64","max_temp":"-2","pressure":"920","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:38","sunset":"17:54","local_uv_irradiance_index":"Very_High","min_gts_temp":"-71","max_gts_temp":"5"},{"id":"64","terrestrial_date":"2013-01-13","sol":"156","ls":"243","season":"Month 9","min_temp":"-65","max_temp":"0","pressure":"922","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:37","sunset":"17:53","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"5"},{"id":"63","terrestrial_date":"2013-01-12","sol":"155","ls":"243","season":"Month 9","min_temp":"-64","max_temp":"0","pressure":"917","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:37","sunset":"17:52","local_uv_irradiance_index":"Very_High","min_gts_temp":"-70","max_gts_temp":"4"},{"id":"62","terrestrial_date":"2013-01-11","sol":"154","ls":"242","season":"Month 9","min_temp":"-65","max_temp":"-2","pressure":"917","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:36","sunset":"17:52","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"4"},{"id":"61","terrestrial_date":"2013-01-10","sol":"153","ls":"241","season":"Month 9","min_temp":"-65","max_temp":"1","pressure":"915","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:36","sunset":"17:51","local_uv_irradiance_index":"Very_High","min_gts_temp":"-70","max_gts_temp":"4"},{"id":"60","terrestrial_date":"2013-01-09","sol":"152","ls":"241","season":"Month 9","min_temp":"-63","max_temp":"1","pressure":"914","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:35","sunset":"17:51","local_uv_irradiance_index":"Very_High","min_gts_temp":"-69","max_gts_temp":"6"},{"id":"59","terrestrial_date":"2013-01-08","sol":"151","ls":"240","season":"Month 9","min_temp":"-65","max_temp":"-3","pressure":"915","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:35","sunset":"17:50","local_uv_irradiance_index":"Very_High","min_gts_temp":"-69","max_gts_temp":"5"},{"id":"58","terrestrial_date":"2013-01-07","sol":"150","ls":"239","season":"Month 8","min_temp":"-64","max_temp":"-3","pressure":"913","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:34","sunset":"17:49","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"6"},{"id":"56","terrestrial_date":"2013-01-06","sol":"149","ls":"239","season":"Month 8","min_temp":"-65","max_temp":"0","pressure":"914","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:34","sunset":"17:49","local_uv_irradiance_index":"Very_High","min_gts_temp":"-71","max_gts_temp":"4"},{"id":"55","terrestrial_date":"2013-01-05","sol":"148","ls":"238","season":"Month 8","min_temp":"-65","max_temp":"-3","pressure":"912","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:33","sunset":"17:48","local_uv_irradiance_index":"Very_High","min_gts_temp":"-70","max_gts_temp":"5"},{"id":"54","terrestrial_date":"2013-01-04","sol":"147","ls":"237","season":"Month 8","min_temp":"-65","max_temp":"0","pressure":"914","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:33","sunset":"17:47","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"6"},{"id":"53","terrestrial_date":"2013-01-03","sol":"146","ls":"237","season":"Month 8","min_temp":"-65","max_temp":"-1","pressure":"908","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:32","sunset":"17:47","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"8"},{"id":"52","terrestrial_date":"2013-01-02","sol":"145","ls":"236","season":"Month 8","min_temp":"-64","max_temp":"-1","pressure":"909","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:32","sunset":"17:46","local_uv_irradiance_index":"Very_High","min_gts_temp":"-70","max_gts_temp":"7"},{"id":"51","terrestrial_date":"2013-01-01","sol":"144","ls":"235","season":"Month 8","min_temp":"-64","max_temp":"2","pressure":"907","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:31","sunset":"17:46","local_uv_irradiance_index":"Very_High","min_gts_temp":"-71","max_gts_temp":"7"},{"id":"50","terrestrial_date":"2012-12-31","sol":"143","ls":"235","season":"Month 8","min_temp":"-63","max_temp":"-2","pressure":"908","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:31","sunset":"17:45","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"8"},{"id":"49","terrestrial_date":"2012-12-29","sol":"142","ls":"234","season":"Month 8","min_temp":"-65","max_temp":"0","pressure":"906","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:30","sunset":"17:44","local_uv_irradiance_index":"Very_High","min_gts_temp":"-73","max_gts_temp":"8"},{"id":"48","terrestrial_date":"2012-12-28","sol":"141","ls":"233","season":"Month 8","min_temp":"-64","max_temp":"-2","pressure":"904","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:30","sunset":"17:44","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"8"},{"id":"47","terrestrial_date":"2012-12-27","sol":"140","ls":"233","season":"Month 8","min_temp":"-66","max_temp":"-2","pressure":"903","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:29","sunset":"17:43","local_uv_irradiance_index":"Very_High","min_gts_temp":"-71","max_gts_temp":"8"},{"id":"45","terrestrial_date":"2012-12-26","sol":"139","ls":"232","season":"Month 8","min_temp":"-66","max_temp":"0","pressure":"899","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:29","sunset":"17:43","local_uv_irradiance_index":"Very_High","min_gts_temp":"-71","max_gts_temp":"9"},{"id":"44","terrestrial_date":"2012-12-25","sol":"138","ls":"232","season":"Month 8","min_temp":"-65","max_temp":"-1","pressure":"899","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:29","sunset":"17:42","local_uv_irradiance_index":"Very_High","min_gts_temp":"-71","max_gts_temp":"8"},{"id":"43","terrestrial_date":"2012-12-24","sol":"137","ls":"231","season":"Month 8","min_temp":"-64","max_temp":"-1","pressure":"896","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:28","sunset":"17:42","local_uv_irradiance_index":"Very_High","min_gts_temp":"-71","max_gts_temp":"8"},{"id":"42","terrestrial_date":"2012-12-23","sol":"136","ls":"230","season":"Month 8","min_temp":"-65","max_temp":"-1","pressure":"897","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:28","sunset":"17:41","local_uv_irradiance_index":"Very_High","min_gts_temp":"-74","max_gts_temp":"7"},{"id":"41","terrestrial_date":"2012-12-22","sol":"135","ls":"230","season":"Month 8","min_temp":"-65","max_temp":"-5","pressure":"894","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:27","sunset":"17:41","local_uv_irradiance_index":"Very_High","min_gts_temp":"-72","max_gts_temp":"8"},{"id":"40","terrestrial_date":"2012-12-21","sol":"134","ls":"229","season":"Month 8","min_temp":"-67","max_temp":"-1","pressure":"893","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:27","sunset":"17:40","local_uv_irradiance_index":"Very_High","min_gts_temp":"-74","max_gts_temp":"7"},{"id":"39","terrestrial_date":"2012-12-20","sol":"133","ls":"228","season":"Month 8","min_temp":"-65","max_temp":"1","pressure":"891","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:27","sunset":"17:39","local_uv_irradiance_index":"Very_High","min_gts_temp":"-69","max_gts_temp":"6"},{"id":"38","terrestrial_date":"2012-12-19","sol":"132","ls":"228","season":"Month 8","min_temp":"-65","max_temp":"-6","pressure":"890","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:26","sunset":"17:39","local_uv_irradiance_index":"Very_High","min_gts_temp":"-69","max_gts_temp":"5"},{"id":"37","terrestrial_date":"2012-12-18","sol":"131","ls":"227","season":"Month 8","min_temp":"-65","max_temp":"-8","pressure":"889","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:26","sunset":"17:38","local_uv_irradiance_index":"Very_High","min_gts_temp":"-70","max_gts_temp":"6"},{"id":"36","terrestrial_date":"2012-12-17","sol":"130","ls":"226","season":"Month 8","min_temp":"-65","max_temp":"-9","pressure":"888","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:26","sunset":"17:38","local_uv_irradiance_index":"High","min_gts_temp":"-70","max_gts_temp":"5"},{"id":"34","terrestrial_date":"2012-12-16","sol":"129","ls":"226","season":"Month 8","min_temp":"-65","max_temp":"-2","pressure":"886","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:25","sunset":"17:37","local_uv_irradiance_index":"High","min_gts_temp":"-69","max_gts_temp":"6"},{"id":"33","terrestrial_date":"2012-12-15","sol":"128","ls":"225","season":"Month 8","min_temp":"-67","max_temp":"-3","pressure":"883","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:25","sunset":"17:37","local_uv_irradiance_index":"High","min_gts_temp":"-69","max_gts_temp":"6"},{"id":"32","terrestrial_date":"2012-12-14","sol":"127","ls":"224","season":"Month 8","min_temp":"-67","max_temp":"-1","pressure":"884","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:25","sunset":"17:37","local_uv_irradiance_index":"Very_High","min_gts_temp":"-71","max_gts_temp":"7"},{"id":"31","terrestrial_date":"2012-12-13","sol":"126","ls":"224","season":"Month 8","min_temp":"-66","max_temp":"-4","pressure":"880","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:24","sunset":"17:36","local_uv_irradiance_index":"Very_High","min_gts_temp":"-71","max_gts_temp":"7"},{"id":"30","terrestrial_date":"2012-12-12","sol":"125","ls":"223","season":"Month 8","min_temp":"-68","max_temp":"-5","pressure":"880","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:24","sunset":"17:36","local_uv_irradiance_index":"Very_High","min_gts_temp":"-66","max_gts_temp":"7"},{"id":"29","terrestrial_date":"2012-12-11","sol":"124","ls":"223","season":"Month 8","min_temp":"-66","max_temp":"-5","pressure":"876","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:24","sunset":"17:35","local_uv_irradiance_index":"Very_High","min_gts_temp":"-63","max_gts_temp":"6"},{"id":"28","terrestrial_date":"2012-12-10","sol":"123","ls":"222","season":"Month 8","min_temp":"-66","max_temp":"-10","pressure":"875","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:23","sunset":"17:35","local_uv_irradiance_index":"High","min_gts_temp":"-62","max_gts_temp":"2"},{"id":"27","terrestrial_date":"2012-12-09","sol":"122","ls":"221","season":"Month 8","min_temp":"-65","max_temp":"-3","pressure":"869","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:23","sunset":"17:34","local_uv_irradiance_index":"Very_High","min_gts_temp":"-66","max_gts_temp":"3"},{"id":"26","terrestrial_date":"2012-12-08","sol":"121","ls":"221","season":"Month 8","min_temp":"-66","max_temp":"0","pressure":"869","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:23","sunset":"17:34","local_uv_irradiance_index":"High","min_gts_temp":"-68","max_gts_temp":"9"},{"id":"25","terrestrial_date":"2012-12-07","sol":"120","ls":"220","season":"Month 8","min_temp":"-67","max_temp":"-3","pressure":"867","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:33","local_uv_irradiance_index":"Very_High","min_gts_temp":"-79","max_gts_temp":"16"},{"id":"23","terrestrial_date":"2012-12-06","sol":"119","ls":"219","season":"Month 8","min_temp":"-66","max_temp":"-6","pressure":"866","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:33","local_uv_irradiance_index":"Very_High","min_gts_temp":"-78","max_gts_temp":"19"},{"id":"22","terrestrial_date":"2012-12-05","sol":"118","ls":"219","season":"Month 8","min_temp":"-65","max_temp":"-6","pressure":"864","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:33","local_uv_irradiance_index":"Very_High","min_gts_temp":"-77","max_gts_temp":"18"},{"id":"21","terrestrial_date":"2012-12-04","sol":"117","ls":"218","season":"Month 8","min_temp":"-66","max_temp":"-5","pressure":"861","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:32","local_uv_irradiance_index":"Very_High","min_gts_temp":"-76","max_gts_temp":"18"},{"id":"20","terrestrial_date":"2012-12-03","sol":"116","ls":"217","season":"Month 8","min_temp":"-67","max_temp":"-6","pressure":"859","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:32","local_uv_irradiance_index":"Very_High","min_gts_temp":"-78","max_gts_temp":"18"},{"id":"19","terrestrial_date":"2012-12-02","sol":"115","ls":"217","season":"Month 8","min_temp":"-66","max_temp":"-8","pressure":"857","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:31","local_uv_irradiance_index":"Very_High","min_gts_temp":"-78","max_gts_temp":"17"},{"id":"18","terrestrial_date":"2012-12-01","sol":"114","ls":"216","season":"Month 8","min_temp":"-69","max_temp":"-6","pressure":"857","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:31","local_uv_irradiance_index":"Very_High","min_gts_temp":"-78","max_gts_temp":"18"},{"id":"17","terrestrial_date":"2012-11-30","sol":"113","ls":"216","season":"Month 8","min_temp":"-66","max_temp":"-6","pressure":"857","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:31","local_uv_irradiance_index":"Very_High","min_gts_temp":"-77","max_gts_temp":"16"},{"id":"16","terrestrial_date":"2012-11-29","sol":"112","ls":"215","season":"Month 8","min_temp":"-65","max_temp":"-8","pressure":"852","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:30","local_uv_irradiance_index":"Very_High","min_gts_temp":"-79","max_gts_temp":"18"},{"id":"15","terrestrial_date":"2012-11-28","sol":"111","ls":"214","season":"Month 8","min_temp":"-66","max_temp":"-4","pressure":"849","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:30","local_uv_irradiance_index":"Very_High","min_gts_temp":"-77","max_gts_temp":"18"},{"id":"14","terrestrial_date":"2012-11-27","sol":"110","ls":"214","season":"Month 8","min_temp":"-65","max_temp":"-5","pressure":"848","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:30","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"17"},{"id":"12","terrestrial_date":"2012-11-26","sol":"109","ls":"213","season":"Month 8","min_temp":"-64","max_temp":"-3","pressure":"844","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:29","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"16"},{"id":"11","terrestrial_date":"2012-11-25","sol":"108","ls":"212","season":"Month 8","min_temp":"-65","max_temp":"-4","pressure":"845","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:29","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"17"},{"id":"10","terrestrial_date":"2012-11-24","sol":"107","ls":"212","season":"Month 8","min_temp":"-66","max_temp":"-5","pressure":"844","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:29","local_uv_irradiance_index":"High","min_gts_temp":"-80","max_gts_temp":"17"},{"id":"9","terrestrial_date":"2012-11-23","sol":"106","ls":"211","season":"Month 8","min_temp":"-66","max_temp":"-8","pressure":"841","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:28","local_uv_irradiance_index":"High","min_gts_temp":"-78","max_gts_temp":"17"},{"id":"8","terrestrial_date":"2012-11-21","sol":"105","ls":"211","season":"Month 8","min_temp":"-66","max_temp":"-6","pressure":"839","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:28","local_uv_irradiance_index":"High","min_gts_temp":"-77","max_gts_temp":"17"},{"id":"7","terrestrial_date":"2012-11-20","sol":"104","ls":"210","season":"Month 8","min_temp":"-67","max_temp":"-5","pressure":"838","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:28","local_uv_irradiance_index":"Very_High","min_gts_temp":"-78","max_gts_temp":"18"},{"id":"6","terrestrial_date":"2012-11-19","sol":"103","ls":"209","season":"Month 7","min_temp":"-66","max_temp":"-3","pressure":"836","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:27","local_uv_irradiance_index":"Very_High","min_gts_temp":"-80","max_gts_temp":"19"},{"id":"5","terrestrial_date":"2012-11-18","sol":"102","ls":"209","season":"Month 7","min_temp":"-67","max_temp":"-3","pressure":"833","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:27","local_uv_irradiance_index":"Very_High","min_gts_temp":"-75","max_gts_temp":"17"},{"id":"4","terrestrial_date":"2012-11-17","sol":"101","ls":"208","season":"Month 7","min_temp":"-65","max_temp":"-2","pressure":"830","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:27","local_uv_irradiance_index":"Very_High","min_gts_temp":"-77","max_gts_temp":"16"},{"id":"3","terrestrial_date":"2012-11-16","sol":"100","ls":"207","season":"Month 7","min_temp":"-66","max_temp":"-1","pressure":"829","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:26","local_uv_irradiance_index":"Very_High","min_gts_temp":"-77","max_gts_temp":"13"},{"id":"242","terrestrial_date":"2012-11-15","sol":"99","ls":"207","season":"Month 7","min_temp":"-68","max_temp":"-1","pressure":"829","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:26","local_uv_irradiance_index":"Very_High","min_gts_temp":"-76","max_gts_temp":"14"},{"id":"241","terrestrial_date":"2012-11-14","sol":"98","ls":"206","season":"Month 7","min_temp":"-68","max_temp":"-1","pressure":"828","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:26","local_uv_irradiance_index":"Very_High","min_gts_temp":"-79","max_gts_temp":"15"},{"id":"240","terrestrial_date":"2012-11-13","sol":"97","ls":"206","season":"Month 7","min_temp":"-68","max_temp":"-3","pressure":"828","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:26","local_uv_irradiance_index":"Very_High","min_gts_temp":"-79","max_gts_temp":"15"},{"id":"239","terrestrial_date":"2012-11-12","sol":"96","ls":"205","season":"Month 7","min_temp":"-71","max_temp":"2","pressure":"826","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:25","local_uv_irradiance_index":"Very_High","min_gts_temp":"-79","max_gts_temp":"15"},{"id":"238","terrestrial_date":"2012-11-11","sol":"95","ls":"204","season":"Month 7","min_temp":"-68","max_temp":"0","pressure":"822","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:25","local_uv_irradiance_index":"Very_High","min_gts_temp":"-79","max_gts_temp":"15"},{"id":"237","terrestrial_date":"2012-11-10","sol":"94","ls":"204","season":"Month 7","min_temp":"-70","max_temp":"0","pressure":"822","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:25","local_uv_irradiance_index":"Very_High","min_gts_temp":"-81","max_gts_temp":"16"},{"id":"236","terrestrial_date":"2012-11-09","sol":"93","ls":"203","season":"Month 7","min_temp":"-72","max_temp":"-1","pressure":"819","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:25","local_uv_irradiance_index":"Very_High","min_gts_temp":"-81","max_gts_temp":"16"},{"id":"235","terrestrial_date":"2012-11-08","sol":"92","ls":"202","season":"Month 7","min_temp":"-74","max_temp":"-1","pressure":"820","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:24","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"15"},{"id":"234","terrestrial_date":"2012-11-07","sol":"91","ls":"202","season":"Month 7","min_temp":"-74","max_temp":"-1","pressure":"817","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:24","local_uv_irradiance_index":"Very_High","min_gts_temp":"-81","max_gts_temp":"16"},{"id":"233","terrestrial_date":"2012-11-06","sol":"90","ls":"201","season":"Month 7","min_temp":"-71","max_temp":"0","pressure":"813","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:24","local_uv_irradiance_index":"Very_High","min_gts_temp":"-80","max_gts_temp":"16"},{"id":"231","terrestrial_date":"2012-11-05","sol":"89","ls":"201","season":"Month 7","min_temp":"-73","max_temp":"-1","pressure":"813","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:24","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"16"},{"id":"230","terrestrial_date":"2012-11-04","sol":"88","ls":"200","season":"Month 7","min_temp":"-70","max_temp":"-2","pressure":"811","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:24","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"15"},{"id":"229","terrestrial_date":"2012-11-03","sol":"87","ls":"199","season":"Month 7","min_temp":"-70","max_temp":"-2","pressure":"808","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:23","local_uv_irradiance_index":"Very_High","min_gts_temp":"-81","max_gts_temp":"15"},{"id":"228","terrestrial_date":"2012-11-02","sol":"86","ls":"199","season":"Month 7","min_temp":"-71","max_temp":"-4","pressure":"808","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:23","local_uv_irradiance_index":"Very_High","min_gts_temp":"-81","max_gts_temp":"15"},{"id":"227","terrestrial_date":"2012-11-01","sol":"85","ls":"198","season":"Month 7","min_temp":"-71","max_temp":"-1","pressure":"805","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:23","local_uv_irradiance_index":"Very_High","min_gts_temp":"-81","max_gts_temp":"15"},{"id":"226","terrestrial_date":"2012-10-31","sol":"84","ls":"198","season":"Month 7","min_temp":"-70","max_temp":"0","pressure":"801","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:23","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"15"},{"id":"225","terrestrial_date":"2012-10-30","sol":"83","ls":"197","season":"Month 7","min_temp":"-72","max_temp":"0","pressure":"801","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:23","local_uv_irradiance_index":"Very_High","min_gts_temp":"-83","max_gts_temp":"15"},{"id":"224","terrestrial_date":"2012-10-29","sol":"82","ls":"196","season":"Month 7","min_temp":"-72","max_temp":"0","pressure":"799","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:22","local_uv_irradiance_index":"Very_High","min_gts_temp":"-83","max_gts_temp":"14"},{"id":"223","terrestrial_date":"2012-10-28","sol":"81","ls":"196","season":"Month 7","min_temp":"-72","max_temp":"-2","pressure":"798","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:22","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"13"},{"id":"222","terrestrial_date":"2012-10-27","sol":"80","ls":"195","season":"Month 7","min_temp":"-70","max_temp":"-3","pressure":"796","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:22","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"14"},{"id":"221","terrestrial_date":"2012-10-26","sol":"79","ls":"195","season":"Month 7","min_temp":"-73","max_temp":"-1","pressure":"795","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:22","local_uv_irradiance_index":"Very_High","min_gts_temp":"-84","max_gts_temp":"14"},{"id":"220","terrestrial_date":"2012-10-25","sol":"78","ls":"194","season":"Month 7","min_temp":"-71","max_temp":"0","pressure":"793","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:22","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"14"},{"id":"219","terrestrial_date":"2012-10-24","sol":"77","ls":"193","season":"Month 7","min_temp":"-71","max_temp":"0","pressure":"792","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:22","local_uv_irradiance_index":"Very_High","min_gts_temp":"-83","max_gts_temp":"15"},{"id":"218","terrestrial_date":"2012-10-23","sol":"76","ls":"193","season":"Month 7","min_temp":"-73","max_temp":"-1","pressure":"792","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:22","local_uv_irradiance_index":"Very_High","min_gts_temp":"-83","max_gts_temp":"15"},{"id":"217","terrestrial_date":"2012-10-22","sol":"75","ls":"192","season":"Month 7","min_temp":"-73","max_temp":"-1","pressure":"791","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-84","max_gts_temp":"13"},{"id":"216","terrestrial_date":"2012-10-21","sol":"74","ls":"192","season":"Month 7","min_temp":"-72","max_temp":"-5","pressure":"790","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-83","max_gts_temp":"13"},{"id":"215","terrestrial_date":"2012-10-20","sol":"73","ls":"191","season":"Month 7","min_temp":"-70","max_temp":"0","pressure":"788","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-83","max_gts_temp":"13"},{"id":"214","terrestrial_date":"2012-10-19","sol":"72","ls":"190","season":"Month 7","min_temp":"-73","max_temp":"-2","pressure":"785","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"12"},{"id":"213","terrestrial_date":"2012-10-18","sol":"71","ls":"190","season":"Month 7","min_temp":"-71","max_temp":"-2","pressure":"784","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-81","max_gts_temp":"12"},{"id":"212","terrestrial_date":"2012-10-17","sol":"70","ls":"189","season":"Month 7","min_temp":"-72","max_temp":"-1","pressure":"783","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"13"},{"id":"211","terrestrial_date":"2012-10-15","sol":"69","ls":"189","season":"Month 7","min_temp":"-73","max_temp":"0","pressure":"778","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"High","min_gts_temp":"-82","max_gts_temp":"12"},{"id":"210","terrestrial_date":"2012-10-14","sol":"68","ls":"188","season":"Month 7","min_temp":"-71","max_temp":"-2","pressure":"781","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-80","max_gts_temp":"12"},{"id":"209","terrestrial_date":"2012-10-13","sol":"67","ls":"187","season":"Month 7","min_temp":"-73","max_temp":"-6","pressure":"780","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-81","max_gts_temp":"13"},{"id":"208","terrestrial_date":"2012-10-12","sol":"66","ls":"187","season":"Month 7","min_temp":"-73","max_temp":"-2","pressure":"778","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:18","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-83","max_gts_temp":"12"},{"id":"207","terrestrial_date":"2012-10-11","sol":"65","ls":"186","season":"Month 7","min_temp":"-72","max_temp":"-2","pressure":"777","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"12"},{"id":"206","terrestrial_date":"2012-10-10","sol":"64","ls":"186","season":"Month 7","min_temp":"-74","max_temp":"0","pressure":"776","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-83","max_gts_temp":"12"},{"id":"205","terrestrial_date":"2012-10-09","sol":"63","ls":"185","season":"Month 7","min_temp":"-73","max_temp":"0","pressure":"775","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-81","max_gts_temp":"12"},{"id":"204","terrestrial_date":"2012-10-08","sol":"62","ls":"184","season":"Month 7","min_temp":"-72","max_temp":"-1","pressure":"774","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"11"},{"id":"203","terrestrial_date":"2012-10-07","sol":"61","ls":"184","season":"Month 7","min_temp":"-72","max_temp":"-2","pressure":"772","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-83","max_gts_temp":"13"},{"id":"202","terrestrial_date":"2012-10-06","sol":"60","ls":"183","season":"Month 7","min_temp":"-75","max_temp":"-2","pressure":"772","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-84","max_gts_temp":"12"},{"id":"201","terrestrial_date":"2012-10-05","sol":"59","ls":"183","season":"Month 7","min_temp":"-76","max_temp":"-1","pressure":"771","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"11"},{"id":"200","terrestrial_date":"2012-10-04","sol":"58","ls":"182","season":"Month 7","min_temp":"-74","max_temp":"-3","pressure":"769","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"11"},{"id":"199","terrestrial_date":"2012-10-03","sol":"57","ls":"181","season":"Month 7","min_temp":"-73","max_temp":"-3","pressure":"769","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:19","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"12"},{"id":"198","terrestrial_date":"2012-10-02","sol":"56","ls":"181","season":"Month 7","min_temp":"-73","max_temp":"-4","pressure":"768","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-80","max_gts_temp":"10"},{"id":"197","terrestrial_date":"2012-10-01","sol":"55","ls":"180","season":"Month 7","min_temp":"-74","max_temp":"-2","pressure":"766","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-78","max_gts_temp":"11"},{"id":"196","terrestrial_date":"2012-09-30","sol":"54","ls":"180","season":"Month 7","min_temp":"-72","max_temp":"-9","pressure":"766","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-77","max_gts_temp":"10"},{"id":"195","terrestrial_date":"2012-09-29","sol":"53","ls":"179","season":"Month 6","min_temp":"-71","max_temp":"-5","pressure":"764","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-81","max_gts_temp":"11"},{"id":"194","terrestrial_date":"2012-09-28","sol":"52","ls":"179","season":"Month 6","min_temp":"-74","max_temp":"-7","pressure":"762","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-84","max_gts_temp":"12"},{"id":"193","terrestrial_date":"2012-09-27","sol":"51","ls":"178","season":"Month 6","min_temp":"-76","max_temp":"-7","pressure":"762","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-83","max_gts_temp":"13"},{"id":"192","terrestrial_date":"2012-09-26","sol":"50","ls":"177","season":"Month 6","min_temp":"-72","max_temp":"-10","pressure":"761","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:20","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-76","max_gts_temp":"11"},{"id":"191","terrestrial_date":"2012-09-25","sol":"49","ls":"177","season":"Month 6","min_temp":"-74","max_temp":"-10","pressure":"761","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-81","max_gts_temp":"10"},{"id":"190","terrestrial_date":"2012-09-24","sol":"48","ls":"176","season":"Month 6","min_temp":"-75","max_temp":"0","pressure":"759","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-84","max_gts_temp":"12"},{"id":"189","terrestrial_date":"2012-09-23","sol":"47","ls":"176","season":"Month 6","min_temp":"-75","max_temp":"-9","pressure":"758","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-83","max_gts_temp":"14"},{"id":"188","terrestrial_date":"2012-09-22","sol":"46","ls":"175","season":"Month 6","min_temp":"-74","max_temp":"-12","pressure":"758","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-83","max_gts_temp":"13"},{"id":"187","terrestrial_date":"2012-09-21","sol":"45","ls":"175","season":"Month 6","min_temp":"-74","max_temp":"-9","pressure":"758","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-87","max_gts_temp":"11"},{"id":"186","terrestrial_date":"2012-09-20","sol":"44","ls":"174","season":"Month 6","min_temp":"-75","max_temp":"-10","pressure":"757","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:21","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-86","max_gts_temp":"13"},{"id":"185","terrestrial_date":"2012-09-19","sol":"43","ls":"173","season":"Month 6","min_temp":"-74","max_temp":"-12","pressure":"756","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-80","max_gts_temp":"9"},{"id":"184","terrestrial_date":"2012-09-18","sol":"42","ls":"173","season":"Month 6","min_temp":"-75","max_temp":"-7","pressure":"754","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-80","max_gts_temp":"7"},{"id":"183","terrestrial_date":"2012-09-17","sol":"41","ls":"172","season":"Month 6","min_temp":"-75","max_temp":"-12","pressure":"753","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-79","max_gts_temp":"8"},{"id":"182","terrestrial_date":"2012-09-16","sol":"40","ls":"172","season":"Month 6","min_temp":"-75","max_temp":"-12","pressure":"753","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-79","max_gts_temp":"8"},{"id":"181","terrestrial_date":"2012-09-15","sol":"39","ls":"171","season":"Month 6","min_temp":"-75","max_temp":"-8","pressure":"751","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-79","max_gts_temp":"7"},{"id":"180","terrestrial_date":"2012-09-14","sol":"38","ls":"171","season":"Month 6","min_temp":"-73","max_temp":"-13","pressure":"750","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:22","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-78","max_gts_temp":"8"},{"id":"179","terrestrial_date":"2012-09-13","sol":"37","ls":"170","season":"Month 6","min_temp":"-73","max_temp":"0","pressure":"750","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:23","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-78","max_gts_temp":"7"},{"id":"178","terrestrial_date":"2012-09-12","sol":"36","ls":"169","season":"Month 6","min_temp":"-73","max_temp":"-1","pressure":"750","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:23","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-78","max_gts_temp":"7"},{"id":"177","terrestrial_date":"2012-09-11","sol":"35","ls":"169","season":"Month 6","min_temp":"-73","max_temp":"-1","pressure":"749","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:23","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-78","max_gts_temp":"6"},{"id":"176","terrestrial_date":"2012-09-10","sol":"34","ls":"168","season":"Month 6","min_temp":"-73","max_temp":"1","pressure":"748","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:23","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-78","max_gts_temp":"6"},{"id":"175","terrestrial_date":"2012-09-08","sol":"33","ls":"168","season":"Month 6","min_temp":"-73","max_temp":"-2","pressure":"748","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:23","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-80","max_gts_temp":"5"},{"id":"174","terrestrial_date":"2012-09-07","sol":"32","ls":"167","season":"Month 6","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:24","sunset":"17:20","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"173","terrestrial_date":"2012-09-06","sol":"31","ls":"167","season":"Month 6","min_temp":"-74","max_temp":"-23","pressure":"745","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:24","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-80","max_gts_temp":"2"},{"id":"172","terrestrial_date":"2012-09-05","sol":"30","ls":"166","season":"Month 6","min_temp":"-74","max_temp":"-3","pressure":"747","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:24","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-80","max_gts_temp":"6"},{"id":"171","terrestrial_date":"2012-09-04","sol":"29","ls":"166","season":"Month 6","min_temp":"-75","max_temp":"-2","pressure":"747","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:24","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-78","max_gts_temp":"7"},{"id":"170","terrestrial_date":"2012-09-03","sol":"28","ls":"165","season":"Month 6","min_temp":"-75","max_temp":"-15","pressure":"745","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:24","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-79","max_gts_temp":"5"},{"id":"163","terrestrial_date":"2012-09-02","sol":"27","ls":"164","season":"Month 6","min_temp":"-75","max_temp":"-15","pressure":"743","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:25","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-79","max_gts_temp":"5"},{"id":"156","terrestrial_date":"2012-09-01","sol":"26","ls":"164","season":"Month 6","min_temp":"-76","max_temp":"-14","pressure":"745","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:25","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-83","max_gts_temp":"6"},{"id":"145","terrestrial_date":"2012-08-31","sol":"25","ls":"163","season":"Month 6","min_temp":"-75","max_temp":"-11","pressure":"743","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:25","sunset":"17:20","local_uv_irradiance_index":"Very_High","min_gts_temp":"-79","max_gts_temp":"6"},{"id":"134","terrestrial_date":"2012-08-30","sol":"24","ls":"163","season":"Month 6","min_temp":"-75","max_temp":"-7","pressure":"742","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:25","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-83","max_gts_temp":"6"},{"id":"123","terrestrial_date":"2012-08-29","sol":"23","ls":"162","season":"Month 6","min_temp":"-75","max_temp":"-16","pressure":"741","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:25","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"9"},{"id":"114","terrestrial_date":"2012-08-28","sol":"22","ls":"162","season":"Month 6","min_temp":"-74","max_temp":"-6","pressure":"742","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:26","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-81","max_gts_temp":"8"},{"id":"112","terrestrial_date":"2012-08-27","sol":"21","ls":"161","season":"Month 6","min_temp":"-74","max_temp":"-3","pressure":"741","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:26","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-81","max_gts_temp":"7"},{"id":"110","terrestrial_date":"2012-08-26","sol":"20","ls":"161","season":"Month 6","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:26","sunset":"17:21","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"101","terrestrial_date":"2012-08-25","sol":"19","ls":"160","season":"Month 6","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:26","sunset":"17:21","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"90","terrestrial_date":"2012-08-24","sol":"18","ls":"160","season":"Month 6","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Higher","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:26","sunset":"17:21","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"79","terrestrial_date":"2012-08-23","sol":"17","ls":"159","season":"Month 6","min_temp":"-76","max_temp":"-4","pressure":"742","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:27","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-81","max_gts_temp":"7"},{"id":"68","terrestrial_date":"2012-08-22","sol":"16","ls":"158","season":"Month 6","min_temp":"-77","max_temp":"0","pressure":"740","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:27","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-81","max_gts_temp":"9"},{"id":"57","terrestrial_date":"2012-08-21","sol":"15","ls":"158","season":"Month 6","min_temp":"-78","max_temp":"-15","pressure":"740","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:27","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"8"},{"id":"46","terrestrial_date":"2012-08-20","sol":"14","ls":"157","season":"Month 6","min_temp":"-74","max_temp":"-16","pressure":"740","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:27","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"9"},{"id":"35","terrestrial_date":"2012-08-19","sol":"13","ls":"157","season":"Month 6","min_temp":"-74","max_temp":"-15","pressure":"732","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:28","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-80","max_gts_temp":"8"},{"id":"24","terrestrial_date":"2012-08-18","sol":"12","ls":"156","season":"Month 6","min_temp":"-76","max_temp":"-18","pressure":"741","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:28","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-82","max_gts_temp":"8"},{"id":"13","terrestrial_date":"2012-08-17","sol":"11","ls":"156","season":"Month 6","min_temp":"-76","max_temp":"-11","pressure":"740","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:28","sunset":"17:21","local_uv_irradiance_index":"Very_High","min_gts_temp":"-83","max_gts_temp":"9"},{"id":"2","terrestrial_date":"2012-08-16","sol":"10","ls":"155","season":"Month 6","min_temp":"-75","max_temp":"-16","pressure":"739","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:28","sunset":"17:22","local_uv_irradiance_index":"Very_High","min_gts_temp":"-83","max_gts_temp":"8"},{"id":"232","terrestrial_date":"2012-08-15","sol":"9","ls":"155","season":"Month 6","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:28","sunset":"17:22","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"},{"id":"1","terrestrial_date":"2012-08-07","sol":"1","ls":"150","season":"Month 6","min_temp":"--","max_temp":"--","pressure":"--","pressure_string":"Lower","abs_humidity":"--","wind_speed":"--","wind_direction":"--","atmo_opacity":"Sunny","sunrise":"05:30","sunset":"17:22","local_uv_irradiance_index":"--","min_gts_temp":"--","max_gts_temp":"--"}]} \ No newline at end of file diff --git a/example/msl/src/MSLDataDictionary.js b/example/msl/src/MSLDataDictionary.js deleted file mode 100644 index d5df9e657a..0000000000 --- a/example/msl/src/MSLDataDictionary.js +++ /dev/null @@ -1,78 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - /** - * A data dictionary describes the telemetry available from a data - * source and its data types. The data dictionary will be parsed by a custom - * server provider for this data source (in this case - * {@link RemsTelemetryServerAdapter}). - * - * Typically a data dictionary would be made available alongside the - * telemetry data source itself. - */ - function () { - return { - "name": "Mars Science Laboratory", - "identifier": "msl", - "instruments": [ - { - "name": "rems", - "identifier": "rems", - "measurements": [ - { - "name": "Min. Air Temperature", - "identifier": "min_temp", - "units": "Degrees (C)", - "type": "float" - }, - { - "name": "Max. Air Temperature", - "identifier": "max_temp", - "units": "Degrees (C)", - "type": "float" - }, - { - "name": "Atmospheric Pressure", - "identifier": "pressure", - "units": "Millibars", - "type": "float" - }, - { - "name": "Min. Ground Temperature", - "identifier": "min_gts_temp", - "units": "Degrees (C)", - "type": "float" - }, - { - "name": "Max. Ground Temperature", - "identifier": "max_gts_temp", - "units": "Degrees (C)", - "type": "float" - } - ] - } - ] - }; - } -); diff --git a/example/msl/src/RemsTelemetryModelProvider.js b/example/msl/src/RemsTelemetryModelProvider.js deleted file mode 100644 index 1542d5c84b..0000000000 --- a/example/msl/src/RemsTelemetryModelProvider.js +++ /dev/null @@ -1,96 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - function () { - "use strict"; - - var PREFIX = "msl_tlm:", - FORMAT_MAPPINGS = { - float: "number", - integer: "number", - string: "string" - }; - - function RemsTelemetryModelProvider(adapter) { - - function isRelevant(id) { - return id.indexOf(PREFIX) === 0; - } - - function makeId(element) { - return PREFIX + element.identifier; - } - - function buildTaxonomy(dictionary) { - var models = {}; - - function addMeasurement(measurement, parent) { - var format = FORMAT_MAPPINGS[measurement.type]; - models[makeId(measurement)] = { - type: "msl.measurement", - name: measurement.name, - location: parent, - telemetry: { - key: measurement.identifier, - ranges: [{ - key: "value", - name: measurement.units, - units: measurement.units, - format: format - }] - } - }; - } - - function addInstrument(subsystem, spacecraftId) { - var measurements = (subsystem.measurements || []), - instrumentId = makeId(subsystem); - - models[instrumentId] = { - type: "msl.instrument", - name: subsystem.name, - location: spacecraftId, - composition: measurements.map(makeId) - }; - measurements.forEach(function (measurement) { - addMeasurement(measurement, instrumentId); - }); - } - - (dictionary.instruments || []).forEach(function (instrument) { - addInstrument(instrument, "msl:curiosity"); - }); - - return models; - } - - return { - getModels: function (ids) { - return ids.some(isRelevant) ? buildTaxonomy(adapter.dictionary) : {}; - } - }; - } - - return RemsTelemetryModelProvider; - } -); diff --git a/example/msl/src/RemsTelemetryProvider.js b/example/msl/src/RemsTelemetryProvider.js deleted file mode 100644 index b4a64111a4..0000000000 --- a/example/msl/src/RemsTelemetryProvider.js +++ /dev/null @@ -1,83 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ -define ( - ['./RemsTelemetrySeries'], - function (RemsTelemetrySeries) { - "use strict"; - - var SOURCE = "rems.source"; - - function RemsTelemetryProvider(adapter, $q) { - this.adapter = adapter; - this.$q = $q; - } - - /** - * Retrieve telemetry from this telemetry source. - * @memberOf example/msl - * @param {Array} requests An array of all request - * objects (which needs to be filtered to only those relevant to this - * source) - * @returns {Promise} A {@link Promise} resolved with a {@link RemsTelemetrySeries} - * object that wraps the telemetry returned from the telemetry source. - */ - RemsTelemetryProvider.prototype.requestTelemetry = function (requests) { - var packaged = {}, - relevantReqs, - adapter = this.adapter; - - function matchesSource(request) { - return (request.source === SOURCE); - } - - function addToPackage(history) { - packaged[SOURCE][history.id] = - new RemsTelemetrySeries(history.values); - } - - function handleRequest(request) { - return adapter.history(request).then(addToPackage); - } - - relevantReqs = requests.filter(matchesSource); - packaged[SOURCE] = {}; - - return this.$q.all(relevantReqs.map(handleRequest)) - .then(function () { - return packaged; - }); - }; - - /** - * This data source does not support real-time subscriptions - */ - RemsTelemetryProvider.prototype.subscribe = function (callback, requests) { - return function () {}; - }; - - RemsTelemetryProvider.prototype.unsubscribe = function (callback, requests) { - return function () {}; - }; - - return RemsTelemetryProvider; - } -); diff --git a/example/msl/src/RemsTelemetrySeries.js b/example/msl/src/RemsTelemetrySeries.js deleted file mode 100644 index 6c01e56d13..0000000000 --- a/example/msl/src/RemsTelemetrySeries.js +++ /dev/null @@ -1,84 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ -define( - function () { - "use strict"; - - /** - * @typedef {Object} RemsTelemetryValue - * @memberOf example/msl - * @property {number} date The date/time of the telemetry value. Constitutes the domain value of this value pair - * @property {number} value The value of this telemetry datum. - * A floating point value representing some observable quantity (eg. - * temperature, air pressure, etc.) - */ - - /** - * A representation of a collection of telemetry data. The REMS - * telemetry data is time ordered, with the 'domain' value - * constituting the time stamp of each data value and the - * 'range' being the value itself. - * - * TelemetrySeries will typically wrap an array of telemetry data, - * and provide an interface for retrieving individual an telemetry - * value. - * @memberOf example/msl - * @param {Array} data An array of telemetry values - * @constructor - */ - function RemsTelemetrySeries(data) { - this.data = data; - } - - /** - * @returns {number} A count of the number of data values available in - * this series - */ - RemsTelemetrySeries.prototype.getPointCount = function () { - return this.data.length; - }; - - /** - * The domain value at the given index. The Rems telemetry data is - * time ordered, so the domain value is the time stamp of each data - * value. - * @param index - * @returns {number} the time value in ms since 1 January 1970 - */ - RemsTelemetrySeries.prototype.getDomainValue = function (index) { - return this.data[index].date; - }; - - /** - * The range value of the REMS data set is the value of the thing - * being measured, be it temperature, air pressure, etc. - * @param index The datum in the data series to return the range - * value of. - * @returns {number} A floating point number - */ - RemsTelemetrySeries.prototype.getRangeValue = function (index) { - return this.data[index].value; - }; - - return RemsTelemetrySeries; - } -); diff --git a/example/msl/src/RemsTelemetryServerAdapter.js b/example/msl/src/RemsTelemetryServerAdapter.js deleted file mode 100644 index 509661c9d9..0000000000 --- a/example/msl/src/RemsTelemetryServerAdapter.js +++ /dev/null @@ -1,145 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ -/*jslint es5: true */ - -define( - [ - "./MSLDataDictionary", - "module" - ], - function (MSLDataDictionary, module) { - "use strict"; - - var TERRESTRIAL_DATE = "terrestrial_date", - LOCAL_DATA = "../data/rems.json"; - - /** - * Fetches historical data from the REMS instrument on the Curiosity - * Rover. - * @memberOf example/msl - * @param $q - * @param $http - * @param REMS_WS_URL The location of the REMS telemetry data. - * @constructor - */ - function RemsTelemetryServerAdapter($http, $log, REMS_WS_URL) { - this.localDataURI = module.uri.substring(0, module.uri.lastIndexOf('/') + 1) + LOCAL_DATA; - this.REMS_WS_URL = REMS_WS_URL; - this.$http = $http; - this.$log = $log; - this.promise = undefined; - - this.dataTransforms = { - //Convert from pascals to millibars - 'pressure': function pascalsToMillibars(pascals) { - return pascals / 100; - } - }; - } - - /** - * The data dictionary for this data source. - * @type {MSLDataDictionary} - */ - RemsTelemetryServerAdapter.prototype.dictionary = MSLDataDictionary; - - /** - * Fetches historical data from source, and associates it with the - * given request ID. - * @private - */ - RemsTelemetryServerAdapter.prototype.requestHistory = function (request) { - var self = this, - id = request.key; - - var dataTransforms = this.dataTransforms; - - function processResponse(response) { - var data = []; - /* - * History data is organised by Sol. Iterate over sols... - */ - response.data.soles.forEach(function (solData) { - /* - * Check that valid data exists - */ - if (!isNaN(solData[id])) { - var dataTransform = dataTransforms[id]; - /* - * Append each data point to the array of values - * for this data point property (min. temp, etc). - */ - data.unshift({ - date: Date.parse(solData[TERRESTRIAL_DATE]), - value: dataTransform ? dataTransform(solData[id]) : solData[id] - }); - } - }); - - return data; - } - - function fallbackToLocal() { - self.$log.warn("Loading REMS data failed, probably due to" - + " cross origin policy. Falling back to local data"); - - return self.$http.get(self.localDataURI); - } - - //Filter results to match request parameters - function filterResults(results) { - return results.filter(function (result) { - return result.date >= (request.start || Number.MIN_VALUE) - && result.date <= (request.end || Number.MAX_VALUE); - }); - } - - function packageAndResolve(results) { - return { - id: id, - values: results - }; - } - - return (this.promise = this.promise || this.$http.get(this.REMS_WS_URL)) - .catch(fallbackToLocal) - .then(processResponse) - .then(filterResults) - .then(packageAndResolve); - }; - - /** - * Requests historical telemetry for the named data attribute. In - * the case of REMS, this data source exposes multiple different - * data variables from the REMS instrument, including temperature - * and others - * @param id The telemetry data point key to be queried. - * @returns {Promise | Array} that resolves with an Array of {@link RemsTelemetryValue} objects for the request data key. - */ - RemsTelemetryServerAdapter.prototype.history = function (request) { - return this.requestHistory(request); - }; - - return RemsTelemetryServerAdapter; - } -); - diff --git a/example/notifications/bundle.js b/example/notifications/bundle.js deleted file mode 100644 index 941c20fc13..0000000000 --- a/example/notifications/bundle.js +++ /dev/null @@ -1,90 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/DialogLaunchController", - "./src/NotificationLaunchController", - "./src/DialogLaunchIndicator", - "./src/NotificationLaunchIndicator", - "./res/dialog-launch.html", - "./res/notification-launch.html" -], function ( - DialogLaunchController, - NotificationLaunchController, - DialogLaunchIndicator, - NotificationLaunchIndicator, - DialogLaunch, - NotificationLaunch -) { - "use strict"; - - return { - name: "example/notifications", - definition: { - "extensions": { - "templates": [ - { - "key": "dialogLaunchTemplate", - "template": DialogLaunch - }, - { - "key": "notificationLaunchTemplate", - "template": NotificationLaunch - } - ], - "controllers": [ - { - "key": "DialogLaunchController", - "implementation": DialogLaunchController, - "depends": [ - "$scope", - "$timeout", - "$log", - "dialogService", - "notificationService" - ] - }, - { - "key": "NotificationLaunchController", - "implementation": NotificationLaunchController, - "depends": [ - "$scope", - "$timeout", - "$log", - "notificationService" - ] - } - ], - "indicators": [ - { - "implementation": DialogLaunchIndicator, - "priority": "fallback" - }, - { - "implementation": NotificationLaunchIndicator, - "priority": "fallback" - } - ] - } - } - }; -}); diff --git a/example/notifications/res/dialog-launch.html b/example/notifications/res/dialog-launch.html deleted file mode 100644 index f6b33d7b7c..0000000000 --- a/example/notifications/res/dialog-launch.html +++ /dev/null @@ -1,9 +0,0 @@ - - -
    - - - - -
    -
    diff --git a/example/notifications/res/notification-launch.html b/example/notifications/res/notification-launch.html deleted file mode 100644 index 11c66566e8..0000000000 --- a/example/notifications/res/notification-launch.html +++ /dev/null @@ -1,9 +0,0 @@ - - -
    - - - - -
    -
    diff --git a/example/notifications/src/DialogLaunchController.js b/example/notifications/src/DialogLaunchController.js deleted file mode 100644 index 4880f79aa2..0000000000 --- a/example/notifications/src/DialogLaunchController.js +++ /dev/null @@ -1,157 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - "use strict"; - - /** - * A controller for the dialog launch view. This view allows manual - * launching of dialogs for demonstration and testing purposes. It - * also demonstrates the use of the DialogService. - * @param $scope - * @param $timeout - * @param $log - * @param dialogService - * @param notificationService - * @constructor - */ - function DialogLaunchController($scope, $timeout, $log, dialogService, notificationService) { - - /* - Demonstrates launching a progress dialog and updating it - periodically with the progress of an ongoing process. - */ - $scope.launchProgress = function (knownProgress) { - var dialog, - model = { - title: "Progress Dialog Example", - progress: 0, - hint: "Do not navigate away from this page or close this browser tab while this operation is in progress.", - actionText: "Calculating...", - unknownProgress: !knownProgress, - unknownDuration: false, - severity: "info", - options: [ - { - label: "Cancel Operation", - callback: function () { - $log.debug("Operation cancelled"); - dialog.dismiss(); - } - }, - { - label: "Do something else...", - callback: function () { - $log.debug("Something else pressed"); - } - } - ] - }; - - function incrementProgress() { - model.progress = Math.min(100, Math.floor(model.progress + Math.random() * 30)); - model.progressText = ["Estimated time remaining: about ", 60 - Math.floor((model.progress / 100) * 60), " seconds"].join(" "); - if (model.progress < 100) { - $timeout(incrementProgress, 1000); - } - } - - dialog = dialogService.showBlockingMessage(model); - - if (dialog) { - //Do processing here - model.actionText = "Processing 100 objects..."; - if (knownProgress) { - $timeout(incrementProgress, 1000); - } - } else { - $log.error("Could not display modal dialog"); - } - }; - - /* - Demonstrates launching an error dialog - */ - $scope.launchError = function () { - var dialog, - model = { - title: "Error Dialog Example", - actionText: "Something happened, and it was not good.", - severity: "error", - options: [ - { - label: "Try Again", - callback: function () { - $log.debug("Try Again Pressed"); - dialog.dismiss(); - } - }, - { - label: "Cancel", - callback: function () { - $log.debug("Cancel Pressed"); - dialog.dismiss(); - } - } - ] - }; - dialog = dialogService.showBlockingMessage(model); - - if (!dialog) { - $log.error("Could not display modal dialog"); - } - }; - - /* - Demonstrates launching an error dialog - */ - $scope.launchInfo = function () { - var dialog, - model = { - title: "Info Dialog Example", - actionText: "This is an example of a blocking info" - + " dialog. This dialog can be used to draw the user's" - + " attention to an event.", - severity: "info", - primaryOption: { - label: "OK", - callback: function () { - $log.debug("OK Pressed"); - dialog.dismiss(); - } - } - }; - - dialog = dialogService.showBlockingMessage(model); - - if (!dialog) { - $log.error("Could not display modal dialog"); - } - }; - - } - - return DialogLaunchController; - } -); diff --git a/example/notifications/src/NotificationLaunchController.js b/example/notifications/src/NotificationLaunchController.js deleted file mode 100644 index 7cf41f4029..0000000000 --- a/example/notifications/src/NotificationLaunchController.js +++ /dev/null @@ -1,126 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - "use strict"; - - /** - * Allows launching of notification messages for the purposes of - * demonstration and testing. Also demonstrates use of - * the NotificationService. Notifications are non-blocking messages that - * appear at the bottom of the screen to inform the user of events - * in a non-intrusive way. For more information see the - * {@link NotificationService} - * @param $scope - * @param $timeout - * @param $log - * @param notificationService - * @constructor - */ - function NotificationLaunchController($scope, $timeout, $log, notificationService) { - var messageCounter = 1; - - function getExampleActionText() { - var actionTexts = [ - "Adipiscing turpis mauris in enim elementu hac, enim aliquam etiam.", - "Eros turpis, pulvinar turpis eros eu", - "Lundium nascetur a, lectus montes ac, parturient in natoque, duis risus risus pulvinar pid rhoncus, habitasse auctor natoque!" - ]; - - return actionTexts[Math.floor(Math.random() * 3)]; - } - - /** - * Launch a new notification with a severity level of 'Error'. - */ - $scope.newError = function () { - notificationService.notify({ - title: "Example error notification " + messageCounter++, - hint: "An error has occurred", - severity: "error" - }); - }; - - /** - * Launch a new notification with a severity of 'Alert'. - */ - $scope.newAlert = function () { - notificationService.notify({ - title: "Alert notification " + (messageCounter++), - hint: "This is an alert message", - severity: "alert", - autoDismiss: true - }); - }; - - /** - * Launch a new notification with a progress bar that is updated - * periodically, tracking an ongoing process. - */ - $scope.newProgress = function () { - let progress = 0; - var notificationModel = { - title: "Progress notification example", - severity: "info", - progress: progress, - actionText: getExampleActionText() - }; - let notification; - - /** - * Simulate an ongoing process and update the progress bar. - * @param notification - */ - function incrementProgress() { - progress = Math.min(100, Math.floor(progress + Math.random() * 30)); - let progressText = ["Estimated time" - + " remaining:" - + " about ", 60 - Math.floor((progress / 100) * 60), " seconds"].join(" "); - notification.progress(progress, progressText); - - if (progress < 100) { - $timeout(function () { - incrementProgress(notificationModel); - }, 1000); - } - } - - notification = notificationService.notify(notificationModel); - incrementProgress(); - }; - - /** - * Launch a new notification with severity level of INFO. - */ - $scope.newInfo = function () { - notificationService.info({ - title: "Example Info notification " + messageCounter++ - }); - }; - - } - - return NotificationLaunchController; - } -); diff --git a/example/notifications/src/NotificationLaunchIndicator.js b/example/notifications/src/NotificationLaunchIndicator.js deleted file mode 100644 index 3132edf427..0000000000 --- a/example/notifications/src/NotificationLaunchIndicator.js +++ /dev/null @@ -1,55 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - "use strict"; - - /** - * A tool for manually invoking notifications. When included this - * indicator will allow for notifications of different types to be - * launched for demonstration and testing purposes. - * @constructor - */ - - function NotificationLaunchIndicator() { - - } - - NotificationLaunchIndicator.template = 'notificationLaunchTemplate'; - - NotificationLaunchIndicator.prototype.getGlyphClass = function () { - return 'ok'; - }; - - NotificationLaunchIndicator.prototype.getText = function () { - return "Launch notification"; - }; - - NotificationLaunchIndicator.prototype.getDescription = function () { - return "Launch notification"; - }; - - return NotificationLaunchIndicator; - } -); diff --git a/example/persistence/bundle.js b/example/persistence/bundle.js deleted file mode 100644 index 7dac4ab22a..0000000000 --- a/example/persistence/bundle.js +++ /dev/null @@ -1,54 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/BrowserPersistenceProvider" -], function ( - BrowserPersistenceProvider -) { - "use strict"; - - return { - name: "example/persistence", - definition: { - "extensions": { - "components": [ - { - "provides": "persistenceService", - "type": "provider", - "implementation": BrowserPersistenceProvider, - "depends": [ - "$q", - "PERSISTENCE_SPACE" - ] - } - ], - "constants": [ - { - "key": "PERSISTENCE_SPACE", - "value": "mct" - } - ] - } - } - }; -}); diff --git a/example/persistence/src/BrowserPersistenceProvider.js b/example/persistence/src/BrowserPersistenceProvider.js deleted file mode 100644 index 12e88adc50..0000000000 --- a/example/persistence/src/BrowserPersistenceProvider.js +++ /dev/null @@ -1,102 +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. - *****************************************************************************/ - -/** - * Stubbed implementation of a persistence provider, - * to permit objects to be created, saved, etc. - */ -define( - [], - function () { - 'use strict'; - - function BrowserPersistenceProvider($q, SPACE) { - var spaces = SPACE ? [SPACE] : [], - caches = {}, - promises = { - as: function (value) { - return $q.when(value); - } - }; - - spaces.forEach(function (space) { - caches[space] = {}; - }); - - return { - listSpaces: function () { - return promises.as(spaces); - }, - listObjects: function (space) { - var cache = caches[space]; - - return promises.as( - cache ? Object.keys(cache) : null - ); - }, - createObject: function (space, key, value) { - var cache = caches[space]; - - if (!cache || cache[key]) { - return promises.as(null); - } - - cache[key] = value; - - return promises.as(true); - }, - readObject: function (space, key) { - var cache = caches[space]; - - return promises.as( - cache ? cache[key] : null - ); - }, - updateObject: function (space, key, value) { - var cache = caches[space]; - - if (!cache || !cache[key]) { - return promises.as(null); - } - - cache[key] = value; - - return promises.as(true); - }, - deleteObject: function (space, key, value) { - var cache = caches[space]; - - if (!cache || !cache[key]) { - return promises.as(null); - } - - delete cache[key]; - - return promises.as(true); - } - }; - - } - - return BrowserPersistenceProvider; - } -); diff --git a/example/policy/bundle.js b/example/policy/bundle.js deleted file mode 100644 index acceddf89a..0000000000 --- a/example/policy/bundle.js +++ /dev/null @@ -1,45 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/ExamplePolicy" -], function ( - ExamplePolicy -) { - "use strict"; - - return { - name: "example/policy", - definition: { - "name": "Example Policy", - "description": "Provides an example of using policies to prohibit actions.", - "extensions": { - "policies": [ - { - "implementation": ExamplePolicy, - "category": "action" - } - ] - } - } - }; -}); diff --git a/example/policy/src/ExamplePolicy.js b/example/policy/src/ExamplePolicy.js deleted file mode 100644 index c17cb21acc..0000000000 --- a/example/policy/src/ExamplePolicy.js +++ /dev/null @@ -1,47 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - "use strict"; - - function ExamplePolicy() { - return { - /** - * Disallow the Remove action on objects whose name contains - * "foo." - */ - allow: function (action, context) { - var domainObject = (context || {}).domainObject, - model = (domainObject && domainObject.getModel()) || {}, - name = model.name || "", - metadata = action.getMetadata() || {}; - - return metadata.key !== 'remove' || name.indexOf('foo') < 0; - } - }; - } - - return ExamplePolicy; - } -); diff --git a/example/profiling/src/DigestIndicator.js b/example/profiling/src/DigestIndicator.js deleted file mode 100644 index 80c6c9f9c2..0000000000 --- a/example/profiling/src/DigestIndicator.js +++ /dev/null @@ -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. - *****************************************************************************/ - -define( - [], - function () { - "use strict"; - - /** - * Displays the number of digests that have occurred since the - * indicator was first instantiated. - * @constructor - * @param $interval Angular's $interval - * @implements {Indicator} - */ - function DigestIndicator($interval, $rootScope) { - var digests = 0, - displayed = 0, - start = Date.now(); - - function update() { - var now = Date.now(), - secs = (now - start) / 1000; - displayed = Math.round(digests / secs); - start = now; - digests = 0; - } - - function increment() { - digests += 1; - } - - $rootScope.$watch(increment); - - // Update state every second - $interval(update, 1000); - - // Provide initial state, too - update(); - - return { - /** - * Get the CSS class that defines the icon - * to display in this indicator. This will appear - * as a dataflow icon. - * @returns {string} the cssClass of the dataflow icon - */ - getCssClass: function () { - return "icon-connectivity"; - }, - getText: function () { - return displayed + " digests/sec"; - }, - getDescription: function () { - return ""; - } - }; - } - - return DigestIndicator; - - } -); diff --git a/example/profiling/src/WatchIndicator.js b/example/profiling/src/WatchIndicator.js deleted file mode 100644 index 21579b5b74..0000000000 --- a/example/profiling/src/WatchIndicator.js +++ /dev/null @@ -1,86 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - "use strict"; - - /** - * Updates a count of currently-active Angular watches. - * @constructor - * @param $interval Angular's $interval - */ - function WatchIndicator($interval, $rootScope) { - var watches = 0; - - function count(scope) { - if (scope) { - watches += (scope.$$watchers || []).length; - count(scope.$$childHead); - count(scope.$$nextSibling); - } - } - - function update() { - watches = 0; - count($rootScope); - } - - // Update state every second - $interval(update, 1000); - - // Provide initial state, too - update(); - - return { - /** - * Get the CSS class (single character used as an icon) - * to display in this indicator. This will return ".", - * which should appear as a database icon. - * @returns {string} the character of the database icon - */ - getCssClass: function () { - return "icon-database"; - }, - /** - * Get the text that should appear in the indicator. - * @returns {string} brief summary of connection status - */ - getText: function () { - return watches + " watches"; - }, - /** - * Get a longer-form description of the current connection - * space, suitable for display in a tooltip - * @returns {string} longer summary of connection status - */ - getDescription: function () { - return ""; - } - }; - } - - return WatchIndicator; - - } -); diff --git a/example/scratchpad/README.md b/example/scratchpad/README.md deleted file mode 100644 index 624a4786b4..0000000000 --- a/example/scratchpad/README.md +++ /dev/null @@ -1,2 +0,0 @@ -Example of using multiple persistence stores by exposing a root -object with a different space prefix. diff --git a/example/scratchpad/bundle.js b/example/scratchpad/bundle.js deleted file mode 100644 index 1735b3d45e..0000000000 --- a/example/scratchpad/bundle.js +++ /dev/null @@ -1,63 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/ScratchPersistenceProvider" -], function ( - ScratchPersistenceProvider -) { - "use strict"; - - return { - name: "example/scratchpad", - definition: { - "extensions": { - "roots": [ - { - "id": "scratch:root" - } - ], - "models": [ - { - "id": "scratch:root", - "model": { - "type": "folder", - "composition": [], - "name": "Scratchpad" - }, - "priority": "preferred" - } - ], - "components": [ - { - "provides": "persistenceService", - "type": "provider", - "implementation": ScratchPersistenceProvider, - "depends": [ - "$q" - ] - } - ] - } - } - }; -}); diff --git a/example/scratchpad/src/ScratchPersistenceProvider.js b/example/scratchpad/src/ScratchPersistenceProvider.js deleted file mode 100644 index 241fb310de..0000000000 --- a/example/scratchpad/src/ScratchPersistenceProvider.js +++ /dev/null @@ -1,79 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - 'use strict'; - - /** - * The ScratchPersistenceProvider keeps JSON documents in memory - * and provides a persistence interface, but changes are lost on reload. - * @memberof example/scratchpad - * @constructor - * @implements {PersistenceService} - * @param q Angular's $q, for promises - */ - function ScratchPersistenceProvider($q) { - this.$q = $q; - this.table = {}; - } - - ScratchPersistenceProvider.prototype.listSpaces = function () { - return this.$q.when(['scratch']); - }; - - ScratchPersistenceProvider.prototype.listObjects = function (space) { - return this.$q.when( - space === 'scratch' ? Object.keys(this.table) : [] - ); - }; - - ScratchPersistenceProvider.prototype.createObject = function (space, key, value) { - if (space === 'scratch') { - this.table[key] = JSON.stringify(value); - } - - return this.$q.when(space === 'scratch'); - }; - - ScratchPersistenceProvider.prototype.readObject = function (space, key) { - return this.$q.when( - (space === 'scratch' && this.table[key]) - ? JSON.parse(this.table[key]) : undefined - ); - }; - - ScratchPersistenceProvider.prototype.deleteObject = function (space, key, value) { - if (space === 'scratch') { - delete this.table[key]; - } - - return this.$q.when(space === 'scratch'); - }; - - ScratchPersistenceProvider.prototype.updateObject = - ScratchPersistenceProvider.prototype.createObject; - - return ScratchPersistenceProvider; - } -); diff --git a/example/styleguide/bundle.js b/example/styleguide/bundle.js deleted file mode 100644 index e1af53082a..0000000000 --- a/example/styleguide/bundle.js +++ /dev/null @@ -1,188 +0,0 @@ -define([ - "./src/ExampleStyleGuideModelProvider", - "./src/MCTExample", - "./res/templates/intro.html", - "./res/templates/standards.html", - "./res/templates/colors.html", - "./res/templates/status.html", - "./res/templates/glyphs.html", - "./res/templates/controls.html", - "./res/templates/input.html", - "./res/templates/menus.html" -], function ( - ExampleStyleGuideModelProvider, - MCTExample, - introTemplate, - standardsTemplate, - colorsTemplate, - statusTemplate, - glyphsTemplate, - controlsTemplate, - inputTemplate, - menusTemplate -) { - return { - name: "example/styleguide", - definition: { - "name": "Open MCT Style Guide", - "description": "Examples and documentation illustrating UI styles in use in Open MCT.", - "extensions": - { - "types": [ - { - "key": "styleguide.intro", - "name": "Introduction", - "cssClass": "icon-page", - "description": "Introduction and overview to the style guide" - }, - { - "key": "styleguide.standards", - "name": "Standards", - "cssClass": "icon-page", - "description": "" - }, - { - "key": "styleguide.colors", - "name": "Colors", - "cssClass": "icon-page", - "description": "" - }, - { - "key": "styleguide.status", - "name": "status", - "cssClass": "icon-page", - "description": "Limits, telemetry paused, etc." - }, - { - "key": "styleguide.glyphs", - "name": "Glyphs", - "cssClass": "icon-page", - "description": "Glyphs overview" - }, - { - "key": "styleguide.controls", - "name": "Controls", - "cssClass": "icon-page", - "description": "Buttons, selects, HTML controls" - }, - { - "key": "styleguide.input", - "name": "Text Inputs", - "cssClass": "icon-page", - "description": "Various text inputs" - }, - { - "key": "styleguide.menus", - "name": "Menus", - "cssClass": "icon-page", - "description": "Context menus, dropdowns" - } - ], - "views": [ - { - "key": "styleguide.intro", - "type": "styleguide.intro", - "template": introTemplate, - "editable": false - }, - { - "key": "styleguide.standards", - "type": "styleguide.standards", - "template": standardsTemplate, - "editable": false - }, - { - "key": "styleguide.colors", - "type": "styleguide.colors", - "template": colorsTemplate, - "editable": false - }, - { - "key": "styleguide.status", - "type": "styleguide.status", - "template": statusTemplate, - "editable": false - }, - { - "key": "styleguide.glyphs", - "type": "styleguide.glyphs", - "template": glyphsTemplate, - "editable": false - }, - { - "key": "styleguide.controls", - "type": "styleguide.controls", - "template": controlsTemplate, - "editable": false - }, - { - "key": "styleguide.input", - "type": "styleguide.input", - "template": inputTemplate, - "editable": false - }, - { - "key": "styleguide.menus", - "type": "styleguide.menus", - "template": menusTemplate, - "editable": false - } - ], - "roots": [ - { - "id": "styleguide:home" - } - ], - "models": [ - { - "id": "styleguide:home", - "priority": "preferred", - "model": { - "type": "noneditable.folder", - "name": "Style Guide Home", - "location": "ROOT", - "composition": [ - "intro", - "standards", - "colors", - "status", - "glyphs", - "styleguide:ui-elements" - ] - } - }, - { - "id": "styleguide:ui-elements", - "priority": "preferred", - "model": { - "type": "noneditable.folder", - "name": "UI Elements", - "location": "styleguide:home", - "composition": [ - "controls", - "input", - "menus" - ] - } - } - ], - "directives": [ - { - "key": "mctExample", - "implementation": MCTExample - } - ], - "components": [ - { - "provides": "modelService", - "type": "provider", - "implementation": ExampleStyleGuideModelProvider, - "depends": [ - "$q" - ] - } - ] - } - } - }; -}); diff --git a/example/styleguide/res/images/diagram-containment.svg b/example/styleguide/res/images/diagram-containment.svg deleted file mode 100644 index a718ae33ac..0000000000 --- a/example/styleguide/res/images/diagram-containment.svg +++ /dev/null @@ -1 +0,0 @@ -dFolderActivity ModeTelemetry ElementClockTimerWeb PageDisplay LayoutTimelineFixedPacketActivityTablePlotCan contain their own type \ No newline at end of file diff --git a/example/styleguide/res/images/diagram-objects.svg b/example/styleguide/res/images/diagram-objects.svg deleted file mode 100644 index c457666dcf..0000000000 --- a/example/styleguide/res/images/diagram-objects.svg +++ /dev/null @@ -1 +0,0 @@ -Telemetry Elementdiagram-objectsDisplay LayoutPlotClockTable \ No newline at end of file diff --git a/example/styleguide/res/images/diagram-views.svg b/example/styleguide/res/images/diagram-views.svg deleted file mode 100644 index c62a2fc3a7..0000000000 --- a/example/styleguide/res/images/diagram-views.svg +++ /dev/null @@ -1 +0,0 @@ -objects-diagram \ No newline at end of file diff --git a/example/styleguide/res/templates/colors.html b/example/styleguide/res/templates/colors.html deleted file mode 100644 index 47631b0cff..0000000000 --- a/example/styleguide/res/templates/colors.html +++ /dev/null @@ -1,84 +0,0 @@ - -
    - - - -
    -

    Open MCT Style Guide

    -

    Colors

    - -
    -

    Overview

    -

    In mission operations, color is used to convey meaning. Alerts, warnings and status conditions are by convention communicated with colors in the green, yellow and red families. Colors must also be reserved for use in plots. As a result, Open MCT uses color selectively and sparingly. Follow these guidelines:

    -
      -
    • Don't use red, orange, yellow or green colors in any element that isn't conveying some kind of status information.
    • -
    • Each theme has a key color (typically blue-ish) that should be used to emphasize interactive elements and important UI controls.
    • -
    • Within each theme values are used to push elements back or bring them forward, lowering or raising them in visual importance. - In this theme, Espresso, lighter colors are placed on a dark background. The lighter a color is, the more it comes toward the observer and is raised in importance. - In this theme, Snow, darker colors are placed on a light background. The darker a color is, the more it comes toward the observer and is raised in importance. -
    • -
    • For consistency, use a theme's pre-defined status colors.
    • -
    -
    - -
    -

    {{ colorSet.category }}

    -

    {{ colorSet.description }}

    -
    -
    -
    -
    -
    -
    - - - - -
    Name{{color.name}}
    SASS{{color.constant}}
    Value - {{color.valEspresso}} - {{color.valSnow}} -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/example/styleguide/res/templates/controls.html b/example/styleguide/res/templates/controls.html deleted file mode 100644 index a29f82586d..0000000000 --- a/example/styleguide/res/templates/controls.html +++ /dev/null @@ -1,172 +0,0 @@ - -
    -

    Open MCT Style Guide

    -

    Controls

    - -
    -

    Standard Buttons

    -
    -
    -

    Use a standard button in locations where there's sufficient room and you must make it clear that the element is an interactive button element. Buttons can be displayed with only an icon, only text, or with icon and text combined.

    -

    Use an icon whenever possible to aid the user's recognition and recall. If both and icon and text are to be used, the text must be within a span with class .title-label.

    -
    - -Edit - - Edit - - -
    -
    - -
    -

    "Major" Buttons

    -
    -
    -

    Major buttons allow emphasis to be placed on a button. Use this on a single button when the user has a small number of choices, and one choice is a normal default. Just add .major to any element that uses .s-button.

    -
    -Ok -Cancel - -
    -
    - -
    -

    Button Sets

    -
    -
    -

    Use button sets to connect buttons that have related purpose or functionality. Buttons in a set round the outer corners of only the first and last buttons, any other buttons in the middle simply get division spacers.

    -

    To use, simply wrap two or more .s-button elements within .l-btn-set.

    -
    - - - - - - -
    -
    - -
    -

    Icon-only Buttons

    -
    -
    -

    When a button is presented within another control it may be advantageous to avoid visual clutter by using an icon-only button. These type of controls present an icon without the "base" of standard buttons. Icon-only buttons should only be used in a context where they are clearly an interactive element and not an object-type identifier, and should not be used with text.

    -
    - - -
    -
    - -
    -

    Checkboxes

    -
    -
    -

    Checkboxes use a combination of minimal additional markup with CSS to present a custom and common look-and-feel across platforms.

    -

    The basic structure is a label with a checkbox-type input and an em element inside. The em is needed as the holder of the custom element; the input itself is hidden. Putting everything inside the label allows the label itself to act as a clickable element.

    -
    - -
    - -
    -
    -
    -
    - -
    -

    Radio Buttons

    -
    -
    -

    Radio buttons use the same technique as checkboxes above.

    -
    - -
    - -
    - -
    -
    -
    - -
    -

    Selects

    -
    -
    -

    Similar to checkboxes and radio buttons, selects use a combination of minimal additional markup with CSS to present a custom and common look-and-feel across platforms. The select element is wrapped by another element, such as a div, which acts as the main display element for the styling. The select provides the click and select functionality, while having all of its native look-and-feel suppressed.

    -
    -
    - -
    -
    -
    -
    - -
    -

    Local Controls

    -
    -
    -

    Local controls are typically buttons and selects that provide actions in close proximity to a component.

    -

    These controls can optionally be hidden to reduce clutter until the user hovers their cursor over an enclosing element. To use this approach, apply the class .has-local-controls to the element that should be aware of the hover and ensure that element encloses .h-local-controls.

    -
    -
    - Some content in here -
    - - -
    -
    -
    - Hover here -
    - - -
    -
    -
    -
    - -
    diff --git a/example/styleguide/res/templates/glyphs.html b/example/styleguide/res/templates/glyphs.html deleted file mode 100644 index f55e333a65..0000000000 --- a/example/styleguide/res/templates/glyphs.html +++ /dev/null @@ -1,216 +0,0 @@ - -
    - -
    -

    Open MCT Style Guide

    -

    Glyphs

    -
    -

    Symbolic glyphs are used extensively in Open MCT to call attention to interactive elements, identify objects, and aid in visual recall. Glyphs are made available in a custom symbols font, and have associated CSS classes for their usage. Using a font in this way (versus using images or sprites) has advantages in that each symbol is in effect a scalable vector that can be sized up or down as needed. Color can also quite easily be applied via CSS.

    -

    New glyphs can be added if needed. Take care to observe the following guidelines: -

      -
    • Symbols should be created at 512 pixels high, and no more than 512 pixels wide. This size is based on a "crisp" 16px approach. Find out more about crisp symbol fonts.
    • -
    • In general, the symbol should occupy most of a square area as possible; avoid symbol aspect ratios that are squat or tall.
    • -
    • For consistency and legibility, symbols are designed as mostly solid shapes. Avoid using thin lines or fine detail that will be lost when the icon is sized down. In general, no stroke should be less than 32 pixels.
    • -
    • Symbols should be legible down to a minimum of 12 x 12 pixels.
    • - -
    -

    -
    - -
    -

    How to Use Glyphs

    -
    -
    -

    The easiest way to use a glyph is to include its CSS class in an element. The CSS adds a pseudo :before HTML element to whatever element it's attached to that makes proper use of the symbols font.

    -

    Alternately, you can use the .ui-symbol class in an object that contains encoded HTML entities. This method is only recommended if you cannot use the aforementioned CSS class approach.

    -
    - -

    - -

    -
      
    -
    -
    -
    - -
    -

    General User Interface Glyphs

    -

    Glyphs suitable for denoting general user interface verbs and nouns.

    -
    -
    -
    - - - - - -
    Class.{{glyph.cssClass}}
    Meaning{{glyph.meaning}}
    CSS Content\{{glyph.cssContent}}
    HTML Entity{{glyph.htmlEntity}}
    -
    -
    -
    - -
    -

    Control Glyphs

    -

    Glyphs created for use in various controls.

    -
    -
    -
    - - - - - -
    Class.{{glyph.cssClass}}
    Meaning{{glyph.meaning}}
    CSS Content\{{glyph.cssContent}}
    HTML Entity{{glyph.htmlEntity}}
    -
    -
    -
    - -
    -

    Object Type Glyphs

    -

    These glyphs are reserved exclusively to denote types of objects in the application. Only use them if you are referring to a pre-existing object type.

    -
    -
    -
    - - - - - -
    Class.{{glyph.cssClass}}
    Meaning{{glyph.meaning}}
    CSS Content\{{glyph.cssContent}}
    HTML Entity{{glyph.htmlEntity}}
    -
    -
    -
    - -
    - diff --git a/example/styleguide/res/templates/input.html b/example/styleguide/res/templates/input.html deleted file mode 100644 index eae33098bb..0000000000 --- a/example/styleguide/res/templates/input.html +++ /dev/null @@ -1,75 +0,0 @@ - -
    -

    Open MCT Style Guide

    -

    Text Input

    -
    -

    Text inputs and textareas have a consistent look-and-feel across the application. The input's placeholder attribute is styled to appear visually different from an entered value.

    -
    - -
    -

    Text Inputs

    -
    -
    -

    Use a text input where the user should enter relatively short text entries.

    -

    A variety of size styles are available: .lg, .med and .sm. .lg text inputs dynamically scale their width to 100% of their container's width. Numeric inputs that benefit from right-alignment can be styled by adding .numeric.

    -
    - -

    - -

    - -

    - -

    - -

    - -
    -
    -
    - -
    -

    Textareas

    -
    -
    -

    Use a textarea where the user should enter relatively longer or multi-line text entries.

    -

    By default, textareas are styled to expand to 100% of the width and height of their container; additionally there are three size styles available that control the height of the element: .lg, .med and .sm.

    -
    -
    - -
    -
    -
    - -
    -

    - -

    - -

    - -
    -
    -
    -
    - diff --git a/example/styleguide/res/templates/intro.html b/example/styleguide/res/templates/intro.html deleted file mode 100644 index 72fbe4bb4e..0000000000 --- a/example/styleguide/res/templates/intro.html +++ /dev/null @@ -1,73 +0,0 @@ - -
    -

    Open MCT Style Guide

    -

    Introduction

    -
    -

    Open MCT is a robust, extensible telemetry monitoring and situational awareness system that provides a framework supporting fast and efficient multi-mission deployment. This guide will explore the major concepts and design elements of Open MCT. Its overall goal is to guide you in creating new features and plugins that seamlessly integrate with the base application.

    -
    - -
    -

    Everything Is An Object

    -
    -
    -

    First and foremost, Open MCT uses a “object-oriented” approach: everything in the system is an object. Objects come in different types, and some objects can contain other objects of given types. This is similar to how the file management system of all modern computers works: a folder object can contain any other type of object, a presentation file can contain an image. This is conceptually the same in Open MCT.

    -

    As you develop plugins for Open MCT, consider how a generalized component might be combined with others when designing to create a rich and powerful larger object, rather than adding a single monolithic, non-modular plugin. To solve a particular problem or allow a new feature in Open MCT, you may need to introduce more than just one new object type.

    -
    -
    - -
    -
    -
    - -
    -

    Object Types

    -
    -
    -

    In the same way that different types of files might be opened and edited by different applications, objects in Open MCT also have different types. For example, a Display Layout provides a way that other objects that display information can be combined and laid out in a canvas area to create a recallable display that suits the needs of the user that created it. A Telemetry Panel allows a user to collect together Telemetry Points and visualize them as a plot or a table.

    -

    Object types provide a containment model that guides the user in their choices while creating a new object, and allows view normalization when flipping between different views. When a given object may only contain other objects of certain types, advantages emerge: the result of adding new objects is more predictable, more alternate views can be provided because the similarities between the contained objects is close, and we can provide more helpful and pointed guidance to the user because we know what types of objects they might be working with at a given time.

    -

    The types of objects that a container can hold should be based on the purpose of the container and the views that it affords. For example, a Folder’s purpose is to allow a user to conceptually organize objects of all other types; a Folder must therefore be able to contain an object of any type.

    -
    -
    - -
    -
    -
    - -
    -

    Object Views

    -
    -
    -

    Views are simply different ways to view the content of a given object. For example, telemetry data could be viewed as a plot or a table. A clock can display its time in analog fashion or with digital numbers. In each view, all of the content is present; it’s just represented differently. When providing views for an object, all the content of the object should be present in each view.

    -
    -
    - -
    -
    -
    - - - -

    -

    -

    -
    diff --git a/example/styleguide/res/templates/mct-example.html b/example/styleguide/res/templates/mct-example.html deleted file mode 100644 index a7fb0b5d0b..0000000000 --- a/example/styleguide/res/templates/mct-example.html +++ /dev/null @@ -1,8 +0,0 @@ -
    -

    Markup

    - -
    
    -    
    -

    Example

    -
    -
    diff --git a/example/styleguide/res/templates/menus.html b/example/styleguide/res/templates/menus.html deleted file mode 100644 index a18a10d216..0000000000 --- a/example/styleguide/res/templates/menus.html +++ /dev/null @@ -1,168 +0,0 @@ - -
    -

    Open MCT Style Guide

    -

    Menus

    - -
    -

    Context Menus

    -
    -
    -

    Context menus are used extensively in Open MCT. They are created dynamically upon a contextual click and positioned at the user's cursor position coincident with the element that invoked them. Context menus must use absolute position and utilize a z-index that places them above other in-page elements.

    -

    See User Interface Standards for more details on z-indexing in Open MCT. Context menus should be destroyed if the user clicks outside the menu element.

    -
    -
    - -
    -
    -
    - -
    -

    Dropdown Menus

    -
    -
    -

    Dropdown menus are a dedicated, more discoverable context menu for a given object. Like context menus, dropdown menus are used extensively in Open MCT, and are most often associated with object header elements. They visually manifest as a downward pointing arrow associated with an element, and when clicked displays a context menu at that location. See guidelines above about context menus in regards to z-indexing and element lifecycle.

    -

    Use a dropdown menu to encapsulate important the actions of an object in the object's header, or in a place that you'd use a context menu, but want to make the availability of the menu more apparent.

    -
    -
    - -
    - - - Object Header - - - - - - -
    - -
    -
    -
    - -
    -

    Checkbox Menus

    -
    -
    -

    Checkbox menus add checkbox options to each item of a dropdown menu. Use this to

    -

    Use a dropdown menu to encapsulate important the actions of an object in the object's header, or in a place that you'd use a context menu, but want to make the availability of the menu more apparent.

    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -

    Palettes

    -
    -
    -

    Use a palette to provide color choices. Similar to context menus and dropdowns, palettes should be dismissed when a choice is made within them, or if the user clicks outside one. Selected palette choices should utilize the selected CSS class to visualize indicate that state.

    -

    Note that while this example uses static markup for illustrative purposes, don't do this - use a front-end framework with repeaters to build the color choices.

    -
    -
    - - - -
    -
    -
    - -
    diff --git a/example/styleguide/res/templates/standards.html b/example/styleguide/res/templates/standards.html deleted file mode 100644 index 675e69c739..0000000000 --- a/example/styleguide/res/templates/standards.html +++ /dev/null @@ -1,48 +0,0 @@ - -
    -

    Open MCT Style Guide

    -

    Standards

    - -
    -

    Absolute Positioning and Z-Indexing

    -

    Absolute positioning is used in Open MCT in the main envelope interface to handle layout and draggable pane splitters, for elements that must be dynamically created and positioned (like context menus) and for buttons that are placed over other elements, such as a plot's zoom/pan history and reset buttons. When using absolute positioning, follow these guidelines:

    -
      -
    • Don't specify a z-index if you don't have to.
    • -
    • If you must specify a z-index, use the lowest number you that prevents your element from being covered and puts it at the correct level per the table below.
    • -
    - - - - - - - - - - - - -
    TypeDescriptionZ-index Range
    Base interface itemsBase level elements0 - 1
    Primary paneElements in the primary "view area" pane2
    Inspector pane, splittersElements in the Inspector, and splitters themselves3
    More base interface stuffBase level elements4 - 9
    TreeviewLefthand treeview elements30 - 39
    Help bubbles, rollover hintsInfobubbles, and similar50 - 59
    Context, button and dropdown menusContext menus, button menus, etc. that must overlay other elements70 - 79
    OverlaysModal overlay displays100 - 109
    Event messagesAlerts, event dialogs1000
    -
    - -
    \ No newline at end of file diff --git a/example/styleguide/res/templates/status.html b/example/styleguide/res/templates/status.html deleted file mode 100644 index 3680c3e652..0000000000 --- a/example/styleguide/res/templates/status.html +++ /dev/null @@ -1,227 +0,0 @@ - - -
    -

    Open MCT Style Guide

    -

    Status Indication

    - -
    -

    Status Classes

    -
    -
    -

    Status classes allow any block or inline-block element to be decorated in order to articulate a - status. Provided classes include color-only and color plus icon; custom icons can easily be - employed by using a color-only status class in combination with an glyph.

    -
      -
    • Color only
    • -
        -
      • s-status-warning-hi
      • -
      • s-status-warning-lo
      • -
      • s-status-diagnostic
      • -
      • s-status-info
      • -
      • s-status-ok
      • -
      -
    • Color and icon
    • -
        -
      • s-status-icon-warning-hi
      • -
      • s-status-icon-warning-lo
      • -
      • s-status-icon-diagnostic
      • -
      • s-status-icon-info
      • -
      • s-status-icon-ok
      • -
      -
    -
    - -
    WARNING HI
    -
    WARNING LOW
    -
    DIAGNOSTIC
    -
    INFO
    -
    OK
    - - -
    WARNING HI with icon
    -
    WARNING LOW with icon
    -
    DIAGNOSTIC with icon
    -
    INFO with icon
    -
    OK with icon
    -
    WARNING HI with custom icon
    -
    Some text with an inline element showing a Diagnostic status.
    -
    -
    -
    - -
    -

    Limit Classes

    -
    -
    -

    Limit classes are a specialized form of status, specifically meant to be applied to telemetry - displays to indicate that a limit threshold has been violated. Open MCT provides both severity - and direction classes; severity (yellow and red) can be used alone or in combination - with direction (upper or lower). Direction classes cannot be used on their own.

    -

    Like Status classes, Limits can be used as color-only, or color plus icon. Custom icons can - be applied in the same fashion as described above.

    -
      -
    • Severity color alone
    • -
        -
      • s-limit-yellow: A yellow limit.
      • -
      • s-limit-red: A red limit.
      • -
      -
    • Severity color and icon
    • -
        -
      • s-limit-icon-yellow: A yellow limit with icon.
      • -
      • s-limit-icon-red: A red limit with icon.
      • -
      -
    • Direction indicators. MUST be used with a "color alone" limit class. See - examples for more.
    • -
        -
      • s-limit-upr: Upper limit.
      • -
      • s-limit-lwr: Lower limit.
      • -
      -
    -
    - -
    Yellow limit
    -
    Red limit
    - - -
    Yellow limit with icon
    -
    Red limit with icon
    -
    Red Limit with a custom icon
    -
    Some text with an inline element showing a yellow limit.
    - - -
    Lower yellow limit
    -
    Upper red limit
    - - - - - - - -
    NameValue 1Value 2
    ENG_PWR 49917.02370.23
    ENG_PWR 499249.784-121.22
    ENG_PWR 49930.4511.007
    -
    -
    -
    - -
    -

    Status Bar Indicators

    -
    -
    -

    Indicators are small iconic notification elements that appear in the Status Bar area of - the application at the window's bottom. Indicators should be used to articulate the state of a - system and optionally provide gestures related to that system. They use a combination of icon and - color to identify themselves and articulate a state respectively.

    -

    Recommendations

    -
      -
    • Keep the icon consistent. The icon is the principal identifier of the system and is a valuable - recall aid for the user. Don't change the icon as a system's state changes, use color and - text for that purpose.
    • -
    • Don't use the same icon more than once. Select meaningful and distinct icons so the user - will be able to quickly identify what they're looking for.
    • -
    - -

    States

    -
      -
    • Disabled: The system is not available to the user.
    • -
    • Off / Available: The system is accessible to the user but is not currently - "On" or has not been configured. If the Indicator directly provides gestures - related to the system, such as opening a configuration dialog box, then use - "Available"; if the user must act elsewhere or the system isn't user-controllable, - use "Off".
    • -
    • On: The system is enabled or configured; it is having an effect on the larger application.
    • -
    • Alert / Error: There has been a problem with the system. Generally, "Alert" - should be used to call attention to an issue that isn't critical, while "Error" - should be used to call attention to a problem that the user should really be aware of or do - something about.
    • -
    - -

    Structure

    -

    Indicators consist of a .ls-indicator - wrapper element with .icon-* classes for the type of thing they represent and - .s-status-* classes to articulate the current state. Title attributes should be used - to provide more verbose information about the thing and/or its status.

    -

    The wrapper encloses a .label element that is displayed on hover. This element should - include a brief statement of the current status, and can also include clickable elements - as <a> tags. An optional .count element can be included to display - information such as a number of messages.

    -

    Icon classes are as defined on the - - Glyphs page. Status classes applicable to Indicators are as follows:

    -
      -
    • s-status-disabled
    • -
    • s-status-off
    • -
    • s-status-available
    • -
    • s-status-on
    • -
    • s-status-alert
    • -
    • s-status-error
    • -
    -
    -
    - - System not enabled. - -
    - -
    - - Data connection available - Configure - - -
    - -
    - - Connected to Skynet - Change - Disconnect - - -
    - -
    - - Skynet at Turing Level 5 - - - - View Alerts - - 495 - -
    -
    -
    -
    -
    diff --git a/example/styleguide/src/ExampleStyleGuideModelProvider.js b/example/styleguide/src/ExampleStyleGuideModelProvider.js deleted file mode 100644 index 80f170a9e9..0000000000 --- a/example/styleguide/src/ExampleStyleGuideModelProvider.js +++ /dev/null @@ -1,82 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-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 () { - "use strict"; - - function ExampleStyleGuideModelProvider($q) { - var pages = {}; - - // Add pages - pages.intro = { - name: "Introduction", - type: "styleguide.intro", - location: "styleguide:home" - }; - pages.standards = { - name: "Standards", - type: "styleguide.standards", - location: "styleguide:home" - }; - pages.colors = { - name: "Colors", - type: "styleguide.colors", - location: "styleguide:home" - }; - pages.glyphs = { - name: "Glyphs", - type: "styleguide.glyphs", - location: "styleguide:home" - }; - pages.status = { - name: "Status Indication", - type: "styleguide.status", - location: "styleguide:home" - }; - pages.controls = { - name: "Controls", - type: "styleguide.controls", - location: "styleguide:ui-elements" - }; - pages.input = { - name: "Text Inputs", - type: "styleguide.input", - location: "styleguide:ui-elements" - }; - pages.menus = { - name: "Menus", - type: "styleguide.menus", - location: "styleguide:ui-elements" - }; - - return { - getModels: function () { - return $q.when(pages); - } - }; - } - - return ExampleStyleGuideModelProvider; - } -); diff --git a/example/styleguide/src/MCTExample.js b/example/styleguide/src/MCTExample.js deleted file mode 100644 index 43b82a2c44..0000000000 --- a/example/styleguide/src/MCTExample.js +++ /dev/null @@ -1,30 +0,0 @@ -define([ - '../res/templates/mct-example.html' -], function ( - MCTExampleTemplate -) { - - function MCTExample() { - function link($scope, $element, $attrs, controller, $transclude) { - var codeEl = $element.find('pre'); - var exampleEl = $element.find('div'); - - $transclude(function (clone) { - exampleEl.append(clone); - codeEl.text(exampleEl.html() - .replace(/ class="ng-scope"/g, "") - .replace(/ ng-scope"/g, '"')); - }); - } - - return { - restrict: "E", - template: MCTExampleTemplate, - transclude: true, - link: link, - replace: true - }; - } - - return MCTExample; -}); diff --git a/index.html b/index.html index 78e5fbf5c6..d8ec226c49 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ - - - - diff --git a/platform/commonUI/browse/res/templates/browse-object.html b/platform/commonUI/browse/res/templates/browse-object.html deleted file mode 100644 index 6d03191000..0000000000 --- a/platform/commonUI/browse/res/templates/browse-object.html +++ /dev/null @@ -1,69 +0,0 @@ - -
    -
    -
    - - - -
    -
    - - - - - - - -
    -
    -
    -
    - -
    - - - -
    - - -
    -
    -
    diff --git a/platform/commonUI/browse/res/templates/browse.html b/platform/commonUI/browse/res/templates/browse.html deleted file mode 100644 index 37eecfb61c..0000000000 --- a/platform/commonUI/browse/res/templates/browse.html +++ /dev/null @@ -1,90 +0,0 @@ - - -
    - -
    - -
    -
    - - - - - - -
    -
    - - - -
    - - -
    - - -
    - - - -
    - - - -
    - - -
    -
    -
    -
    -
    -
    - -
    diff --git a/platform/commonUI/browse/res/templates/browse/inspector-region.html b/platform/commonUI/browse/res/templates/browse/inspector-region.html deleted file mode 100644 index fbddf4c26f..0000000000 --- a/platform/commonUI/browse/res/templates/browse/inspector-region.html +++ /dev/null @@ -1,39 +0,0 @@ - -
    - - - -
    - - -
    - -
    -
    -
    diff --git a/platform/commonUI/browse/res/templates/browse/object-header-frame.html b/platform/commonUI/browse/res/templates/browse/object-header-frame.html deleted file mode 100644 index ff1172cf78..0000000000 --- a/platform/commonUI/browse/res/templates/browse/object-header-frame.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - {{parameters.mode}} - {{model.name}} - - - diff --git a/platform/commonUI/browse/res/templates/browse/object-header.html b/platform/commonUI/browse/res/templates/browse/object-header.html deleted file mode 100644 index 3deb3ccfce..0000000000 --- a/platform/commonUI/browse/res/templates/browse/object-header.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - {{parameters.mode}} - {{model.name}} - - - diff --git a/platform/commonUI/browse/res/templates/browse/object-properties.html b/platform/commonUI/browse/res/templates/browse/object-properties.html deleted file mode 100644 index efe5a64ad2..0000000000 --- a/platform/commonUI/browse/res/templates/browse/object-properties.html +++ /dev/null @@ -1,64 +0,0 @@ - -
    -
      -

      Properties

      -
    • -
      {{ data.name }}
      -
      {{ data.value }}
      -
    • -
    - -
      -

      Location

      -
    • -
      This Link
      -
      -
      - - -
      -
      -
    • -
    • -
      Original
      -
      -
      - - -
      -
      -
    • -
    -
    diff --git a/platform/commonUI/browse/res/templates/menu-arrow.html b/platform/commonUI/browse/res/templates/menu-arrow.html deleted file mode 100644 index 719dd86c49..0000000000 --- a/platform/commonUI/browse/res/templates/menu-arrow.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - diff --git a/platform/commonUI/browse/src/InspectorRegion.js b/platform/commonUI/browse/src/InspectorRegion.js deleted file mode 100644 index d519c9521f..0000000000 --- a/platform/commonUI/browse/src/InspectorRegion.js +++ /dev/null @@ -1,67 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - '../../regions/src/Region' - ], - function (Region) { - - /** - * Defines the a default Inspector region. Captured in a class to - * allow for modular extension and customization of regions based on - * the typical case. - * @memberOf platform/commonUI/regions - * @constructor - */ - function InspectorRegion() { - Region.call(this, {'name': 'Inspector'}); - - this.buildRegion(); - } - - InspectorRegion.prototype = Object.create(Region.prototype); - InspectorRegion.prototype.constructor = Region; - - /** - * @private - */ - InspectorRegion.prototype.buildRegion = function () { - var metadataRegion = { - name: 'metadata', - title: 'Metadata Region', - // Which modes should the region part be visible in? If - // nothing provided here, then assumed that part is visible - // in both. The visibility or otherwise of a region part - // should be decided by a policy. In this case, 'modes' is a - // shortcut that is used by the EditableRegionPolicy. - modes: ['browse', 'edit'], - content: { - key: 'object-properties' - } - }; - this.addRegion(new Region(metadataRegion), 0); - }; - - return InspectorRegion; - } -); diff --git a/platform/commonUI/browse/src/navigation/NavigateAction.js b/platform/commonUI/browse/src/navigation/NavigateAction.js deleted file mode 100644 index 0637d5b51f..0000000000 --- a/platform/commonUI/browse/src/navigation/NavigateAction.js +++ /dev/null @@ -1,69 +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. - *****************************************************************************/ - -/** - * Module defining NavigateAction. Created by vwoeltje on 11/10/14. - */ -define( - [], - function () { - - /** - * The navigate action navigates to a specific domain object. - * @memberof platform/commonUI/browse - * @constructor - * @implements {Action} - */ - function NavigateAction(navigationService, context) { - this.domainObject = context.domainObject; - this.navigationService = navigationService; - } - - /** - * Navigate to the object described in the context. - * @returns {Promise} a promise that is resolved once the - * navigation has been updated - */ - NavigateAction.prototype.perform = function () { - if (this.navigationService.shouldNavigate()) { - this.navigationService.setNavigation(this.domainObject, true); - - return Promise.resolve({}); - } - - return Promise.reject('Navigation Prevented by User'); - }; - - /** - * Navigate as an action is only applicable when a domain object - * is described in the action context. - * @param {ActionContext} context the context in which the action - * will be performed - * @returns {boolean} true if applicable - */ - NavigateAction.appliesTo = function (context) { - return context.domainObject !== undefined; - }; - - return NavigateAction; - } -); diff --git a/platform/commonUI/browse/src/navigation/NavigationService.js b/platform/commonUI/browse/src/navigation/NavigationService.js deleted file mode 100644 index 25612a2fe7..0000000000 --- a/platform/commonUI/browse/src/navigation/NavigationService.js +++ /dev/null @@ -1,203 +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. - *****************************************************************************/ - -/** - * Module defining NavigationService. Created by vwoeltje on 11/10/14. - */ -define( - [], - function () { - - /** - * The navigation service maintains the application's current - * navigation state, and allows listening for changes thereto. - * - * @memberof platform/commonUI/browse - * @constructor - */ - function NavigationService($window) { - this.navigated = undefined; - this.callbacks = []; - this.checks = []; - this.$window = $window; - - this.oldUnload = $window.onbeforeunload; - $window.onbeforeunload = this.onBeforeUnload.bind(this); - } - - /** - * Get the current navigation state. - * - * @returns {DomainObject} the object that is navigated-to - */ - NavigationService.prototype.getNavigation = function () { - return this.navigated; - }; - - /** - * Navigate to a specified object. If navigation checks exist and - * return reasons to prevent navigation, it will prompt the user before - * continuing. Trying to navigate to the currently navigated object will - * do nothing. - * - * If a truthy value is passed for `force`, it will skip navigation - * and will not prevent navigation to an already selected object. - * - * @param {DomainObject} domainObject the domain object to navigate to - * @param {Boolean} force if true, force navigation to occur. - * @returns {Boolean} true if navigation occurred, otherwise false. - */ - NavigationService.prototype.setNavigation = function (domainObject, force) { - if (force) { - this.doNavigation(domainObject); - - return true; - } - - if (this.navigated === domainObject) { - return true; - } - - var doNotNavigate = this.shouldWarnBeforeNavigate(); - if (doNotNavigate && !this.$window.confirm(doNotNavigate)) { - return false; - } - - this.doNavigation(domainObject); - - return true; - }; - - /** - * Listen for changes in navigation. The passed callback will - * be invoked with the new domain object of navigation when - * this changes. - * - * @param {function} callback the callback to invoke when - * navigation state changes - */ - NavigationService.prototype.addListener = function (callback) { - this.callbacks.push(callback); - }; - - /** - * Stop listening for changes in navigation state. - * - * @param {function} callback the callback which should - * no longer be invoked when navigation state - * changes - */ - NavigationService.prototype.removeListener = function (callback) { - this.callbacks = this.callbacks.filter(function (cb) { - return cb !== callback; - }); - }; - - /** - * Check if navigation should proceed. May prompt a user for input - * if any checkFns return messages. Returns true if the user wishes to - * navigate, otherwise false. If using this prior to calling - * `setNavigation`, you should call `setNavigation` with `force=true` - * to prevent duplicate dialogs being displayed to the user. - * - * @returns {Boolean} true if the user wishes to navigate, otherwise false. - */ - NavigationService.prototype.shouldNavigate = function () { - var doNotNavigate = this.shouldWarnBeforeNavigate(); - - return !doNotNavigate || this.$window.confirm(doNotNavigate); - }; - - /** - * Register a check function to be called before any navigation occurs. - * Check functions should return a human readable "message" if - * there are any reasons to prevent navigation. Otherwise, they should - * return falsy. Returns a function which can be called to remove the - * check function. - * - * @param {Function} checkFn a function to call before navigation occurs. - * @returns {Function} removeCheck call to remove check - */ - NavigationService.prototype.checkBeforeNavigation = function (checkFn) { - this.checks.push(checkFn); - - return function removeCheck() { - this.checks = this.checks.filter(function (fn) { - return checkFn !== fn; - }); - }.bind(this); - }; - - /** - * Private method to actually perform navigation. - * - * @private - */ - NavigationService.prototype.doNavigation = function (value) { - this.navigated = value; - this.callbacks.forEach(function (callback) { - callback(value); - }); - }; - - /** - * Returns either a false value, or a string that should be displayed - * to the user before navigation is allowed. - * - * @private - */ - NavigationService.prototype.shouldWarnBeforeNavigate = function () { - var reasons = []; - this.checks.forEach(function (checkFn) { - var reason = checkFn(); - if (reason) { - reasons.push(reason); - } - }); - - if (reasons.length) { - return reasons.join('\n'); - } - - return false; - }; - - /** - * Listener for window on before unload event-- will warn before - * navigation is allowed. - * - * @private - */ - NavigationService.prototype.onBeforeUnload = function () { - var shouldWarnBeforeNavigate = this.shouldWarnBeforeNavigate(); - if (shouldWarnBeforeNavigate) { - return shouldWarnBeforeNavigate; - } - - if (this.oldUnload) { - return this.oldUnload.apply(undefined, [].slice.apply(arguments)); - } - }; - - return NavigationService; - } -); diff --git a/platform/commonUI/browse/src/navigation/OrphanNavigationHandler.js b/platform/commonUI/browse/src/navigation/OrphanNavigationHandler.js deleted file mode 100644 index 198f57d9cc..0000000000 --- a/platform/commonUI/browse/src/navigation/OrphanNavigationHandler.js +++ /dev/null @@ -1,76 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([], function () { - - /** - * Navigates away from orphan objects whenever they are detected. - * - * An orphan object is an object whose apparent parent does not - * actually contain it. This may occur in certain circumstances, such - * as when persistence succeeds for a newly-created object but fails - * for its parent. - * - * @param throttle the `throttle` service - * @param topic the `topic` service - * @param navigationService the `navigationService` - * @constructor - */ - function OrphanNavigationHandler(throttle, topic, navigationService) { - var throttledCheckNavigation; - - function getParent(domainObject) { - var context = domainObject.getCapability('context'); - - return context.getParent(); - } - - function preventOrphanNavigation(domainObject) { - var parent = getParent(domainObject); - parent.useCapability('composition') - .then(function (composees) { - var isOrphan = composees.every(function (c) { - return c.getId() !== domainObject.getId(); - }); - if (isOrphan) { - parent.getCapability('action').perform('navigate'); - } - }); - } - - function checkNavigation() { - var navigatedObject = navigationService.getNavigation(); - if (navigatedObject && navigatedObject.hasCapability('context')) { - if (!navigatedObject.getCapability('editor').isEditContextRoot()) { - preventOrphanNavigation(navigatedObject); - } - } - } - - throttledCheckNavigation = throttle(checkNavigation); - - navigationService.addListener(throttledCheckNavigation); - topic('mutation').listen(throttledCheckNavigation); - } - - return OrphanNavigationHandler; -}); diff --git a/platform/commonUI/browse/test/InspectorRegionSpec.js b/platform/commonUI/browse/test/InspectorRegionSpec.js deleted file mode 100644 index 2f228cb99f..0000000000 --- a/platform/commonUI/browse/test/InspectorRegionSpec.js +++ /dev/null @@ -1,43 +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. - *****************************************************************************/ - -/** - * MCTIncudeSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../src/InspectorRegion"], - function (InspectorRegion) { - - describe("The inspector region", function () { - var inspectorRegion; - - beforeEach(function () { - inspectorRegion = new InspectorRegion(); - }); - - it("creates default region parts", function () { - expect(inspectorRegion.regions.length).toBe(1); - }); - - }); - } -); diff --git a/platform/commonUI/browse/test/navigation/NavigateActionSpec.js b/platform/commonUI/browse/test/navigation/NavigateActionSpec.js deleted file mode 100644 index 158714b635..0000000000 --- a/platform/commonUI/browse/test/navigation/NavigateActionSpec.js +++ /dev/null @@ -1,85 +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. - *****************************************************************************/ - -/** - * MCTRepresentationSpec. Created by vwoeltje on 11/6/14. - */ -define([ - "../../src/navigation/NavigateAction" -], function ( - NavigateAction -) { - - describe("The navigate action", function () { - var mockNavigationService, - mockDomainObject, - action; - - beforeEach(function () { - mockNavigationService = jasmine.createSpyObj( - "navigationService", - [ - "shouldNavigate", - "setNavigation" - ] - ); - - mockDomainObject = {}; - - action = new NavigateAction( - mockNavigationService, - { domainObject: mockDomainObject } - ); - }); - - it("sets navigation if it is allowed", function () { - mockNavigationService.shouldNavigate.and.returnValue(true); - - return action.perform() - .then(function () { - expect(mockNavigationService.setNavigation) - .toHaveBeenCalledWith(mockDomainObject, true); - }); - }); - - it("does not set navigation if it is not allowed", function () { - mockNavigationService.shouldNavigate.and.returnValue(false); - var onSuccess = jasmine.createSpy('onSuccess'); - - return action.perform() - .then(onSuccess, function () { - expect(onSuccess).not.toHaveBeenCalled(); - expect(mockNavigationService.setNavigation) - .not - .toHaveBeenCalledWith(mockDomainObject); - }); - }); - - it("is only applicable when a domain object is in context", function () { - expect(NavigateAction.appliesTo({})).toBeFalsy(); - expect(NavigateAction.appliesTo({ - domainObject: mockDomainObject - })).toBeTruthy(); - }); - - }); -}); diff --git a/platform/commonUI/browse/test/navigation/NavigationServiceSpec.js b/platform/commonUI/browse/test/navigation/NavigationServiceSpec.js deleted file mode 100644 index 7d3520b2ba..0000000000 --- a/platform/commonUI/browse/test/navigation/NavigationServiceSpec.js +++ /dev/null @@ -1,88 +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. - *****************************************************************************/ - -/** - * MCTRepresentationSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/navigation/NavigationService"], - function (NavigationService) { - - describe("The navigation service", function () { - var $window, - navigationService; - - beforeEach(function () { - $window = jasmine.createSpyObj('$window', ['confirm']); - navigationService = new NavigationService($window); - }); - - it("stores navigation state", function () { - var testObject = { someKey: 42 }, - otherObject = { someKey: "some value" }; - expect(navigationService.getNavigation()) - .toBeUndefined(); - navigationService.setNavigation(testObject); - expect(navigationService.getNavigation()) - .toBe(testObject); - expect(navigationService.getNavigation()) - .toBe(testObject); - navigationService.setNavigation(otherObject); - expect(navigationService.getNavigation()) - .toBe(otherObject); - }); - - it("notifies listeners on change", function () { - var testObject = { someKey: 42 }, - callback = jasmine.createSpy("callback"); - - navigationService.addListener(callback); - expect(callback).not.toHaveBeenCalled(); - - navigationService.setNavigation(testObject); - expect(callback).toHaveBeenCalledWith(testObject); - }); - - it("does not notify listeners when no changes occur", function () { - var testObject = { someKey: 42 }, - callback = jasmine.createSpy("callback"); - - navigationService.addListener(callback); - navigationService.setNavigation(testObject); - navigationService.setNavigation(testObject); - expect(callback.calls.count()).toEqual(1); - }); - - it("stops notifying listeners after removal", function () { - var testObject = { someKey: 42 }, - callback = jasmine.createSpy("callback"); - - navigationService.addListener(callback); - navigationService.removeListener(callback); - - navigationService.setNavigation(testObject); - expect(callback).not.toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/commonUI/browse/test/navigation/OrphanNavigationHandlerSpec.js b/platform/commonUI/browse/test/navigation/OrphanNavigationHandlerSpec.js deleted file mode 100644 index 904b2db2f1..0000000000 --- a/platform/commonUI/browse/test/navigation/OrphanNavigationHandlerSpec.js +++ /dev/null @@ -1,182 +0,0 @@ -/***************************************************************************** -* Open MCT, Copyright (c) 2014-2021, United States Government -* as represented by the Administrator of the National Aeronautics and Space -* Administration. All rights reserved. -* -* Open MCT is licensed under the Apache License, Version 2.0 (the -* "License"); you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* http://www.apache.org/licenses/LICENSE-2.0. -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -* -* Open MCT includes source code licensed under additional open source -* licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available -* at runtime from the About dialog for additional information. -*****************************************************************************/ - -define([ - '../../src/navigation/OrphanNavigationHandler' -], function (OrphanNavigationHandler) { - describe("OrphanNavigationHandler", function () { - var mockTopic, - mockThrottle, - mockMutationTopic, - mockNavigationService, - mockDomainObject, - mockParentObject, - mockContext, - mockActionCapability, - mockEditor, - testParentComposition, - testId, - mockThrottledFns; - - beforeEach(function () { - testId = 'some-identifier'; - - mockThrottledFns = []; - - mockTopic = jasmine.createSpy('topic'); - mockThrottle = jasmine.createSpy('throttle'); - mockNavigationService = jasmine.createSpyObj('navigationService', [ - 'getNavigation', - 'addListener' - ]); - mockMutationTopic = jasmine.createSpyObj('mutationTopic', [ - 'listen' - ]); - mockDomainObject = jasmine.createSpyObj('domainObject', [ - 'getId', - 'getCapability', - 'hasCapability' - ]); - mockParentObject = jasmine.createSpyObj('domainObject', [ - 'getId', - 'getCapability', - 'useCapability' - ]); - mockContext = jasmine.createSpyObj('context', ['getParent']); - mockActionCapability = jasmine.createSpyObj('action', ['perform']); - mockEditor = jasmine.createSpyObj('editor', ['isEditContextRoot']); - - mockThrottle.and.callFake(function (fn) { - var mockThrottledFn = - jasmine.createSpy('throttled-' + mockThrottledFns.length); - mockThrottledFn.and.callFake(fn); - mockThrottledFns.push(mockThrottledFn); - - return mockThrottledFn; - }); - mockTopic.and.returnValue(mockMutationTopic); - mockDomainObject.getId.and.returnValue(testId); - mockDomainObject.getCapability.and.callFake(function (c) { - return { - context: mockContext, - editor: mockEditor - }[c]; - }); - mockDomainObject.hasCapability.and.callFake(function (c) { - return Boolean(mockDomainObject.getCapability(c)); - }); - mockParentObject.getCapability.and.callFake(function (c) { - return { - action: mockActionCapability - }[c]; - }); - testParentComposition = []; - mockParentObject.useCapability.and.returnValue(Promise.resolve(testParentComposition)); - - mockContext.getParent.and.returnValue(mockParentObject); - mockNavigationService.getNavigation.and.returnValue(mockDomainObject); - mockEditor.isEditContextRoot.and.returnValue(false); - - return new OrphanNavigationHandler( - mockThrottle, - mockTopic, - mockNavigationService - ); - }); - - it("listens for mutation with a throttled function", function () { - expect(mockMutationTopic.listen) - .toHaveBeenCalledWith(jasmine.any(Function)); - expect(mockThrottledFns.indexOf( - mockMutationTopic.listen.calls.mostRecent().args[0] - )).not.toEqual(-1); - }); - - it("listens for navigation changes with a throttled function", function () { - expect(mockNavigationService.addListener) - .toHaveBeenCalledWith(jasmine.any(Function)); - expect(mockThrottledFns.indexOf( - mockNavigationService.addListener.calls.mostRecent().args[0] - )).not.toEqual(-1); - }); - - [false, true].forEach(function (isOrphan) { - var prefix = isOrphan ? "" : "non-"; - describe("for " + prefix + "orphan objects", function () { - beforeEach(function () { - if (!isOrphan) { - testParentComposition.push(mockDomainObject); - } - }); - - [false, true].forEach(function (isEditRoot) { - var caseName = isEditRoot - ? "that are being edited" : "that are not being edited"; - - function itNavigatesAsExpected() { - if (isOrphan && !isEditRoot) { - it("navigates to the parent", function () { - return Promise.resolve().then(function () { - expect(mockActionCapability.perform) - .toHaveBeenCalledWith('navigate'); - }); - }); - } else { - it("does nothing", function () { - return Promise.resolve().then(function () { - expect(mockActionCapability.perform) - .not.toHaveBeenCalled(); - }); - }); - } - } - - describe(caseName, function () { - beforeEach(function () { - mockEditor.isEditContextRoot.and.returnValue(isEditRoot); - }); - - describe("when navigation changes", function () { - beforeEach(function () { - mockNavigationService.addListener.calls.mostRecent() - .args[0](mockDomainObject); - }); - itNavigatesAsExpected(); - }); - - describe("when mutation occurs", function () { - beforeEach(function () { - mockMutationTopic.listen.calls.mostRecent() - .args[0](mockParentObject); - }); - - itNavigatesAsExpected(); - }); - - }); - }); - }); - }); - - }); -}); - diff --git a/platform/commonUI/dialog/README.md b/platform/commonUI/dialog/README.md deleted file mode 100644 index c42cf3a95b..0000000000 --- a/platform/commonUI/dialog/README.md +++ /dev/null @@ -1,27 +0,0 @@ -This bundle provides `dialogService`, which can be used to prompt -for user input. - -## `getUserChoice` - -The `getUserChoice` method is useful for displaying a message and a set of -buttons. This method returns a promise which will resolve to the user's -chosen option (or, more specifically, its `key`), and will be rejected if -the user closes the dialog with the X in the top-right; - -The `dialogModel` given as an argument to this method should have the -following properties. - -* `title`: The title to display at the top of the dialog. -* `hint`: Short message to display below the title. -* `template`: Identifying key (as will be passed to `mct-include`) for - the template which will be used to populate the inner area of the dialog. -* `model`: Model to pass in the `ng-model` attribute of - `mct-include`. -* `parameters`: Parameters to pass in the `parameters` attribute of - `mct-include`. -* `options`: An array of options describing each button at the bottom. - Each option may have the following properties: - * `name`: Human-readable name to display in the button. - * `key`: Machine-readable key, to pass as the result of the resolved - promise when clicked. - * `description`: Description to show in tool tip on hover. diff --git a/platform/commonUI/dialog/bundle.js b/platform/commonUI/dialog/bundle.js deleted file mode 100644 index 1b00d138bb..0000000000 --- a/platform/commonUI/dialog/bundle.js +++ /dev/null @@ -1,112 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/DialogService", - "./src/OverlayService", - "./res/templates/overlay-dialog.html", - "./res/templates/overlay-options.html", - "./res/templates/dialog.html", - "./res/templates/overlay-blocking-message.html", - "./res/templates/message.html", - "./res/templates/notification-message.html", - "./res/templates/overlay-message-list.html", - "./res/templates/overlay.html" -], function ( - DialogService, - OverlayService, - overlayDialogTemplate, - overlayOptionsTemplate, - dialogTemplate, - overlayBlockingMessageTemplate, - messageTemplate, - notificationMessageTemplate, - overlayMessageListTemplate, - overlayTemplate -) { - - return { - name: "platform/commonUI/dialog", - definition: { - "extensions": { - "services": [ - { - "key": "dialogService", - "implementation": DialogService, - "depends": [ - "overlayService", - "$q", - "$log", - "$document" - ] - }, - { - "key": "overlayService", - "implementation": OverlayService, - "depends": [ - "$document", - "$compile", - "$rootScope", - "$timeout" - ] - } - ], - "templates": [ - { - "key": "overlay-dialog", - "template": overlayDialogTemplate - }, - { - "key": "overlay-options", - "template": overlayOptionsTemplate - }, - { - "key": "form-dialog", - "template": dialogTemplate - }, - { - "key": "overlay-blocking-message", - "template": overlayBlockingMessageTemplate - }, - { - "key": "message", - "template": messageTemplate - }, - { - "key": "notification-message", - "template": notificationMessageTemplate - }, - { - "key": "overlay-message-list", - "template": overlayMessageListTemplate - } - ], - "containers": [ - { - "key": "overlay", - "template": overlayTemplate - } - ] - } - } - }; -}); diff --git a/platform/commonUI/dialog/res/templates/dialog.html b/platform/commonUI/dialog/res/templates/dialog.html deleted file mode 100644 index 348fddc0f1..0000000000 --- a/platform/commonUI/dialog/res/templates/dialog.html +++ /dev/null @@ -1,43 +0,0 @@ - -
    -
    {{ngModel.title}}
    -
    All fields marked are required.
    -
    -
    - - -
    -
    - - -
    diff --git a/platform/commonUI/dialog/res/templates/message.html b/platform/commonUI/dialog/res/templates/message.html deleted file mode 100644 index c75878cfc5..0000000000 --- a/platform/commonUI/dialog/res/templates/message.html +++ /dev/null @@ -1,32 +0,0 @@ -
    -
    -
    -
    {{ngModel.title}}
    -
    -
    - {{ngModel.hint}} - [{{ngModel.timestamp}}] -
    -
    -
    - {{ngModel.actionText}} -
    - -
    -
    - - -
    -
    -
    \ No newline at end of file diff --git a/platform/commonUI/dialog/res/templates/notification-message.html b/platform/commonUI/dialog/res/templates/notification-message.html deleted file mode 100644 index 8fabb81081..0000000000 --- a/platform/commonUI/dialog/res/templates/notification-message.html +++ /dev/null @@ -1,25 +0,0 @@ -
    -
    -
    -
    {{ngModel.message}}
    -
    -
    - -
    -
    -
    - - -
    -
    diff --git a/platform/commonUI/dialog/res/templates/overlay-blocking-message.html b/platform/commonUI/dialog/res/templates/overlay-blocking-message.html deleted file mode 100644 index 1730aa5a5b..0000000000 --- a/platform/commonUI/dialog/res/templates/overlay-blocking-message.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - diff --git a/platform/commonUI/dialog/res/templates/overlay-dialog.html b/platform/commonUI/dialog/res/templates/overlay-dialog.html deleted file mode 100644 index 357c3f6642..0000000000 --- a/platform/commonUI/dialog/res/templates/overlay-dialog.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - diff --git a/platform/commonUI/dialog/res/templates/overlay-message-list.html b/platform/commonUI/dialog/res/templates/overlay-message-list.html deleted file mode 100644 index caabd7d4bf..0000000000 --- a/platform/commonUI/dialog/res/templates/overlay-message-list.html +++ /dev/null @@ -1,29 +0,0 @@ - -
    -
    -
    {{ngModel.dialog.title}}
    -
    Displaying {{ngModel.dialog.messages.length}} messages -
    - -
    -
    - -
    -
    - -
    -
    -
    diff --git a/platform/commonUI/dialog/res/templates/overlay-options.html b/platform/commonUI/dialog/res/templates/overlay-options.html deleted file mode 100644 index 9dc0ed193d..0000000000 --- a/platform/commonUI/dialog/res/templates/overlay-options.html +++ /dev/null @@ -1,43 +0,0 @@ - - -
    -
    {{ngModel.dialog.title}}
    -
    {{ngModel.dialog.hint}}
    -
    -
    - - -
    -
    - -
    -
    diff --git a/platform/commonUI/dialog/res/templates/overlay.html b/platform/commonUI/dialog/res/templates/overlay.html deleted file mode 100644 index b6e2829038..0000000000 --- a/platform/commonUI/dialog/res/templates/overlay.html +++ /dev/null @@ -1,30 +0,0 @@ - -
    -
    -
    - -
    -
    -
    diff --git a/platform/commonUI/dialog/src/DialogService.js b/platform/commonUI/dialog/src/DialogService.js deleted file mode 100644 index 85f7847282..0000000000 --- a/platform/commonUI/dialog/src/DialogService.js +++ /dev/null @@ -1,271 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * This bundle implements the dialog service, which can be used to - * launch dialogs for user input & notifications. - * @namespace platform/commonUI/dialog - */ -define( - [], - function () { - /** - * The dialog service is responsible for handling window-modal - * communication with the user, such as displaying forms for user - * input. - * @memberof platform/commonUI/dialog - * @constructor - */ - function DialogService(overlayService, $q, $log, $document) { - this.overlayService = overlayService; - this.$q = $q; - this.$log = $log; - this.activeOverlay = undefined; - - this.findBody = function () { - return $document.find('body'); - }; - } - - /** - * @private - */ - DialogService.prototype.dismissOverlay = function (overlay) { - //Dismiss the overlay - overlay.dismiss(); - - //If dialog is the current active one, dismiss it - if (overlay === this.activeOverlay) { - this.activeOverlay = undefined; - } - }; - - DialogService.prototype.getDialogResponse = function (key, model, resultGetter, typeClass) { - // We will return this result as a promise, because user - // input is asynchronous. - var deferred = this.$q.defer(), - self = this, - overlay, - handleEscKeydown; - - // Confirm function; this will be passed in to the - // overlay-dialog template and associated with a - // OK button click - function confirm(value) { - // Pass along the result - deferred.resolve(resultGetter ? resultGetter() : value); - self.dismissOverlay(overlay); - } - - // Cancel function; this will be passed in to the - // overlay-dialog template and associated with a - // Cancel or X button click - function cancel() { - deferred.reject(); - self.findBody().off('keydown', handleEscKeydown); - self.dismissOverlay(overlay); - } - - handleEscKeydown = function (event) { - if (event.keyCode === 27) { - cancel(); - } - }; - - // Add confirm/cancel callbacks - model.confirm = confirm; - model.cancel = cancel; - - this.findBody().on('keydown', handleEscKeydown); - - if (this.canShowDialog(model)) { - // Add the overlay using the OverlayService, which - // will handle actual insertion into the DOM - overlay = this.activeOverlay = this.overlayService.createOverlay( - key, - model, - typeClass || "t-dialog" - ); - } else { - deferred.reject(); - } - - return deferred.promise; - }; - - /** - * Request user input via a window-modal dialog. - * - * @param {FormModel} formModel a description of the form - * to be shown (see platform/forms) - * @param {object} value the initial state of the form - * @returns {Promise} a promise for the form value that the - * user has supplied; this may be rejected if - * user input cannot be obtained (for instance, - * because the user cancelled the dialog) - */ - DialogService.prototype.getUserInput = function (formModel, value) { - var overlayModel = { - title: formModel.name, - message: formModel.message, - structure: formModel, - value: value - }; - - // Provide result from the model - function resultGetter() { - return overlayModel.value; - } - - // Show the overlay-dialog - return this.getDialogResponse( - "overlay-dialog", - overlayModel, - resultGetter - ); - }; - - /** - * Request that the user chooses from a set of options, - * which will be shown as buttons. - * - * @param dialogModel a description of the dialog to show - * @return {Promise} a promise for the user's choice - */ - DialogService.prototype.getUserChoice = function (dialogModel) { - // Show the overlay-options dialog - return this.getDialogResponse( - "overlay-options", - { dialog: dialogModel } - ); - }; - - /** - * Tests if a dialog can be displayed. A modal dialog may only be - * displayed if one is not already visible. - * Will log a warning message if it can't display a dialog. - * @returns {boolean} true if dialog is currently visible, false - * otherwise - */ - DialogService.prototype.canShowDialog = function (dialogModel) { - if (this.activeOverlay) { - // Only one dialog should be shown at a time. - // The application design should be such that - // we never even try to do this. - this.$log.warn([ - "Dialog already showing; ", - "unable to show ", - dialogModel.title - ].join("")); - - return false; - } else { - return true; - } - }; - - /** - * A user action that can be performed from a blocking dialog. These - * actions will be rendered as buttons within a blocking dialog. - * - * @typedef DialogOption - * @property {string} label a label to be displayed as the button - * text for this action - * @property {function} callback a function to be called when the - * button is clicked - */ - - /** - * @typedef DialogHandle - * @property {function} dismiss a function to dismiss the given dialog - */ - - /** - * A description of the model options that may be passed to the - * showBlockingMessage method. Note that the DialogModel described - * here is shared with the Notifications framework. - * @see NotificationService - * - * @typedef DialogModel - * @property {string} title the title to use for the dialog - * @property {string} severity the severity level of this message. - * These are defined in a bundle constant with key 'dialogSeverity' - * @property {string} hint the 'hint' message to show below the title - * @property {string} actionText text that indicates a current action, - * shown above a progress bar to indicate what's happening. - * @property {number} progress a percentage value (1-100) - * indicating the completion of the blocking task - * @property {boolean} delay adds a brief delay before loading - * the dialog. Useful for removing the dialog flicker when the - * conditions for displaying the dialog change rapidly. - * @property {string} progressText the message to show below a - * progress bar to indicate progress. For example, this might be - * used to indicate time remaining, or items still to process. - * @property {boolean} unknownProgress some tasks may be - * impossible to provide an estimate for. Providing a true value for - * this attribute will indicate to the user that the progress and - * duration cannot be estimated. - * @property {DialogOption} primaryOption an action that will - * be added to the dialog as a button. The primary action can be - * used as the suggested course of action for the user. Making it - * distinct from other actions allows it to be styled differently, - * and treated preferentially in banner mode. - * @property {DialogOption[]} options a list of actions that will - * be added to the dialog as buttons. - */ - - /** - * Displays a blocking (modal) dialog. This dialog can be used for - * displaying messages that require the user's - * immediate attention. The message may include an indication of - * progress, as well as a series of actions that - * the user can take if necessary - * @param {DialogModel} dialogModel defines options for the dialog - * @param {typeClass} string tells overlayService that this overlay should use appropriate CSS class - * @returns {boolean | {DialogHandle}} - */ - DialogService.prototype.showBlockingMessage = function (dialogModel) { - if (this.canShowDialog(dialogModel)) { - // Add the overlay using the OverlayService, which - // will handle actual insertion into the DOM - var self = this, - overlay = this.overlayService.createOverlay( - "overlay-blocking-message", - dialogModel, - "t-dialog-sm" - ); - - this.activeOverlay = overlay; - - return { - dismiss: function () { - self.dismissOverlay(overlay); - } - }; - } else { - return false; - } - }; - - return DialogService; - } -); diff --git a/platform/commonUI/dialog/src/OverlayService.js b/platform/commonUI/dialog/src/OverlayService.js deleted file mode 100644 index d53be9574f..0000000000 --- a/platform/commonUI/dialog/src/OverlayService.js +++ /dev/null @@ -1,113 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - // Template to inject into the DOM to show the dialog; really just points to - // the a specific template that can be included via mct-include - var TEMPLATE = ''; - - /** - * The OverlayService is responsible for pre-pending templates to - * the body of the document, which is useful for displaying templates - * which need to block the full screen. - * - * This is intended to be used by the DialogService; by design, it - * does not have any protections in place to prevent multiple overlays - * from being shown at once. (The DialogService does have these - * protections, and should be used for most overlay-type interactions, - * particularly where a multiple-overlay effect is not specifically - * desired). - * - * @memberof platform/commonUI/dialog - * @constructor - */ - function OverlayService($document, $compile, $rootScope, $timeout) { - this.$compile = $compile; - this.$timeout = $timeout; - - // Don't include $document and $rootScope directly; - // avoids https://docs.angularjs.org/error/ng/cpws - this.findBody = function () { - return $document.find('body'); - }; - - this.newScope = function () { - return $rootScope.$new(); - }; - } - - /** - * Add a new overlay to the document. This will be - * prepended to the document body; the overlay's - * template (as pointed to by the `key` argument) is - * responsible for having a useful z-order, and for - * blocking user interactions if appropriate. - * - * @param {string} key the symbolic key which identifies - * the template of the overlay to be shown - * @param {object} overlayModel the model to pass to the - * included overlay template (this will be passed - * in via ng-model) - * @param {string} typeClass the element class to use in rendering - * the overlay. Can be specified to provide custom styling of - * overlays - */ - OverlayService.prototype.createOverlay = function (key, overlayModel, typeClass) { - // Create a new scope for this overlay - var scope = this.newScope(), - element; - - // Stop showing the overlay; additionally, release the scope - // that it uses. - function dismiss() { - scope.$destroy(); - element.remove(); - } - - // If no model is supplied, just fill in a default "cancel" - overlayModel = overlayModel || { cancel: dismiss }; - - // Populate the scope; will be passed directly to the template - scope.overlay = overlayModel; - scope.key = key; - scope.typeClass = typeClass || 't-dialog'; - - this.$timeout(() => { - // Create the overlay element and add it to the document's body - element = this.$compile(TEMPLATE)(scope); - - // Append so that most recent dialog is last in DOM. This means the most recent dialog will be on top when - // multiple overlays with the same z-index are active. - this.findBody().append(element); - }); - - return { - dismiss: dismiss - }; - }; - - return OverlayService; - } -); diff --git a/platform/commonUI/dialog/test/DialogServiceSpec.js b/platform/commonUI/dialog/test/DialogServiceSpec.js deleted file mode 100644 index d08c3acd18..0000000000 --- a/platform/commonUI/dialog/test/DialogServiceSpec.js +++ /dev/null @@ -1,214 +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. - *****************************************************************************/ - -/** - * MCTIncudeSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../src/DialogService"], - function (DialogService) { - - describe("The dialog service", function () { - var mockOverlayService, - mockQ, - mockLog, - mockOverlay, - mockDeferred, - mockDocument, - mockBody, - dialogService; - - beforeEach(function () { - mockOverlayService = jasmine.createSpyObj( - "overlayService", - ["createOverlay"] - ); - mockQ = jasmine.createSpyObj( - "$q", - ["defer"] - ); - mockLog = jasmine.createSpyObj( - "$log", - ["warn", "info", "debug"] - ); - mockOverlay = jasmine.createSpyObj( - "overlay", - ["dismiss"] - ); - mockDeferred = jasmine.createSpyObj( - "deferred", - ["resolve", "reject"] - ); - mockDocument = jasmine.createSpyObj( - "$document", - ["find"] - ); - mockBody = jasmine.createSpyObj('body', ['on', 'off']); - mockDocument.find.and.returnValue(mockBody); - - mockDeferred.promise = "mock promise"; - - mockQ.defer.and.returnValue(mockDeferred); - mockOverlayService.createOverlay.and.returnValue(mockOverlay); - - dialogService = new DialogService( - mockOverlayService, - mockQ, - mockLog, - mockDocument - ); - }); - - it("adds an overlay when user input is requested", function () { - dialogService.getUserInput({}, {}); - expect(mockOverlayService.createOverlay).toHaveBeenCalled(); - }); - - it("allows user input to be canceled", function () { - dialogService.getUserInput({}, { someKey: "some value" }); - mockOverlayService.createOverlay.calls.mostRecent().args[1].cancel(); - expect(mockDeferred.reject).toHaveBeenCalled(); - expect(mockDeferred.resolve).not.toHaveBeenCalled(); - }); - - it("passes back the result of user input when confirmed", function () { - var value = { someKey: 42 }; - dialogService.getUserInput({}, value); - mockOverlayService.createOverlay.calls.mostRecent().args[1].confirm(); - expect(mockDeferred.reject).not.toHaveBeenCalled(); - expect(mockDeferred.resolve).toHaveBeenCalledWith(value); - }); - - it("logs a warning when a dialog is already showing", function () { - dialogService.getUserInput({}, {}); - expect(mockLog.warn).not.toHaveBeenCalled(); - dialogService.getUserInput({}, {}); - expect(mockLog.warn).toHaveBeenCalled(); - expect(mockDeferred.reject).toHaveBeenCalled(); - }); - - it("can show multiple dialogs if prior ones are dismissed", function () { - dialogService.getUserInput({}, {}); - expect(mockLog.warn).not.toHaveBeenCalled(); - mockOverlayService.createOverlay.calls.mostRecent().args[1].confirm(); - dialogService.getUserInput({}, {}); - expect(mockLog.warn).not.toHaveBeenCalled(); - expect(mockDeferred.reject).not.toHaveBeenCalled(); - }); - - it("provides an options dialogs", function () { - var dialogModel = {}; - dialogService.getUserChoice(dialogModel); - expect(mockOverlayService.createOverlay).toHaveBeenCalledWith( - 'overlay-options', - { - dialog: dialogModel, - confirm: jasmine.any(Function), - cancel: jasmine.any(Function) - }, - 't-dialog' - ); - }); - - it("invokes the overlay service with the correct parameters when" - + " a blocking dialog is requested", function () { - var dialogModel = {}; - expect(dialogService.showBlockingMessage(dialogModel)).not.toBe(false); - expect(mockOverlayService.createOverlay).toHaveBeenCalledWith( - "overlay-blocking-message", - dialogModel, - "t-dialog-sm" - ); - }); - - it("adds a keydown event listener to the body", function () { - dialogService.getUserInput({}, {}); - expect(mockDocument.find).toHaveBeenCalledWith("body"); - expect(mockBody.on).toHaveBeenCalledWith("keydown", jasmine.any(Function)); - }); - - it("destroys the event listener when the dialog is cancelled", function () { - dialogService.getUserInput({}, {}); - mockOverlayService.createOverlay.calls.mostRecent().args[1].cancel(); - expect(mockBody.off).toHaveBeenCalledWith("keydown", jasmine.any(Function)); - }); - - it("cancels the dialog when an escape keydown event is triggered", function () { - dialogService.getUserInput({}, {}); - mockBody.on.calls.mostRecent().args[1]({ - keyCode: 27 - }); - expect(mockDeferred.reject).toHaveBeenCalled(); - expect(mockDeferred.resolve).not.toHaveBeenCalled(); - }); - - it("ignores non escape keydown events", function () { - dialogService.getUserInput({}, {}); - mockBody.on.calls.mostRecent().args[1]({ - keyCode: 13 - }); - expect(mockDeferred.reject).not.toHaveBeenCalled(); - expect(mockDeferred.resolve).not.toHaveBeenCalled(); - }); - - describe("the blocking message dialog", function () { - var dialogModel = {}; - var dialogHandle; - - beforeEach(function () { - dialogHandle = dialogService.showBlockingMessage(dialogModel); - }); - - it("returns a handle to the dialog", function () { - expect(dialogHandle).not.toBe(undefined); - }); - - it("dismissing the dialog dismisses the overlay", function () { - dialogHandle.dismiss(); - expect(mockOverlay.dismiss).toHaveBeenCalled(); - }); - - it("individual dialogs can be dismissed", function () { - var secondDialogHandle, - secondMockOverlay; - - dialogHandle.dismiss(); - - secondMockOverlay = jasmine.createSpyObj( - "overlay", - ["dismiss"] - ); - mockOverlayService.createOverlay.and.returnValue(secondMockOverlay); - secondDialogHandle = dialogService.showBlockingMessage(dialogModel); - - //Dismiss the first dialog. It should only dismiss if it - // is active - dialogHandle.dismiss(); - expect(secondMockOverlay.dismiss).not.toHaveBeenCalled(); - secondDialogHandle.dismiss(); - expect(secondMockOverlay.dismiss).toHaveBeenCalled(); - }); - }); - - }); - } -); diff --git a/platform/commonUI/dialog/test/OverlayServiceSpec.js b/platform/commonUI/dialog/test/OverlayServiceSpec.js deleted file mode 100644 index e360011a9d..0000000000 --- a/platform/commonUI/dialog/test/OverlayServiceSpec.js +++ /dev/null @@ -1,104 +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. - *****************************************************************************/ - -/** - * MCTIncudeSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../src/OverlayService"], - function (OverlayService) { - - describe("The overlay service", function () { - var mockDocument, - mockCompile, - mockRootScope, - mockBody, - mockTemplate, - mockElement, - mockScope, - mockTimeout, - overlayService; - - beforeEach(function () { - mockDocument = jasmine.createSpyObj("$document", ["find"]); - mockCompile = jasmine.createSpy("$compile"); - mockRootScope = jasmine.createSpyObj("$rootScope", ["$new"]); - mockBody = jasmine.createSpyObj("body", ["append"]); - mockTemplate = jasmine.createSpy("template"); - mockElement = jasmine.createSpyObj("element", ["remove"]); - mockScope = jasmine.createSpyObj("scope", ["$destroy"]); - mockTimeout = function (callback) { - callback(); - }; - - mockDocument.find.and.returnValue(mockBody); - mockCompile.and.returnValue(mockTemplate); - mockRootScope.$new.and.returnValue(mockScope); - mockTemplate.and.returnValue(mockElement); - - overlayService = new OverlayService( - mockDocument, - mockCompile, - mockRootScope, - mockTimeout - ); - }); - - it("prepends an mct-include to create overlays", function () { - overlayService.createOverlay("test", {}); - expect(mockCompile).toHaveBeenCalled(); - expect(mockCompile.calls.mostRecent().args[0].indexOf("mct-include")) - .not.toEqual(-1); - }); - - it("adds the templated element to the body", function () { - overlayService.createOverlay("test", {}); - expect(mockBody.append).toHaveBeenCalledWith(mockElement); - }); - - it("places the provided model/key in its template's scope", function () { - overlayService.createOverlay("test", { someKey: 42 }); - expect(mockScope.overlay).toEqual({ someKey: 42 }); - expect(mockScope.key).toEqual("test"); - - // Make sure this is actually what was rendered, too - expect(mockTemplate).toHaveBeenCalledWith(mockScope); - }); - - it("removes the prepended element on request", function () { - var overlay = overlayService.createOverlay("test", {}); - - // Verify precondition - expect(mockElement.remove).not.toHaveBeenCalled(); - expect(mockScope.$destroy).not.toHaveBeenCalled(); - - // Dismiss the overlay - overlay.dismiss(); - - // Now it should have been removed, and the scope destroyed - expect(mockElement.remove).toHaveBeenCalled(); - expect(mockScope.$destroy).toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/commonUI/edit/README.md b/platform/commonUI/edit/README.md deleted file mode 100644 index ca52b78f1a..0000000000 --- a/platform/commonUI/edit/README.md +++ /dev/null @@ -1,45 +0,0 @@ -Contains sources and resources associated with Edit mode. - -# Extensions - -# Toolbars - -Views may specify the contents of a toolbar through a `toolbar` -property in their bundle definition. This should appear as the -structure one would provide to the `mct-toolbar` directive, -except additional properties are recognized to support the -mediation between toolbar contents, user interaction, and the -current selection (as read from the `selection` property of the -view's scope.) These additional properties are: - -* `property`: Name of the property within a selected object. If, - for any given object in the selection, that field is a function, - then that function is assumed to be an accessor-mutator function - (that is, it will be called with no arguments to get, and with - an argument to set.) -* `method`: Name of a method to invoke upon a selected object when - a control is activated, e.g. on a button click. -* `exclusive`: Optional; true if this control should be considered - applicable only when all elements in the selection has - the associated property. Otherwise, only at least one member of the - current selection must have this property for the control to be shown. - -Controls in the toolbar are shown based on applicability to the -current selection. Applicability for a given member of the selection -is determined by the presence of absence of the named `property` -field. As a consequence of this, if `undefined` is a valid value for -that property, an accessor-mutator function must be used. Likewise, -if toolbar properties are meant to be view-global (as opposed to -per-selection) then the view must include some object to act as its -proxy in the current selection (in addition to whatever objects the -user will conceive of as part of the current selection), typically -with `inclusive` set to `true`. - -## Selection - -The `selection` property of a view's scope in Edit mode will be -initialized to an empty array. This array's contents may be modified -to implicitly change the contents of the toolbar based on the rules -described above. Care should be taken to modify this array in-place -instead of shadowing it (as the selection will typically -be a few scopes up the hierarchy from the view's actual scope.) diff --git a/platform/commonUI/edit/bundle.js b/platform/commonUI/edit/bundle.js deleted file mode 100644 index dc0299ce62..0000000000 --- a/platform/commonUI/edit/bundle.js +++ /dev/null @@ -1,209 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/controllers/EditActionController", - "./src/controllers/EditPanesController", - "./src/controllers/EditObjectController", - "./src/actions/EditAndComposeAction", - "./src/actions/EditAction", - "./src/actions/SaveAction", - "./src/actions/SaveAndStopEditingAction", - "./src/actions/CancelAction", - "./src/policies/EditPersistableObjectsPolicy", - "./src/representers/EditRepresenter", - "./src/capabilities/EditorCapability", - "./res/templates/library.html", - "./res/templates/edit-object.html", - "./res/templates/edit-action-buttons.html", - "./res/templates/topbar-edit.html" -], function ( - EditActionController, - EditPanesController, - EditObjectController, - EditAndComposeAction, - EditAction, - SaveAction, - SaveAndStopEditingAction, - CancelAction, - EditPersistableObjectsPolicy, - EditRepresenter, - EditorCapability, - libraryTemplate, - editObjectTemplate, - editActionButtonsTemplate, - topbarEditTemplate -) { - return { - name: "platform/commonUI/edit", - definition: { - "extensions": { - "controllers": [ - { - "key": "EditActionController", - "implementation": EditActionController, - "depends": [ - "$scope" - ] - }, - { - "key": "EditPanesController", - "implementation": EditPanesController, - "depends": [ - "$scope" - ] - }, - { - "key": "EditObjectController", - "implementation": EditObjectController, - "depends": [ - "$scope", - "$location", - "navigationService" - ] - } - ], - "actions": [ - { - "key": "compose", - "implementation": EditAndComposeAction - }, - { - "key": "edit", - "implementation": EditAction, - "depends": [ - "$location", - "navigationService", - "$log" - ], - "description": "Edit", - "category": "view-control", - "cssClass": "major icon-pencil", - "group": "action", - "priority": 10 - }, - { - "key": "save-and-stop-editing", - "category": "save", - "implementation": SaveAndStopEditingAction, - "name": "Save and Finish Editing", - "cssClass": "icon-save labeled", - "description": "Save changes made to these objects.", - "depends": [ - "dialogService", - "notificationService" - ] - }, - { - "key": "save", - "category": "save", - "implementation": SaveAction, - "name": "Save and Continue Editing", - "cssClass": "icon-save labeled", - "description": "Save changes made to these objects.", - "depends": [ - "dialogService", - "notificationService" - ] - }, - { - "key": "cancel", - "category": "conclude-editing", - "implementation": CancelAction, - // Because we use the name as label for edit buttons and mct-control buttons need - // the label to be set to undefined in order to not apply the labeled CSS rule. - "name": undefined, - "cssClass": "icon-x no-label", - "description": "Discard changes made to these objects.", - "depends": [] - } - ], - "policies": [ - { - "category": "action", - "implementation": EditPersistableObjectsPolicy, - "depends": ["openmct"] - } - ], - "templates": [ - { - "key": "edit-library", - "template": libraryTemplate - } - ], - "representations": [ - { - "key": "edit-object", - "template": editObjectTemplate, - "uses": [ - "view" - ], - "gestures": [ - "drop" - ] - }, - { - "key": "edit-action-buttons", - "template": editActionButtonsTemplate, - "uses": [ - "action" - ] - }, - { - "key": "topbar-edit", - "template": topbarEditTemplate - } - ], - "representers": [ - { - "implementation": EditRepresenter, - "depends": [ - "$log" - ] - } - ], - "capabilities": [ - { - "key": "editor", - "name": "Editor Capability", - "description": "Provides transactional editing capabilities", - "implementation": EditorCapability, - "depends": [ - "openmct" - ] - } - ], - "runs": [ - { - depends: [ - "toolbars[]", - "openmct" - ], - implementation: function (toolbars, openmct) { - toolbars.forEach(openmct.toolbars.addProvider, openmct.toolbars); - } - } - ] - } - } - }; -}); diff --git a/platform/commonUI/edit/res/templates/edit-action-buttons.html b/platform/commonUI/edit/res/templates/edit-action-buttons.html deleted file mode 100644 index 52fa2ec728..0000000000 --- a/platform/commonUI/edit/res/templates/edit-action-buttons.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/platform/commonUI/edit/res/templates/edit-object.html b/platform/commonUI/edit/res/templates/edit-object.html deleted file mode 100644 index 6304215f0d..0000000000 --- a/platform/commonUI/edit/res/templates/edit-object.html +++ /dev/null @@ -1,78 +0,0 @@ - -
    -
    -
    - - - - -
    -
    - - - - - -
    -
    -
    -
    - -
    - - - - - -
    - - -
    -
    - - - -
    diff --git a/platform/commonUI/edit/res/templates/library.html b/platform/commonUI/edit/res/templates/library.html deleted file mode 100644 index e39ca9ff1f..0000000000 --- a/platform/commonUI/edit/res/templates/library.html +++ /dev/null @@ -1,25 +0,0 @@ - - - diff --git a/platform/commonUI/edit/res/templates/topbar-edit.html b/platform/commonUI/edit/res/templates/topbar-edit.html deleted file mode 100644 index 0624e7550a..0000000000 --- a/platform/commonUI/edit/res/templates/topbar-edit.html +++ /dev/null @@ -1,39 +0,0 @@ - -
    - - -
    - - - - - -
    -
    diff --git a/platform/commonUI/edit/src/actions/CancelAction.js b/platform/commonUI/edit/src/actions/CancelAction.js deleted file mode 100644 index 83b8cf99d6..0000000000 --- a/platform/commonUI/edit/src/actions/CancelAction.js +++ /dev/null @@ -1,90 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - function () { - - /** - * The "Cancel" action; the action triggered by clicking Cancel from - * Edit Mode. Exits the editing user interface and invokes object - * capabilities to persist the changes that have been made. - * @constructor - * @memberof platform/commonUI/edit - * @implements {Action} - */ - function CancelAction(context) { - this.domainObject = context.domainObject; - } - - /** - * Cancel editing. - * - * @returns {Promise} a promise that will be fulfilled when - * cancellation has completed - */ - CancelAction.prototype.perform = function () { - var domainObject = this.domainObject; - - function returnToBrowse() { - var parent; - - //If the object existed already, navigate to refresh view - // with previous object state. - if (domainObject.getModel().persisted) { - return domainObject.getCapability("action").perform("navigate"); - } else { - //If the object was new, and user has cancelled, then - //navigate back to parent because nothing to show. - return domainObject.getCapability("location").getOriginal().then(function (original) { - parent = original.getCapability("context").getParent(); - - return parent.getCapability("action").perform("navigate"); - }); - } - } - - function cancel() { - return domainObject.getCapability("editor").finish(); - } - - //Do navigation first in order to trigger unsaved changes dialog - return returnToBrowse() - .then(cancel); - }; - - /** - * Check if this action is applicable in a given context. - * This will ensure that a domain object is present in the context, - * and that this domain object is in Edit mode. - * @returns {boolean} true if applicable - */ - CancelAction.appliesTo = function (context) { - var domainObject = (context || {}).domainObject; - - return domainObject !== undefined - && domainObject.hasCapability('editor') - && domainObject.getCapability('editor').isEditContextRoot(); - }; - - return CancelAction; - } -); diff --git a/platform/commonUI/edit/src/actions/EditAction.js b/platform/commonUI/edit/src/actions/EditAction.js deleted file mode 100644 index 1a5c620461..0000000000 --- a/platform/commonUI/edit/src/actions/EditAction.js +++ /dev/null @@ -1,101 +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. - *****************************************************************************/ - -/** - * Module defining EditAction. Created by vwoeltje on 11/14/14. - */ -define( - [], - function () { - - // A no-op action to return in the event that the action cannot - // be completed. - var NULL_ACTION = { - perform: function () { - return undefined; - } - }; - - /** - * The Edit action is performed when the user wishes to enter Edit - * mode (typically triggered by the Edit button.) This will - * show the user interface for editing (by way of a change in - * route) - * @memberof platform/commonUI/edit - * @constructor - * @implements {Action} - */ - function EditAction($location, navigationService, $log, context) { - var domainObject = (context || {}).domainObject; - - // We cannot enter Edit mode if we have no domain object to - // edit, so verify that one was defined as part of the - // context. (This is also verified in appliesTo, so this - // would indicate abnormal behavior.) - if (!domainObject) { - $log.warn([ - "No domain object to edit; ", - "edit action is not valid." - ].join("")); - - return NULL_ACTION; - } - - this.domainObject = domainObject; - this.$location = $location; - this.navigationService = navigationService; - } - - /** - * Enter edit mode. - */ - EditAction.prototype.perform = function () { - - //If this is not the currently navigated object, then navigate - // to it. - if (this.navigationService.getNavigation() !== this.domainObject) { - this.navigationService.setNavigation(this.domainObject); - } - - this.domainObject.useCapability("editor"); - }; - - /** - * Check for applicability; verify that a domain object is present - * for this action to be performed upon. - * @param {ActionContext} context the context in which this action - * will be performed; should contain a `domainObject` property - */ - EditAction.appliesTo = function (context) { - var domainObject = (context || {}).domainObject, - type = domainObject && domainObject.getCapability('type'); - - // Only allow editing of types that support it and are not already - // being edited - return type && type.hasFeature('creation') - && domainObject.hasCapability('editor') - && !domainObject.getCapability('editor').isEditContextRoot(); - }; - - return EditAction; - } -); diff --git a/platform/commonUI/edit/src/actions/EditAndComposeAction.js b/platform/commonUI/edit/src/actions/EditAndComposeAction.js deleted file mode 100644 index c3ace5ea35..0000000000 --- a/platform/commonUI/edit/src/actions/EditAndComposeAction.js +++ /dev/null @@ -1,59 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * Add one domain object to another's composition. - * @constructor - * @memberof platform/commonUI/edit - * @implements {Action} - */ - function EditAndComposeAction(context) { - this.domainObject = (context || {}).domainObject; - this.selectedObject = (context || {}).selectedObject; - } - - EditAndComposeAction.prototype.perform = function () { - var self = this, - editAction = this.domainObject.getCapability('action').getActions("edit")[0]; - - // Link these objects - function doLink() { - var composition = self.domainObject - && self.domainObject.getCapability('composition'); - - return composition && composition.add(self.selectedObject); - } - - if (editAction) { - editAction.perform(); - } - - return this.selectedObject && doLink(); - }; - - return EditAndComposeAction; - } -); diff --git a/platform/commonUI/edit/src/actions/SaveAction.js b/platform/commonUI/edit/src/actions/SaveAction.js deleted file mode 100644 index 6cfabe2c8e..0000000000 --- a/platform/commonUI/edit/src/actions/SaveAction.js +++ /dev/null @@ -1,98 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['./SaveInProgressDialog'], - function (SaveInProgressDialog) { - - /** - * The "Save" action; it invokes object capabilities to persist - * the changes that have been made. - * @constructor - * @implements {Action} - * @memberof platform/commonUI/edit - */ - function SaveAction( - dialogService, - notificationService, - context - ) { - this.domainObject = (context || {}).domainObject; - this.dialogService = dialogService; - this.notificationService = notificationService; - } - - /** - * Save changes. - * - * @returns {Promise} a promise that will be fulfilled when - * cancellation has completed - * @memberof platform/commonUI/edit.SaveAction# - */ - SaveAction.prototype.perform = function () { - var self = this, - domainObject = this.domainObject, - dialog = new SaveInProgressDialog(this.dialogService); - - // Invoke any save behavior introduced by the editor capability; - // this is introduced by EditableDomainObject which is - // used to insulate underlying objects from changes made - // during editing. - function doSave() { - return domainObject.getCapability("editor").save(); - } - - function onSuccess() { - dialog.hide(); - self.notificationService.info("Save Succeeded"); - } - - function onFailure() { - dialog.hide(); - self.notificationService.error("Save Failed"); - } - - dialog.show(); - - return doSave() - .then(onSuccess) - .catch(onFailure); - }; - - /** - * Check if this action is applicable in a given context. - * This will ensure that a domain object is present in the context, - * and that this domain object is in Edit mode. - * @returns true if applicable - */ - SaveAction.appliesTo = function (context) { - var domainObject = (context || {}).domainObject; - - return domainObject !== undefined - && domainObject.hasCapability('editor') - && domainObject.getCapability('editor').isEditContextRoot() - && domainObject.getModel().persisted !== undefined; - }; - - return SaveAction; - } -); diff --git a/platform/commonUI/edit/src/actions/SaveAndStopEditingAction.js b/platform/commonUI/edit/src/actions/SaveAndStopEditingAction.js deleted file mode 100644 index 32412bcfcc..0000000000 --- a/platform/commonUI/edit/src/actions/SaveAndStopEditingAction.js +++ /dev/null @@ -1,75 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["./SaveAction"], - function (SaveAction) { - - /** - * The "Save and Stop Editing" action performs a [Save action]{@link SaveAction} - * on the object under edit followed by exiting the edit user interface. - * @constructor - * @implements {Action} - * @memberof platform/commonUI/edit - */ - function SaveAndStopEditingAction( - dialogService, - notificationService, - context - ) { - this.context = context; - this.domainObject = (context || {}).domainObject; - this.dialogService = dialogService; - this.notificationService = notificationService; - } - - /** - * Trigger a save operation and exit edit mode. - * - * @returns {Promise} a promise that will be fulfilled when - * cancellation has completed - * @memberof platform/commonUI/edit.SaveAndStopEditingAction# - */ - SaveAndStopEditingAction.prototype.perform = function () { - var domainObject = this.domainObject, - saveAction = new SaveAction(this.dialogService, this.notificationService, this.context); - - function closeEditor() { - return domainObject.getCapability("editor").finish(); - } - - return saveAction.perform() - .then(closeEditor) - .catch(closeEditor); - }; - - /** - * Check if this action is applicable in a given context. - * This will ensure that a domain object is present in the context, - * and that this domain object is in Edit mode. - * @returns true if applicable - */ - SaveAndStopEditingAction.appliesTo = SaveAction.appliesTo; - - return SaveAndStopEditingAction; - } -); diff --git a/platform/commonUI/edit/src/actions/SaveInProgressDialog.js b/platform/commonUI/edit/src/actions/SaveInProgressDialog.js deleted file mode 100644 index ea419e7040..0000000000 --- a/platform/commonUI/edit/src/actions/SaveInProgressDialog.js +++ /dev/null @@ -1,24 +0,0 @@ -define([], function () { - function SaveInProgressDialog(dialogService) { - this.dialogService = dialogService; - this.dialog = undefined; - } - - SaveInProgressDialog.prototype.show = function () { - this.dialog = this.dialogService.showBlockingMessage({ - title: "Saving", - hint: "Do not navigate away from this page or close this browser tab while this message is displayed.", - unknownProgress: true, - severity: "info", - delay: true - }); - }; - - SaveInProgressDialog.prototype.hide = function () { - if (this.dialog) { - this.dialog.dismiss(); - } - }; - - return SaveInProgressDialog; -}); diff --git a/platform/commonUI/edit/src/capabilities/EditorCapability.js b/platform/commonUI/edit/src/capabilities/EditorCapability.js deleted file mode 100644 index db3970c14e..0000000000 --- a/platform/commonUI/edit/src/capabilities/EditorCapability.js +++ /dev/null @@ -1,64 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * A capability that implements an editing 'session' for a domain - * object. An editing session is initiated via a call to .edit(). - * Once initiated, any persist operations will be queued pending a - * subsequent call to [.save()](@link #save) or [.finish()](@link - * #finish). - * @param domainObject - * @constructor - */ - function EditorCapability( - openmct, - domainObject - ) { - this.openmct = openmct; - this.domainObject = domainObject; - } - - /** - * Determines whether this object, or any of its ancestors are - * currently being edited. - * @returns boolean - */ - EditorCapability.prototype.inEditContext = function () { - return this.openmct.editor.isEditing(); - }; - - /** - * Is this the root editing object (ie. the object that the user - * clicked 'edit' on)? - * @returns {*} - */ - EditorCapability.prototype.isEditContextRoot = function () { - return this.openmct.editor.isEditing(); - }; - - return EditorCapability; - } -); diff --git a/platform/commonUI/edit/src/controllers/EditActionController.js b/platform/commonUI/edit/src/controllers/EditActionController.js deleted file mode 100644 index f11579bbfc..0000000000 --- a/platform/commonUI/edit/src/controllers/EditActionController.js +++ /dev/null @@ -1,79 +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. - *****************************************************************************/ - -/** - * Module defining EditActionController. Created by vwoeltje on 11/17/14. - */ -define( - [], - function () { - - var SAVE_ACTION_CONTEXT = { category: 'save' }; - var OTHERS_ACTION_CONTEXT = { category: 'conclude-editing' }; - - /** - * Controller which supplies action instances for Save/Cancel. - * @memberof platform/commonUI/edit - * @constructor - */ - function EditActionController($scope) { - - function actionToMenuOption(action) { - return { - key: action, - name: action.getMetadata().name, - cssClass: action.getMetadata().cssClass - }; - } - - // Maintain all "conclude-editing" and "save" actions in the - // present context. - function updateActions() { - $scope.saveActions = $scope.action - ? $scope.action.getActions(SAVE_ACTION_CONTEXT) - : []; - - $scope.saveActionsAsMenuOptions = $scope.saveActions.map(actionToMenuOption); - - $scope.saveActionMenuClickHandler = function (clickedAction) { - clickedAction.perform(); - }; - - $scope.otherEditActions = $scope.action - ? $scope.action.getActions(OTHERS_ACTION_CONTEXT) - : []; - - // Required because Angular does not allow 'bind' - // in expressions. - $scope.actionPerformer = function (action) { - return action.perform.bind(action); - }; - } - - // Update set of actions whenever the action capability - // changes or becomes available. - $scope.$watch("action", updateActions); - } - - return EditActionController; - } -); diff --git a/platform/commonUI/edit/src/controllers/EditObjectController.js b/platform/commonUI/edit/src/controllers/EditObjectController.js deleted file mode 100644 index 595e008ac0..0000000000 --- a/platform/commonUI/edit/src/controllers/EditObjectController.js +++ /dev/null @@ -1,86 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * This bundle implements Edit mode. - * @namespace platform/commonUI/edit - */ -define( - [], - function () { - - function cancelEditing(domainObject) { - var navigatedObject = domainObject, - editorCapability = navigatedObject - && navigatedObject.getCapability("editor"); - - return editorCapability - && editorCapability.finish(); - } - - /** - * Controller which is responsible for populating the scope for - * Edit mode - * @memberof platform/commonUI/edit - * @constructor - */ - function EditObjectController($scope, $location, navigationService) { - this.scope = $scope; - var domainObject = $scope.domainObject; - - var removeCheck = navigationService - .checkBeforeNavigation(function () { - return "Continuing will cause the loss of any unsaved changes."; - }); - - $scope.$on('$destroy', function () { - removeCheck(); - cancelEditing(domainObject); - }); - - function setViewForDomainObject() { - - var locationViewKey = $location.search().view; - - function selectViewIfMatching(view) { - if (view.key === locationViewKey) { - $scope.representation = $scope.representation || {}; - $scope.representation.selected = view; - } - } - - if (locationViewKey) { - ((domainObject && domainObject.useCapability('view')) || []) - .forEach(selectViewIfMatching); - } - } - - setViewForDomainObject(); - - $scope.doAction = function (action) { - return $scope[action] && $scope[action](); - }; - } - - return EditObjectController; - } -); diff --git a/platform/commonUI/edit/src/controllers/EditPanesController.js b/platform/commonUI/edit/src/controllers/EditPanesController.js deleted file mode 100644 index 9593250a89..0000000000 --- a/platform/commonUI/edit/src/controllers/EditPanesController.js +++ /dev/null @@ -1,66 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * Supports the Library and Elements panes in Edit mode. - * @memberof platform/commonUI/edit - * @constructor - */ - function EditPanesController($scope) { - var self = this; - - // Update root object based on represented object - function updateRoot(domainObject) { - var root = self.rootDomainObject, - context = domainObject - && domainObject.getCapability('context'), - newRoot = context && context.getTrueRoot(), - oldId = root && root.getId(), - newId = newRoot && newRoot.getId(); - - // Only update if this has actually changed, - // to avoid excessive refreshing. - if (oldId !== newId) { - self.rootDomainObject = newRoot; - } - } - - // Update root when represented object changes - $scope.$watch('domainObject', updateRoot); - } - - /** - * Get the root-level domain object, as reported by the - * represented domain object. - * @returns {DomainObject} the root object - */ - EditPanesController.prototype.getRoot = function () { - return this.rootDomainObject; - }; - - return EditPanesController; - } -); diff --git a/platform/commonUI/edit/src/policies/EditPersistableObjectsPolicy.js b/platform/commonUI/edit/src/policies/EditPersistableObjectsPolicy.js deleted file mode 100644 index 5cb7f76df6..0000000000 --- a/platform/commonUI/edit/src/policies/EditPersistableObjectsPolicy.js +++ /dev/null @@ -1,57 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-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( - ['objectUtils'], - function (objectUtils) { - - /** - * Policy that prevents editing of any object from a provider that does not - * support persistence (ie. the 'save' operation). Editing is prevented - * as a subsequent save would fail, causing the loss of a user's changes. - * @param openmct - * @constructor - */ - function EditPersistableObjectsPolicy(openmct) { - this.openmct = openmct; - } - - EditPersistableObjectsPolicy.prototype.allow = function (action, context) { - var domainObject = context.domainObject; - var key = action.getMetadata().key; - var category = (context || {}).category; - - // Use category to selectively block edit from the view. Edit action - // is also invoked during the create process which should be allowed, - // because it may be saved elsewhere - if ((key === 'edit' && category === 'view-control') || key === 'properties') { - let identifier = this.openmct.objects.parseKeyString(domainObject.getId()); - - return this.openmct.objects.isPersistable(identifier); - } - - return true; - }; - - return EditPersistableObjectsPolicy; - } -); diff --git a/platform/commonUI/edit/src/representers/EditRepresenter.js b/platform/commonUI/edit/src/representers/EditRepresenter.js deleted file mode 100644 index d39ed0d897..0000000000 --- a/platform/commonUI/edit/src/representers/EditRepresenter.js +++ /dev/null @@ -1,99 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * The EditRepresenter is responsible for implementing - * representation-level behavior relevant to Edit mode. - * Specifically, this listens for changes to view configuration - * or to domain object models, and triggers persistence when - * these are detected. - * - * This is exposed as an extension of category `representers`, - * which mct-representation will utilize to add additional - * behavior to each representation. - * - * This will be called once per mct-representation directive, - * and may be reused for different domain objects and/or - * representations resulting from changes there. - * - * @memberof platform/commonUI/edit - * @implements {Representer} - * @constructor - */ - function EditRepresenter($log, $scope) { - this.$log = $log; - this.$scope = $scope; - - this.$scope.commit = this.commit.bind(this); - } - - /** - * Commit any changes made to the in-scope model to the domain object. - * Also commits any changes made to $scope.configuration to the proper - * configuration value for the current representation. - * - * @param {String} message a message to log with the commit message. - */ - EditRepresenter.prototype.commit = function (message) { - var model = this.$scope.model, - configuration = this.$scope.configuration, - domainObject = this.domainObject; - - this.$log.debug([ - "Committing ", - domainObject && domainObject.getModel().name, - "(" + (domainObject && domainObject.getId()) + "):", - message - ].join(" ")); - - if (this.domainObject) { - if (this.key && configuration) { - model.configuration = model.configuration || {}; - model.configuration[this.key] = configuration; - } - - domainObject.useCapability('mutation', function () { - return model; - }); - } - }; - - // Handle a specific representation of a specific domain object - EditRepresenter.prototype.represent = function (representation, representedObject) { - this.domainObject = representedObject; - if (representation) { - this.key = representation.key; - } else { - delete this.key; - } - }; - - // Respond to the destruction of the current representation. - EditRepresenter.prototype.destroy = function () {}; - - return EditRepresenter; - } -); diff --git a/platform/commonUI/edit/test/actions/CancelActionSpec.js b/platform/commonUI/edit/test/actions/CancelActionSpec.js deleted file mode 100644 index 54073b6036..0000000000 --- a/platform/commonUI/edit/test/actions/CancelActionSpec.js +++ /dev/null @@ -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. - *****************************************************************************/ - -define( - ["../../src/actions/CancelAction"], - function (CancelAction) { - - describe("The Cancel action", function () { - var mockDomainObject, - mockParentObject, - capabilities = {}, - parentCapabilities = {}, - actionContext, - action; - - function mockPromise(value) { - return { - then: function (callback) { - return mockPromise(callback(value)); - } - }; - } - - beforeEach(function () { - mockDomainObject = jasmine.createSpyObj( - "domainObject", - [ - "getCapability", - "hasCapability", - "getModel" - ] - ); - mockDomainObject.getModel.and.returnValue({}); - - mockParentObject = jasmine.createSpyObj( - "parentObject", - [ - "getCapability" - ] - ); - mockParentObject.getCapability.and.callFake(function (name) { - return parentCapabilities[name]; - }); - - capabilities.editor = jasmine.createSpyObj( - "editor", - ["save", "finish", "isEditContextRoot"] - ); - capabilities.action = jasmine.createSpyObj( - "actionCapability", - [ - "perform" - ] - ); - capabilities.location = jasmine.createSpyObj( - "locationCapability", - [ - "getOriginal" - ] - ); - capabilities.location.getOriginal.and.returnValue(mockPromise(mockDomainObject)); - capabilities.context = jasmine.createSpyObj( - "contextCapability", - [ - "getParent" - ] - ); - capabilities.context.getParent.and.returnValue(mockParentObject); - - parentCapabilities.action = jasmine.createSpyObj( - "actionCapability", - [ - "perform" - ] - ); - - actionContext = { - domainObject: mockDomainObject - }; - - mockDomainObject.getCapability.and.callFake(function (name) { - return capabilities[name]; - }); - - mockDomainObject.hasCapability.and.callFake(function (name) { - return Boolean(capabilities[name]); - }); - - capabilities.editor.finish.and.returnValue(mockPromise(true)); - - action = new CancelAction(actionContext); - - }); - - it("only applies to domain object that is being edited", function () { - capabilities.editor.isEditContextRoot.and.returnValue(true); - expect(CancelAction.appliesTo(actionContext)).toBeTruthy(); - expect(mockDomainObject.hasCapability).toHaveBeenCalledWith("editor"); - - capabilities.editor.isEditContextRoot.and.returnValue(false); - expect(CancelAction.appliesTo(actionContext)).toBeFalsy(); - - mockDomainObject.hasCapability.and.returnValue(false); - expect(CancelAction.appliesTo(actionContext)).toBeFalsy(); - }); - - it("invokes the editor capability's cancel functionality when" - + " performed", function () { - mockDomainObject.getModel.and.returnValue({persisted: 1}); - //Return true from navigate action - capabilities.action.perform.and.returnValue(mockPromise(true)); - action.perform(); - - // Should have called finish - expect(capabilities.editor.finish).toHaveBeenCalled(); - - // Definitely shouldn't call save! - expect(capabilities.editor.save).not.toHaveBeenCalled(); - }); - - it("navigates to object if existing using navigate action", function () { - mockDomainObject.getModel.and.returnValue({persisted: 1}); - //Return true from navigate action - capabilities.action.perform.and.returnValue(mockPromise(true)); - action.perform(); - expect(capabilities.action.perform).toHaveBeenCalledWith("navigate"); - }); - - it("navigates to parent if new using navigate action", function () { - mockDomainObject.getModel.and.returnValue({persisted: undefined}); - action.perform(); - expect(parentCapabilities.action.perform).toHaveBeenCalledWith("navigate"); - }); - }); - } -); diff --git a/platform/commonUI/edit/test/actions/EditActionSpec.js b/platform/commonUI/edit/test/actions/EditActionSpec.js deleted file mode 100644 index ece393a924..0000000000 --- a/platform/commonUI/edit/test/actions/EditActionSpec.js +++ /dev/null @@ -1,108 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/actions/EditAction"], - function (EditAction) { - - describe("The Edit action", function () { - var mockLocation, - mockNavigationService, - mockLog, - mockDomainObject, - mockType, - mockEditor, - actionContext, - capabilities, - action; - - beforeEach(function () { - mockLocation = jasmine.createSpyObj( - "$location", - ["path"] - ); - mockNavigationService = jasmine.createSpyObj( - "navigationService", - ["setNavigation", "getNavigation", "addListener", "removeListener"] - ); - mockLog = jasmine.createSpyObj( - "$log", - ["error", "warn", "info", "debug"] - ); - mockDomainObject = jasmine.createSpyObj( - "domainObject", - ["getId", "getModel", "getCapability", "hasCapability", "useCapability"] - ); - mockType = jasmine.createSpyObj( - "type", - ["hasFeature"] - ); - mockEditor = jasmine.createSpyObj( - "editorCapability", - ["edit", "isEditContextRoot"] - ); - - capabilities = { - type: mockType, - editor: mockEditor - }; - - mockDomainObject.getCapability.and.callFake(function (name) { - return capabilities[name]; - }); - mockDomainObject.hasCapability.and.returnValue(true); - mockType.hasFeature.and.returnValue(true); - - actionContext = { domainObject: mockDomainObject }; - - action = new EditAction( - mockLocation, - mockNavigationService, - mockLog, - actionContext - ); - }); - - it("is only applicable when an editable domain object is present", function () { - expect(EditAction.appliesTo(actionContext)).toBeTruthy(); - expect(EditAction.appliesTo({})).toBeFalsy(); - - expect(mockDomainObject.hasCapability).toHaveBeenCalledWith('editor'); - // Should have checked for creatability - expect(mockType.hasFeature).toHaveBeenCalledWith('creation'); - }); - - it("is only applicable to objects not already in edit mode", function () { - mockEditor.isEditContextRoot.and.returnValue(false); - expect(EditAction.appliesTo(actionContext)).toBe(true); - mockEditor.isEditContextRoot.and.returnValue(true); - expect(EditAction.appliesTo(actionContext)).toBe(false); - }); - - it ("invokes the Edit capability on the object", function () { - action.perform(); - expect(mockDomainObject.useCapability).toHaveBeenCalledWith("editor"); - }); - - }); - } -); diff --git a/platform/commonUI/edit/test/actions/EditAndComposeActionSpec.js b/platform/commonUI/edit/test/actions/EditAndComposeActionSpec.js deleted file mode 100644 index 049e3db26d..0000000000 --- a/platform/commonUI/edit/test/actions/EditAndComposeActionSpec.js +++ /dev/null @@ -1,119 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/actions/EditAndComposeAction"], - function (EditAndComposeAction) { - - describe("The Link action", function () { - var mockDomainObject, - mockParent, - mockContext, - mockComposition, - mockActionCapability, - mockEditAction, - mockType, - actionContext, - model, - capabilities, - action; - - function mockPromise(value) { - return { - then: function (callback) { - return mockPromise(callback(value)); - } - }; - } - - beforeEach(function () { - mockDomainObject = jasmine.createSpyObj( - "domainObject", - ["getId", "getCapability"] - ); - mockParent = { - getModel: function () { - return model; - }, - getCapability: function (k) { - return capabilities[k]; - }, - useCapability: function (k, v) { - return capabilities[k].invoke(v); - } - }; - mockContext = jasmine.createSpyObj("context", ["getParent"]); - mockComposition = jasmine.createSpyObj("composition", ["invoke", "add"]); - mockType = jasmine.createSpyObj("type", ["hasFeature", "getKey"]); - mockActionCapability = jasmine.createSpyObj("actionCapability", ["getActions"]); - mockEditAction = jasmine.createSpyObj("editAction", ["perform"]); - - mockDomainObject.getId.and.returnValue("test"); - mockDomainObject.getCapability.and.returnValue(mockContext); - mockContext.getParent.and.returnValue(mockParent); - mockType.hasFeature.and.returnValue(true); - mockType.getKey.and.returnValue("layout"); - mockComposition.invoke.and.returnValue(mockPromise(true)); - mockComposition.add.and.returnValue(mockPromise(true)); - mockActionCapability.getActions.and.returnValue([]); - - capabilities = { - composition: mockComposition, - action: mockActionCapability, - type: mockType - }; - model = { - composition: ["a", "b", "c"] - }; - - actionContext = { - domainObject: mockParent, - selectedObject: mockDomainObject - }; - - action = new EditAndComposeAction(actionContext); - }); - - it("adds to the parent's composition when performed", function () { - action.perform(); - expect(mockComposition.add) - .toHaveBeenCalledWith(mockDomainObject); - }); - - it("enables edit mode for objects that have an edit action", function () { - mockActionCapability.getActions.and.returnValue([mockEditAction]); - action.perform(); - expect(mockEditAction.perform).toHaveBeenCalled(); - }); - - it("Does not enable edit mode for objects that do not have an" - + " edit action", function () { - mockActionCapability.getActions.and.returnValue([]); - action.perform(); - expect(mockEditAction.perform).not.toHaveBeenCalled(); - expect(mockComposition.add) - .toHaveBeenCalledWith(mockDomainObject); - }); - - }); - } -); diff --git a/platform/commonUI/edit/test/actions/SaveActionSpec.js b/platform/commonUI/edit/test/actions/SaveActionSpec.js deleted file mode 100644 index 1cd2643424..0000000000 --- a/platform/commonUI/edit/test/actions/SaveActionSpec.js +++ /dev/null @@ -1,159 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/actions/SaveAction"], - function (SaveAction) { - - describe("The Save action", function () { - var mockDomainObject, - mockEditorCapability, - actionContext, - mockDialogService, - mockNotificationService, - mockActionCapability, - capabilities = {}, - action; - - function mockPromise(value) { - return { - then: function (callback) { - return mockPromise(callback(value)); - }, - catch: function (callback) { - return mockPromise(callback(value)); - } - }; - } - - beforeEach(function () { - mockDomainObject = jasmine.createSpyObj( - "domainObject", - [ - "getCapability", - "hasCapability", - "getModel", - "getOriginalObject" - ] - ); - mockEditorCapability = jasmine.createSpyObj( - "editor", - ["save", "isEditContextRoot"] - ); - mockActionCapability = jasmine.createSpyObj( - "actionCapability", - ["perform"] - ); - capabilities.editor = mockEditorCapability; - capabilities.action = mockActionCapability; - - actionContext = { - domainObject: mockDomainObject - }; - - mockDialogService = jasmine.createSpyObj( - "dialogService", - ["showBlockingMessage"] - ); - - mockNotificationService = jasmine.createSpyObj( - "notificationService", - ["info", "error"] - ); - - mockDomainObject.hasCapability.and.returnValue(true); - mockDomainObject.getCapability.and.callFake(function (capability) { - return capabilities[capability]; - }); - mockDomainObject.getModel.and.returnValue({persisted: 0}); - mockEditorCapability.save.and.returnValue(mockPromise(true)); - mockEditorCapability.isEditContextRoot.and.returnValue(true); - - action = new SaveAction(mockDialogService, mockNotificationService, actionContext); - }); - - it("only applies to domain object with an editor capability", function () { - expect(SaveAction.appliesTo(actionContext)).toBe(true); - expect(mockDomainObject.hasCapability).toHaveBeenCalledWith("editor"); - - mockDomainObject.hasCapability.and.returnValue(false); - mockDomainObject.getCapability.and.returnValue(undefined); - expect(SaveAction.appliesTo(actionContext)).toBe(false); - }); - - it("only applies to domain object that has already been persisted", - function () { - mockDomainObject.getModel.and.returnValue({persisted: undefined}); - expect(SaveAction.appliesTo(actionContext)).toBe(false); - }); - - it("uses the editor capability to save the object", - function () { - action.perform(); - expect(mockEditorCapability.save).toHaveBeenCalled(); - }); - - describe("in order to keep the user in the loop", function () { - var mockDialogHandle; - - beforeEach(function () { - mockDialogHandle = jasmine.createSpyObj("dialogHandle", ["dismiss"]); - mockDialogService.showBlockingMessage.and.returnValue(mockDialogHandle); - }); - - it("shows a dialog while saving", function () { - mockEditorCapability.save.and.returnValue(new Promise(function () { - })); - action.perform(); - expect(mockDialogService.showBlockingMessage).toHaveBeenCalled(); - expect(mockDialogHandle.dismiss).not.toHaveBeenCalled(); - }); - - it("hides the dialog when saving is complete", function () { - action.perform(); - expect(mockDialogService.showBlockingMessage).toHaveBeenCalled(); - expect(mockDialogHandle.dismiss).toHaveBeenCalled(); - }); - - it("notifies if saving succeeded", function () { - var mockCallback = jasmine.createSpy("callback"); - mockEditorCapability.save.and.returnValue(Promise.resolve()); - - return action.perform().then(mockCallback).then(function () { - expect(mockNotificationService.info).toHaveBeenCalled(); - expect(mockNotificationService.error).not.toHaveBeenCalled(); - }); - }); - - it("notifies if saving failed", function () { - var mockCallback = jasmine.createSpy("callback"); - mockEditorCapability.save.and.returnValue(Promise.reject("some failure reason")); - - return action.perform().then(mockCallback).then(function () { - expect(mockNotificationService.error).toHaveBeenCalled(); - expect(mockNotificationService.info).not.toHaveBeenCalled(); - }); - }); - }); - }); - } -); diff --git a/platform/commonUI/edit/test/actions/SaveAndStopEditingActionSpec.js b/platform/commonUI/edit/test/actions/SaveAndStopEditingActionSpec.js deleted file mode 100644 index c4eb1058ae..0000000000 --- a/platform/commonUI/edit/test/actions/SaveAndStopEditingActionSpec.js +++ /dev/null @@ -1,128 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/actions/SaveAndStopEditingAction"], - function (SaveAndStopEditingAction) { - - describe("The Save and Stop Editing action", function () { - - // Some mocks appear unused because the - // underlying SaveAction that this action - // depends on is not mocked, so we mock some - // of SaveAction's own dependencies to make - // it run. - var mockDomainObject, - mockEditorCapability, - actionContext, - dialogService, - notificationService, - mockActionCapability, - capabilities = {}, - action; - - function mockPromise(value) { - return { - then: function (callback) { - return mockPromise(callback(value)); - }, - catch: function (callback) { - return mockPromise(callback(value)); - } - }; - } - - beforeEach(function () { - mockDomainObject = jasmine.createSpyObj( - "domainObject", - [ - "getCapability", - "hasCapability", - "getModel", - "getOriginalObject" - ] - ); - mockEditorCapability = jasmine.createSpyObj( - "editor", - ["save", "finish", "isEditContextRoot"] - ); - mockActionCapability = jasmine.createSpyObj( - "actionCapability", - ["perform"] - ); - capabilities.editor = mockEditorCapability; - capabilities.action = mockActionCapability; - - actionContext = { - domainObject: mockDomainObject - }; - dialogService = jasmine.createSpyObj( - "dialogService", - ["showBlockingMessage"] - ); - - notificationService = jasmine.createSpyObj( - "notificationService", - ["info", "error"] - ); - - mockDomainObject.hasCapability.and.returnValue(true); - mockDomainObject.getCapability.and.callFake(function (capability) { - return capabilities[capability]; - }); - mockDomainObject.getModel.and.returnValue({ persisted: 0 }); - mockEditorCapability.save.and.returnValue(mockPromise(true)); - mockEditorCapability.isEditContextRoot.and.returnValue(true); - - action = new SaveAndStopEditingAction(dialogService, notificationService, actionContext); - }); - - it("only applies to domain object with an editor capability", function () { - expect(SaveAndStopEditingAction.appliesTo(actionContext)).toBe(true); - expect(mockDomainObject.hasCapability).toHaveBeenCalledWith("editor"); - - mockDomainObject.hasCapability.and.returnValue(false); - mockDomainObject.getCapability.and.returnValue(undefined); - expect(SaveAndStopEditingAction.appliesTo(actionContext)).toBe(false); - }); - - it("only applies to domain object that has already been persisted", function () { - mockDomainObject.getModel.and.returnValue({ persisted: undefined }); - expect(SaveAndStopEditingAction.appliesTo(actionContext)).toBe(false); - }); - - it("does not close the editor before completing the save", function () { - mockEditorCapability.save.and.returnValue(new Promise(function () { - })); - action.perform(); - expect(mockEditorCapability.save).toHaveBeenCalled(); - expect(mockEditorCapability.finish).not.toHaveBeenCalled(); - }); - - it("closes the editor after saving", function () { - action.perform(); - expect(mockEditorCapability.save).toHaveBeenCalled(); - expect(mockEditorCapability.finish).toHaveBeenCalled(); - }); - }); - } -); diff --git a/platform/commonUI/edit/test/controllers/EditActionControllerSpec.js b/platform/commonUI/edit/test/controllers/EditActionControllerSpec.js deleted file mode 100644 index 7156cab241..0000000000 --- a/platform/commonUI/edit/test/controllers/EditActionControllerSpec.js +++ /dev/null @@ -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. - *****************************************************************************/ - -define( - ["../../src/controllers/EditActionController"], - function (EditActionController) { - - describe("The Edit Action controller", function () { - var mockSaveActionMetadata = { - name: "mocked-save-action", - cssClass: "mocked-save-action-css" - }; - - function fakeGetActions(actionContext) { - if (actionContext.category === "save") { - var mockedSaveActions = [ - jasmine.createSpyObj("mockSaveAction", ["getMetadata", "perform"]), - jasmine.createSpyObj("mockSaveAction", ["getMetadata", "perform"]) - ]; - mockedSaveActions.forEach(function (action) { - action.getMetadata.and.returnValue(mockSaveActionMetadata); - }); - - return mockedSaveActions; - } else if (actionContext.category === "conclude-editing") { - return ["a", "b", "c"]; - } else { - throw "EditActionController uses a context that's not covered by tests."; - } - } - - var mockScope, - mockActions, - controller; - - beforeEach(function () { - mockActions = jasmine.createSpyObj("action", ["getActions"]); - mockActions.getActions.and.callFake(fakeGetActions); - mockScope = jasmine.createSpyObj("$scope", ["$watch"]); - mockScope.action = mockActions; - controller = new EditActionController(mockScope); - }); - - function makeControllerUpdateActions() { - mockScope.$watch.calls.mostRecent().args[1](); - } - - it("watches scope that may change applicable actions", function () { - // The action capability - expect(mockScope.$watch).toHaveBeenCalledWith( - "action", - jasmine.any(Function) - ); - }); - - it("populates the scope with 'save' actions", function () { - makeControllerUpdateActions(); - expect(mockScope.saveActions.length).toEqual(2); - }); - - it("converts 'save' actions to their menu counterparts", function () { - makeControllerUpdateActions(); - var menuOptions = mockScope.saveActionsAsMenuOptions; - - expect(menuOptions.length).toEqual(2); - expect(menuOptions[0].key).toEqual(mockScope.saveActions[0]); - expect(menuOptions[1].key).toEqual(mockScope.saveActions[1]); - menuOptions.forEach(function (option) { - expect(option.name).toEqual(mockSaveActionMetadata.name); - expect(option.cssClass).toEqual(mockSaveActionMetadata.cssClass); - }); - }); - - it("uses a click handler to perform the clicked action", function () { - makeControllerUpdateActions(); - var sampleSaveAction = mockScope.saveActions[0]; - mockScope.saveActionMenuClickHandler(sampleSaveAction); - expect(sampleSaveAction.perform).toHaveBeenCalled(); - }); - - it("populates the scope with other editing actions", function () { - makeControllerUpdateActions(); - expect(mockScope.otherEditActions).toEqual(["a", "b", "c"]); - }); - }); - } -); diff --git a/platform/commonUI/edit/test/controllers/EditObjectControllerSpec.js b/platform/commonUI/edit/test/controllers/EditObjectControllerSpec.js deleted file mode 100644 index e63b8f2db9..0000000000 --- a/platform/commonUI/edit/test/controllers/EditObjectControllerSpec.js +++ /dev/null @@ -1,138 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/controllers/EditObjectController"], - function (EditObjectController) { - - describe("The Edit Object controller", function () { - var mockScope, - mockObject, - testViews, - mockEditorCapability, - mockLocation, - mockNavigationService, - removeCheck, - mockStatusCapability, - mockCapabilities, - controller; - - beforeEach(function () { - mockScope = jasmine.createSpyObj( - "$scope", - ["$on", "$watch"] - ); - mockObject = jasmine.createSpyObj( - "domainObject", - ["getId", "getModel", "getCapability", "hasCapability", "useCapability"] - ); - mockEditorCapability = jasmine.createSpyObj( - "mockEditorCapability", - ["isEditContextRoot", "dirty", "finish"] - ); - mockStatusCapability = jasmine.createSpyObj('statusCapability', - ["get"] - ); - - mockCapabilities = { - "editor": mockEditorCapability, - "status": mockStatusCapability - }; - - mockLocation = jasmine.createSpyObj('$location', - ["search"] - ); - mockLocation.search.and.returnValue({"view": "fixed"}); - mockNavigationService = jasmine.createSpyObj('navigationService', - ["checkBeforeNavigation"] - ); - - removeCheck = jasmine.createSpy('removeCheck'); - mockNavigationService.checkBeforeNavigation.and.returnValue(removeCheck); - - mockObject.getId.and.returnValue("test"); - mockObject.getModel.and.returnValue({ name: "Test object" }); - mockObject.getCapability.and.callFake(function (key) { - return mockCapabilities[key]; - }); - - testViews = [ - { key: 'abc' }, - { - key: 'def', - someKey: 'some value' - }, - { key: 'xyz' } - ]; - - mockObject.useCapability.and.callFake(function (c) { - return (c === 'view') && testViews; - }); - mockLocation.search.and.returnValue({ view: 'def' }); - - mockScope.domainObject = mockObject; - - controller = new EditObjectController( - mockScope, - mockLocation, - mockNavigationService - ); - }); - - it("adds a check before navigation", function () { - expect(mockNavigationService.checkBeforeNavigation) - .toHaveBeenCalledWith(jasmine.any(Function)); - - var checkFn = mockNavigationService.checkBeforeNavigation.calls.mostRecent().args[0]; - - mockEditorCapability.isEditContextRoot.and.returnValue(false); - mockEditorCapability.dirty.and.returnValue(false); - - expect(checkFn()).toBe("Continuing will cause the loss of any unsaved changes."); - - mockEditorCapability.isEditContextRoot.and.returnValue(true); - expect(checkFn()).toBe("Continuing will cause the loss of any unsaved changes."); - - mockEditorCapability.dirty.and.returnValue(true); - expect(checkFn()) - .toBe("Continuing will cause the loss of any unsaved changes."); - - }); - - it("cleans up on destroy", function () { - expect(mockScope.$on) - .toHaveBeenCalledWith("$destroy", jasmine.any(Function)); - - mockScope.$on.calls.mostRecent().args[1](); - - expect(mockEditorCapability.finish).toHaveBeenCalled(); - expect(removeCheck).toHaveBeenCalled(); - }); - - it("sets the active view from query parameters", function () { - expect(mockScope.representation.selected) - .toEqual(testViews[1]); - }); - - }); - } -); diff --git a/platform/commonUI/edit/test/controllers/EditPanesControllerSpec.js b/platform/commonUI/edit/test/controllers/EditPanesControllerSpec.js deleted file mode 100644 index 76d6532653..0000000000 --- a/platform/commonUI/edit/test/controllers/EditPanesControllerSpec.js +++ /dev/null @@ -1,114 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/controllers/EditPanesController"], - function (EditPanesController) { - - describe("The Edit Panes controller", function () { - var mockScope, - mockDomainObject, - mockContext, - controller; - - beforeEach(function () { - mockScope = jasmine.createSpyObj("$scope", ["$watch"]); - mockDomainObject = jasmine.createSpyObj( - 'domainObject', - ['getId', 'getCapability'] - ); - mockContext = jasmine.createSpyObj( - 'context', - ['getTrueRoot'] - ); - - mockDomainObject.getId.and.returnValue('test-id'); - mockDomainObject.getCapability.and.returnValue(mockContext); - - // Return a new instance of the root object each time - mockContext.getTrueRoot.and.callFake(function () { - var mockRoot = jasmine.createSpyObj('root', ['getId']); - mockRoot.getId.and.returnValue('root-id'); - - return mockRoot; - }); - - controller = new EditPanesController(mockScope); - }); - - it("watches for the domain object in view", function () { - expect(mockScope.$watch).toHaveBeenCalledWith( - "domainObject", - jasmine.any(Function) - ); - }); - - it("exposes the root object found via the object's context capability", function () { - mockScope.$watch.calls.mostRecent().args[1](mockDomainObject); - - // Verify that the correct capability was used - expect(mockDomainObject.getCapability) - .toHaveBeenCalledWith('context'); - - // Should have exposed the root from getRoot - expect(controller.getRoot().getId()).toEqual('root-id'); - }); - - it("preserves the same root instance to avoid excessive refreshing", function () { - var firstRoot; - // Expose the domain object - mockScope.$watch.calls.mostRecent().args[1](mockDomainObject); - firstRoot = controller.getRoot(); - // Update! - mockScope.$watch.calls.mostRecent().args[1](mockDomainObject); - // Should still have the same object instance, to avoid - // triggering the watch used by the template we're supporting - expect(controller.getRoot()).toBe(firstRoot); - }); - - // Complements the test above; the object pointed to should change - // when the actual root has changed (detected by identifier) - it("updates the root when it changes", function () { - var firstRoot; - // Expose the domain object - mockScope.$watch.calls.mostRecent().args[1](mockDomainObject); - firstRoot = controller.getRoot(); - - // Change the exposed root - mockContext.getTrueRoot.and.callFake(function () { - var mockRoot = jasmine.createSpyObj('root', ['getId']); - mockRoot.getId.and.returnValue('other-root-id'); - - return mockRoot; - }); - - // Update! - mockScope.$watch.calls.mostRecent().args[1](mockDomainObject); - - // Should still have the same object instance, to avoid - // triggering the watch used by the template we're supporting - expect(controller.getRoot()).not.toBe(firstRoot); - expect(controller.getRoot().getId()).toEqual('other-root-id'); - }); - }); - } -); diff --git a/platform/commonUI/edit/test/policies/EditPersistableObjectsSpec.js b/platform/commonUI/edit/test/policies/EditPersistableObjectsSpec.js deleted file mode 100644 index 8213c6253d..0000000000 --- a/platform/commonUI/edit/test/policies/EditPersistableObjectsSpec.js +++ /dev/null @@ -1,102 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-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/policies/EditPersistableObjectsPolicy"], - function (EditPersistableObjectsPolicy) { - - describe("The Edit persistable objects policy", function () { - var mockDomainObject, - mockEditAction, - mockPropertiesAction, - mockOtherAction, - mockAPI, - mockObjectAPI, - testContext, - policy; - - beforeEach(function () { - mockDomainObject = jasmine.createSpyObj( - 'domainObject', - [ - 'getId' - ] - ); - - mockObjectAPI = jasmine.createSpyObj('objectAPI', [ - 'isPersistable', - 'parseKeyString' - ]); - - mockAPI = { - objects: mockObjectAPI - }; - - mockEditAction = jasmine.createSpyObj('edit', ['getMetadata']); - mockPropertiesAction = jasmine.createSpyObj('properties', ['getMetadata']); - mockOtherAction = jasmine.createSpyObj('other', ['getMetadata']); - - mockEditAction.getMetadata.and.returnValue({ key: 'edit' }); - mockPropertiesAction.getMetadata.and.returnValue({ key: 'properties' }); - mockOtherAction.getMetadata.and.returnValue({key: 'other'}); - - mockDomainObject.getId.and.returnValue('test:testId'); - - testContext = { - domainObject: mockDomainObject, - category: 'view-control' - }; - - policy = new EditPersistableObjectsPolicy(mockAPI); - }); - - it("Applies to edit action", function () { - expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled(); - - policy.allow(mockEditAction, testContext); - expect(mockObjectAPI.isPersistable).toHaveBeenCalled(); - }); - - it("Applies to properties action", function () { - expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled(); - - policy.allow(mockPropertiesAction, testContext); - expect(mockObjectAPI.isPersistable).toHaveBeenCalled(); - }); - - it("does not apply to other actions", function () { - expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled(); - - policy.allow(mockOtherAction, testContext); - expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled(); - }); - - it("Tests object provider for editability", function () { - mockObjectAPI.isPersistable.and.returnValue(false); - expect(policy.allow(mockEditAction, testContext)).toBe(false); - expect(mockObjectAPI.isPersistable).toHaveBeenCalled(); - mockObjectAPI.isPersistable.and.returnValue(true); - expect(policy.allow(mockEditAction, testContext)).toBe(true); - }); - }); - } -); diff --git a/platform/commonUI/edit/test/representers/EditRepresenterSpec.js b/platform/commonUI/edit/test/representers/EditRepresenterSpec.js deleted file mode 100644 index a9d9657a4d..0000000000 --- a/platform/commonUI/edit/test/representers/EditRepresenterSpec.js +++ /dev/null @@ -1,87 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - '../../src/representers/EditRepresenter' -], function ( - EditRepresenter -) { - describe('EditRepresenter', function () { - var $log, - $scope, - representer; - - beforeEach(function () { - $log = jasmine.createSpyObj('$log', ['debug']); - $scope = {}; - representer = new EditRepresenter($log, $scope); - }); - - it('injects a commit function in scope', function () { - expect($scope.commit).toEqual(jasmine.any(Function)); - }); - - describe('representation', function () { - var domainObject, - representation; - - beforeEach(function () { - domainObject = jasmine.createSpyObj('domainObject', [ - 'getId', - 'getModel', - 'useCapability' - ]); - - domainObject.getId.and.returnValue('anId'); - domainObject.getModel.and.returnValue({name: 'anObject'}); - - representation = { - key: 'someRepresentation' - }; - $scope.model = {name: 'anotherName'}; - $scope.configuration = {some: 'config'}; - representer.represent(representation, domainObject); - }); - - it('logs a message when commiting', function () { - $scope.commit('Test Message'); - expect($log.debug) - .toHaveBeenCalledWith('Committing anObject (anId): Test Message'); - }); - - it('mutates the object when committing', function () { - $scope.commit('Test Message'); - - expect(domainObject.useCapability) - .toHaveBeenCalledWith('mutation', jasmine.any(Function)); - - var mutateValue = domainObject.useCapability.calls.all()[0].args[1](); - - expect(mutateValue.configuration.someRepresentation) - .toEqual({some: 'config'}); - expect(mutateValue.name).toEqual('anotherName'); - }); - - }); - - }); -}); diff --git a/platform/commonUI/general/README.md b/platform/commonUI/general/README.md deleted file mode 100644 index 544baf08bc..0000000000 --- a/platform/commonUI/general/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Directives - -* `mct-scroll-x` is an attribute whose value is an assignable - Angular expression. This two-way binds that expression to the - horizontal scroll state of the element on which it is applied. -* `mct-scroll-y` is an attribute whose value is an assignable - Angular expression. This two-way binds that expression to the - vertical scroll state of the element on which it is applied. diff --git a/platform/commonUI/general/bundle.js b/platform/commonUI/general/bundle.js deleted file mode 100644 index cee38b124a..0000000000 --- a/platform/commonUI/general/bundle.js +++ /dev/null @@ -1,529 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/services/UrlService", - "./src/services/PopupService", - "./src/SplashScreenManager", - "./src/StyleSheetLoader", - "./src/controllers/TimeRangeController", - "./src/controllers/DateTimePickerController", - "./src/controllers/DateTimeFieldController", - "./src/controllers/TreeNodeController", - "./src/controllers/ActionGroupController", - "./src/controllers/ToggleController", - "./src/controllers/ClickAwayController", - "./src/controllers/ViewSwitcherController", - "./src/controllers/GetterSetterController", - "./src/controllers/SelectorController", - "./src/controllers/ObjectInspectorController", - "./src/controllers/BannerController", - "./src/directives/MCTContainer", - "./src/directives/MCTDrag", - "./src/directives/MCTSelectable", - "./src/directives/MCTClickElsewhere", - "./src/directives/MCTResize", - "./src/directives/MCTPopup", - "./src/directives/MCTScroll", - "./src/directives/MCTSplitPane", - "./src/directives/MCTSplitter", - "./src/directives/MCTTree", - "./src/directives/MCTIndicators", - "./src/filters/ReverseFilter", - "./res/templates/bottombar.html", - "./res/templates/controls/action-button.html", - "./res/templates/controls/input-filter.html", - "./res/templates/angular-indicator.html", - "./res/templates/message-banner.html", - "./res/templates/progress-bar.html", - "./res/templates/controls/time-controller.html", - "./res/templates/containers/accordion.html", - "./res/templates/subtree.html", - "./res/templates/tree.html", - "./res/templates/tree-node.html", - "./res/templates/label.html", - "./res/templates/controls/action-group.html", - "./res/templates/controls/switcher.html", - "./res/templates/object-inspector.html", - "./res/templates/controls/selector.html", - "./res/templates/controls/datetime-picker.html", - "./res/templates/controls/datetime-field.html" -], function ( - UrlService, - PopupService, - SplashScreenManager, - StyleSheetLoader, - TimeRangeController, - DateTimePickerController, - DateTimeFieldController, - TreeNodeController, - ActionGroupController, - ToggleController, - ClickAwayController, - ViewSwitcherController, - GetterSetterController, - SelectorController, - ObjectInspectorController, - BannerController, - MCTContainer, - MCTDrag, - MCTSelectable, - MCTClickElsewhere, - MCTResize, - MCTPopup, - MCTScroll, - MCTSplitPane, - MCTSplitter, - MCTTree, - MCTIndicators, - ReverseFilter, - bottombarTemplate, - actionButtonTemplate, - inputFilterTemplate, - indicatorTemplate, - messageBannerTemplate, - progressBarTemplate, - timeControllerTemplate, - accordionTemplate, - subtreeTemplate, - treeTemplate, - treeNodeTemplate, - labelTemplate, - actionGroupTemplate, - switcherTemplate, - objectInspectorTemplate, - selectorTemplate, - datetimePickerTemplate, - datetimeFieldTemplate -) { - - return { - name: "platform/commonUI/general", - definition: { - "name": "General UI elements", - "description": "General UI elements, meant to be reused across modes", - "resources": "res", - "extensions": { - "services": [ - { - "key": "urlService", - "implementation": UrlService, - "depends": [ - "$location" - ] - }, - { - "key": "popupService", - "implementation": PopupService, - "depends": [ - "$document", - "$window" - ] - } - ], - "runs": [ - { - "implementation": StyleSheetLoader, - "depends": [ - "stylesheets[]", - "$document", - "THEME", - "ASSETS_PATH" - ] - }, - { - "implementation": SplashScreenManager, - "depends": [ - "$document" - ] - } - ], - "filters": [ - { - "implementation": ReverseFilter, - "key": "reverse" - } - ], - "templates": [ - { - "key": "bottombar", - "template": bottombarTemplate - }, - { - "key": "action-button", - "template": actionButtonTemplate - }, - { - "key": "input-filter", - "template": inputFilterTemplate - }, - { - "key": "indicator", - "template": indicatorTemplate - }, - { - "key": "message-banner", - "template": messageBannerTemplate - }, - { - "key": "progress-bar", - "template": progressBarTemplate - }, - { - "key": "time-controller", - "template": timeControllerTemplate - } - ], - "controllers": [ - { - "key": "TimeRangeController", - "implementation": TimeRangeController, - "depends": [ - "$scope", - "$timeout", - "formatService", - "DEFAULT_TIME_FORMAT", - "now" - ] - }, - { - "key": "DateTimePickerController", - "implementation": DateTimePickerController, - "depends": [ - "$scope", - "now" - ] - }, - { - "key": "DateTimeFieldController", - "implementation": DateTimeFieldController, - "depends": [ - "$scope", - "formatService", - "DEFAULT_TIME_FORMAT" - ] - }, - { - "key": "TreeNodeController", - "implementation": TreeNodeController, - "depends": [ - "$scope", - "$timeout", - "navigationService" - ] - }, - { - "key": "ActionGroupController", - "implementation": ActionGroupController, - "depends": [ - "$scope" - ] - }, - { - "key": "ToggleController", - "implementation": ToggleController - }, - { - "key": "ClickAwayController", - "implementation": ClickAwayController, - "depends": [ - "$document", - "$timeout" - ] - }, - { - "key": "ViewSwitcherController", - "implementation": ViewSwitcherController, - "depends": [ - "$scope", - "$timeout" - ] - }, - { - "key": "GetterSetterController", - "implementation": GetterSetterController, - "depends": [ - "$scope" - ] - }, - { - "key": "SelectorController", - "implementation": SelectorController, - "depends": [ - "objectService", - "$scope" - ] - }, - { - "key": "ObjectInspectorController", - "implementation": ObjectInspectorController, - "depends": [ - "$scope", - "objectService" - ] - }, - { - "key": "BannerController", - "implementation": BannerController, - "depends": [ - "$scope", - "notificationService", - "dialogService" - ] - } - ], - "directives": [ - { - "key": "mctContainer", - "implementation": MCTContainer, - "depends": [ - "containers[]" - ] - }, - { - "key": "mctDrag", - "implementation": MCTDrag, - "depends": [ - "$document", - "agentService" - ] - }, - { - "key": "mctSelectable", - "implementation": MCTSelectable, - "depends": [ - "openmct" - ] - }, - { - "key": "mctClickElsewhere", - "implementation": MCTClickElsewhere, - "depends": [ - "$document" - ] - }, - { - "key": "mctResize", - "implementation": MCTResize, - "depends": [ - "$timeout" - ] - }, - { - "key": "mctPopup", - "implementation": MCTPopup, - "depends": [ - "$compile", - "popupService" - ] - }, - { - "key": "mctScrollX", - "implementation": MCTScroll, - "depends": [ - "$parse", - "MCT_SCROLL_X_PROPERTY", - "MCT_SCROLL_X_ATTRIBUTE" - ] - }, - { - "key": "mctScrollY", - "implementation": MCTScroll, - "depends": [ - "$parse", - "MCT_SCROLL_Y_PROPERTY", - "MCT_SCROLL_Y_ATTRIBUTE" - ] - }, - { - "key": "mctSplitPane", - "implementation": MCTSplitPane, - "depends": [ - "$parse", - "$log", - "$interval", - "$window" - ] - }, - { - "key": "mctSplitter", - "implementation": MCTSplitter - }, - { - "key": "mctTree", - "implementation": MCTTree, - "depends": ['gestureService', 'openmct'] - }, - { - "key": "mctIndicators", - "implementation": MCTIndicators, - "depends": ['openmct'] - } - ], - "constants": [ - { - "key": "MCT_SCROLL_X_PROPERTY", - "value": "scrollLeft" - }, - { - "key": "MCT_SCROLL_X_ATTRIBUTE", - "value": "mctScrollX" - }, - { - "key": "MCT_SCROLL_Y_PROPERTY", - "value": "scrollTop" - }, - { - "key": "MCT_SCROLL_Y_ATTRIBUTE", - "value": "mctScrollY" - }, - { - "key": "THEME", - "value": "unspecified", - "priority": "fallback" - }, - { - "key": "ASSETS_PATH", - "value": ".", - "priority": "fallback" - } - ], - "containers": [ - { - "key": "accordion", - "template": accordionTemplate, - "attributes": [ - "label" - ] - } - ], - "representations": [ - { - "key": "tree", - "template": subtreeTemplate, - "uses": [ - "composition" - ], - "type": "root", - "priority": "preferred" - }, - { - "key": "tree", - "template": treeTemplate - }, - { - "key": "subtree", - "template": subtreeTemplate, - "uses": [ - "composition" - ] - }, - { - "key": "tree-node", - "template": treeNodeTemplate, - "uses": [ - "action" - ] - }, - { - "key": "label", - "template": labelTemplate, - "uses": [ - "type", - "location" - ], - "gestures": [ - "drag", - "menu", - "info" - ] - }, - { - "key": "node", - "template": labelTemplate, - "uses": [ - "type" - ], - "gestures": [ - "drag", - "menu" - ] - }, - { - "key": "action-group", - "template": actionGroupTemplate, - "uses": [ - "action" - ] - }, - { - "key": "switcher", - "template": switcherTemplate, - "uses": [ - "view" - ] - }, - { - "key": "object-inspector", - "template": objectInspectorTemplate - } - ], - "controls": [ - { - "key": "selector", - "template": selectorTemplate - }, - { - "key": "datetime-picker", - "template": datetimePickerTemplate - }, - { - "key": "datetime-field", - "template": datetimeFieldTemplate - } - ], - "licenses": [ - { - "name": "Normalize.css", - "version": "1.1.2", - "description": "Browser style normalization", - "author": "Nicolas Gallagher, Jonathan Neal", - "website": "http://necolas.github.io/normalize.css/", - "copyright": "Copyright (c) Nicolas Gallagher and Jonathan Neal", - "license": "license-mit", - "link": "https://github.com/necolas/normalize.css/blob/v1.1.2/LICENSE.md" - }, - { - "name": "Zepto", - "version": "1.1.6", - "description": "DOM manipulation", - "author": "Thomas Fuchs", - "website": "http://zeptojs.com/", - "copyright": "Copyright (c) 2010-2016 Thomas Fuchs", - "license": "license-mit", - "link": "https://github.com/madrobby/zepto/blob/master/MIT-LICENSE" - } - ] - } - } - }; -}); diff --git a/platform/commonUI/general/res/templates/angular-indicator.html b/platform/commonUI/general/res/templates/angular-indicator.html deleted file mode 100644 index 8aebc93aea..0000000000 --- a/platform/commonUI/general/res/templates/angular-indicator.html +++ /dev/null @@ -1,26 +0,0 @@ - - -
    - {{ngModel.getText()}} -
    diff --git a/platform/commonUI/general/res/templates/bottombar.html b/platform/commonUI/general/res/templates/bottombar.html deleted file mode 100644 index 5cf4656d1a..0000000000 --- a/platform/commonUI/general/res/templates/bottombar.html +++ /dev/null @@ -1,28 +0,0 @@ - -
    -
    - -
    - - -
    diff --git a/platform/commonUI/general/res/templates/containers/accordion.html b/platform/commonUI/general/res/templates/containers/accordion.html deleted file mode 100644 index 40910a5e6b..0000000000 --- a/platform/commonUI/general/res/templates/containers/accordion.html +++ /dev/null @@ -1,34 +0,0 @@ - -
    - {{container.label}} -
    -
    -
    diff --git a/platform/commonUI/general/res/templates/controls/action-group.html b/platform/commonUI/general/res/templates/controls/action-group.html deleted file mode 100644 index 15b864b23b..0000000000 --- a/platform/commonUI/general/res/templates/controls/action-group.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - diff --git a/platform/commonUI/general/res/templates/controls/breadcrumb.html b/platform/commonUI/general/res/templates/controls/breadcrumb.html deleted file mode 100644 index ce923da77e..0000000000 --- a/platform/commonUI/general/res/templates/controls/breadcrumb.html +++ /dev/null @@ -1,15 +0,0 @@ -
    - - diff --git a/platform/commonUI/general/res/templates/controls/datetime-field.html b/platform/commonUI/general/res/templates/controls/datetime-field.html deleted file mode 100644 index a319e56cb4..0000000000 --- a/platform/commonUI/general/res/templates/controls/datetime-field.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - -
    - - -
    -
    -
    diff --git a/platform/commonUI/general/res/templates/controls/datetime-picker.html b/platform/commonUI/general/res/templates/controls/datetime-picker.html deleted file mode 100644 index b16866299e..0000000000 --- a/platform/commonUI/general/res/templates/controls/datetime-picker.html +++ /dev/null @@ -1,66 +0,0 @@ - - -
    -
    -
    - - {{month}} {{year}} - -
    -
    - -
      -
    • -
      {{cell.day}}
      -
      {{cell.dayOfYear}}
      -
    • -
    -
    -
    -
    -
    - - {{nameFor(key)}} - -
    -
    - -
    - -
    -
    -
    -
    -
    diff --git a/platform/commonUI/general/res/templates/controls/input-filter.html b/platform/commonUI/general/res/templates/controls/input-filter.html deleted file mode 100644 index 932ee74324..0000000000 --- a/platform/commonUI/general/res/templates/controls/input-filter.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - diff --git a/platform/commonUI/general/res/templates/controls/selector.html b/platform/commonUI/general/res/templates/controls/selector.html deleted file mode 100644 index 28485c8a75..0000000000 --- a/platform/commonUI/general/res/templates/controls/selector.html +++ /dev/null @@ -1,65 +0,0 @@ - -
    -
    -
    Available
    -
    - - -
    -
    - -
    -
    Selected
    -
    -
      -
    • - - - - -
    • -
    -
    -
    -
    diff --git a/platform/commonUI/general/res/templates/controls/switcher.html b/platform/commonUI/general/res/templates/controls/switcher.html deleted file mode 100644 index 147b0e9ba7..0000000000 --- a/platform/commonUI/general/res/templates/controls/switcher.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - diff --git a/platform/commonUI/general/res/templates/controls/time-controller.html b/platform/commonUI/general/res/templates/controls/time-controller.html deleted file mode 100644 index f11bf3f6cb..0000000000 --- a/platform/commonUI/general/res/templates/controls/time-controller.html +++ /dev/null @@ -1,100 +0,0 @@ - -
    -
    - - - - - - - - to - - - - - - - -
    - -
    -
    -
    -
    -
    {{startInnerText}}
    -
    -
    -
    {{endInnerText}}
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    - {{tick}} -
    -
    -
    -
    diff --git a/platform/commonUI/general/res/templates/label.html b/platform/commonUI/general/res/templates/label.html deleted file mode 100644 index 27983aed19..0000000000 --- a/platform/commonUI/general/res/templates/label.html +++ /dev/null @@ -1,31 +0,0 @@ - -
    -
    - -
    -
    {{model.name}}
    -
    diff --git a/platform/commonUI/general/res/templates/message-banner.html b/platform/commonUI/general/res/templates/message-banner.html deleted file mode 100644 index 861db30522..0000000000 --- a/platform/commonUI/general/res/templates/message-banner.html +++ /dev/null @@ -1,20 +0,0 @@ -
    - - {{active.notification.model.title}} - - - - - - - -
    diff --git a/platform/commonUI/general/res/templates/object-inspector.html b/platform/commonUI/general/res/templates/object-inspector.html deleted file mode 100644 index 6abc35e920..0000000000 --- a/platform/commonUI/general/res/templates/object-inspector.html +++ /dev/null @@ -1,49 +0,0 @@ - - -
    - -
    -
    -
    Inspection
    - - -
    -
    - -
    -
    -

    Elements

    - - -
    -
    -
    -
    -
    diff --git a/platform/commonUI/general/res/templates/progress-bar.html b/platform/commonUI/general/res/templates/progress-bar.html deleted file mode 100644 index 69883ed7ec..0000000000 --- a/platform/commonUI/general/res/templates/progress-bar.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - -
    - {{ngModel.progressPerc}}% complete. - {{ngModel.progressText}} -
    diff --git a/platform/commonUI/general/res/templates/subtree.html b/platform/commonUI/general/res/templates/subtree.html deleted file mode 100644 index 3eb67eb594..0000000000 --- a/platform/commonUI/general/res/templates/subtree.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - diff --git a/platform/commonUI/general/res/templates/tree-node.html b/platform/commonUI/general/res/templates/tree-node.html deleted file mode 100644 index ef0ecc452e..0000000000 --- a/platform/commonUI/general/res/templates/tree-node.html +++ /dev/null @@ -1,50 +0,0 @@ - - -
    - -
    - - -
    -
    -
    diff --git a/platform/commonUI/general/res/templates/tree.html b/platform/commonUI/general/res/templates/tree.html deleted file mode 100644 index db7511b5a6..0000000000 --- a/platform/commonUI/general/res/templates/tree.html +++ /dev/null @@ -1,30 +0,0 @@ - -
      -
    • - - -
    • -
    diff --git a/platform/commonUI/general/res/templates/tree/node.html b/platform/commonUI/general/res/templates/tree/node.html deleted file mode 100644 index 41f3a2b226..0000000000 --- a/platform/commonUI/general/res/templates/tree/node.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/platform/commonUI/general/res/templates/tree/toggle.html b/platform/commonUI/general/res/templates/tree/toggle.html deleted file mode 100644 index daa78ec9a0..0000000000 --- a/platform/commonUI/general/res/templates/tree/toggle.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/platform/commonUI/general/res/templates/tree/tree-label.html b/platform/commonUI/general/res/templates/tree/tree-label.html deleted file mode 100644 index da82af2337..0000000000 --- a/platform/commonUI/general/res/templates/tree/tree-label.html +++ /dev/null @@ -1,4 +0,0 @@ -
    -
    -
    -
    diff --git a/platform/commonUI/general/res/templates/tree/wait-node.html b/platform/commonUI/general/res/templates/tree/wait-node.html deleted file mode 100644 index eb54b14d49..0000000000 --- a/platform/commonUI/general/res/templates/tree/wait-node.html +++ /dev/null @@ -1,24 +0,0 @@ - -
  • - Loading... -
  • diff --git a/platform/commonUI/general/src/StyleSheetLoader.js b/platform/commonUI/general/src/StyleSheetLoader.js deleted file mode 100644 index 1cc0c88ad5..0000000000 --- a/platform/commonUI/general/src/StyleSheetLoader.js +++ /dev/null @@ -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. - *****************************************************************************/ - -/** - * This bundle provides various general-purpose UI elements, including - * platform styling. - * @namespace platform/commonUI/general - */ -define( - [], - function () { - - /** - * The StyleSheetLoader adds links to style sheets exposed from - * various bundles as extensions of category `stylesheets`. - * @memberof platform/commonUI/general - * @constructor - * @param {object[]} stylesheets stylesheet extension definitions - * @param $document Angular's jqLite-wrapped document element - * @param {string} activeTheme the theme in use - * @param {string} [assetPath] the directory relative to which - * stylesheets will be found - */ - function StyleSheetLoader(stylesheets, $document, activeTheme, assetPath) { - var head = $document.find('head'), - document = $document[0]; - - // Procedure for adding a single stylesheet - function addStyleSheet(stylesheet) { - // Create a link element, and construct full path - var link = document.createElement('link'), - path = [ - assetPath, - stylesheet.bundle.path, - stylesheet.bundle.resources, - stylesheet.stylesheetUrl - ].join("/"); - - // Initialize attributes on the link - link.setAttribute("rel", "stylesheet"); - link.setAttribute("type", "text/css"); - link.setAttribute("href", path); - - // Append the link to the head element - head.append(link); - } - - // Stylesheets which specify themes should only be applied - // when that theme has been declared. - function matchesTheme(stylesheet) { - return stylesheet.theme === undefined - || stylesheet.theme === activeTheme; - } - - assetPath = assetPath || "."; - - // Add all stylesheets from extensions - stylesheets.filter(matchesTheme).forEach(addStyleSheet); - } - - return StyleSheetLoader; - } -); diff --git a/platform/commonUI/general/src/controllers/ActionGroupController.js b/platform/commonUI/general/src/controllers/ActionGroupController.js deleted file mode 100644 index 2566fb3285..0000000000 --- a/platform/commonUI/general/src/controllers/ActionGroupController.js +++ /dev/null @@ -1,104 +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. - *****************************************************************************/ - -/** - * Module defining ActionGroupController. Created by vwoeltje on 11/14/14. - */ -define( - [], - function () { - - /** - * Controller which keeps an up-to-date list of actions of - * a certain category, and additionally bins them into - * groups as described by their metadata. Used specifically - * to support button groups. - * - * This will maintain two fields in the scope: - * * `groups`: An array of arrays. Each element in the outer - * array corresponds to a group; the inner array contains - * the actions which are in that group. - * * `ungrouped`: All actions which did not have a defined - * group. - * - * @memberof platform/commonUI/general - * @constructor - */ - function ActionGroupController($scope) { - - // Separate out the actions that have been retrieved - // into groups, and populate scope with this. - function groupActions(actions) { - var groups = {}, - ungrouped = []; - - function assignToGroup(action) { - var metadata = action.getMetadata(), - group = metadata.group; - if (group) { - groups[group] = groups[group] || []; - groups[group].push(action); - } else { - ungrouped.push(action); - } - } - - (actions || []).forEach(assignToGroup); - - $scope.ungrouped = ungrouped; - $scope.groups = Object.keys(groups).sort().map(function (k) { - return groups[k]; - }); - } - - // Callback for when state which might influence action groupings - // changes. - function updateGroups() { - var actionCapability = $scope.action, - params = $scope.parameters || {}, - category = params.category; - - if (actionCapability && category) { - // Get actions by capability, and group them - groupActions(actionCapability.getActions({ - category: category - })); - } else { - // We don't have enough information to get any actions. - groupActions([]); - } - } - - // Changes to the represented object, to its action capability, or - // to the chosen action category may all require an update. - $scope.$watch("domainObject", updateGroups); - $scope.$watch("action", updateGroups); - $scope.$watch("parameters.category", updateGroups); - - // Start with empty arrays. - $scope.ungrouped = []; - $scope.groups = []; - } - - return ActionGroupController; - } -); diff --git a/platform/commonUI/general/src/controllers/BannerController.js b/platform/commonUI/general/src/controllers/BannerController.js deleted file mode 100644 index 93cce6195f..0000000000 --- a/platform/commonUI/general/src/controllers/BannerController.js +++ /dev/null @@ -1,77 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * A controller for banner notifications. Banner notifications are a - * non-blocking way of drawing the user's attention to an event such - * as system errors, or the progress or successful completion of an - * ongoing task. This controller provides scoped functions for - * dismissing and 'maximizing' notifications. See {@link NotificationService} - * for more details on Notifications. - * - * @param $scope - * @param notificationService - * @param dialogService - * @constructor - */ - function BannerController($scope, notificationService, dialogService) { - $scope.active = notificationService.active; - - $scope.action = function (action, $event) { - /* - Prevents default 'maximize' behaviour when clicking on - notification button - */ - $event.stopPropagation(); - - return action(); - }; - - $scope.dismiss = function (notification, $event) { - $event.stopPropagation(); - notification.dismiss(); - }; - - $scope.maximize = function (notification) { - if (notification.model.severity !== "info") { - var dialog; - notification.model.cancel = function () { - dialog.dismiss(); - }; - - //If the notification is dismissed by the user, close - // the dialog. - notification.on('dismiss', function () { - dialog.dismiss(); - }); - - dialog = dialogService.showBlockingMessage(notification.model); - } - }; - } - - return BannerController; - }); diff --git a/platform/commonUI/general/src/controllers/ClickAwayController.js b/platform/commonUI/general/src/controllers/ClickAwayController.js deleted file mode 100644 index 982e2d0aa9..0000000000 --- a/platform/commonUI/general/src/controllers/ClickAwayController.js +++ /dev/null @@ -1,97 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * A ClickAwayController is used to toggle things (such as context - * menus) where clicking elsewhere in the document while the toggle - * is in an active state is intended to dismiss the toggle. - * - * @memberof platform/commonUI/general - * @constructor - * @param $scope the scope in which this controller is active - * @param $document the document element, injected by Angular - */ - function ClickAwayController($document, $timeout) { - var self = this; - - this.state = false; - this.$document = $document; - - // Callback used by the document listener. Timeout ensures that - // `clickaway` action occurs after `toggle` if `toggle` is - // triggered by a click/mouseup. - this.clickaway = function () { - $timeout(function () { - self.deactivate(); - }); - }; - } - - // Track state, but also attach and detach a listener for - // mouseup events on the document. - ClickAwayController.prototype.deactivate = function () { - this.state = false; - this.$document.off("mouseup", this.clickaway); - }; - - ClickAwayController.prototype.activate = function () { - this.state = true; - this.$document.on("mouseup", this.clickaway); - }; - - /** - * Get the current state of the toggle. - * @return {boolean} true if active - */ - ClickAwayController.prototype.isActive = function () { - return this.state; - }; - - /** - * Set a new state for the toggle. - * @return {boolean} true to activate - */ - ClickAwayController.prototype.setState = function (newState) { - if (this.state !== newState) { - this.toggle(); - } - }; - - /** - * Toggle the current state; activate if it is inactive, - * deactivate if it is active. - */ - ClickAwayController.prototype.toggle = function () { - if (this.state) { - this.deactivate(); - } else { - this.activate(); - } - }; - - return ClickAwayController; - } -); diff --git a/platform/commonUI/general/src/controllers/DateTimeFieldController.js b/platform/commonUI/general/src/controllers/DateTimeFieldController.js deleted file mode 100644 index 4132ded766..0000000000 --- a/platform/commonUI/general/src/controllers/DateTimeFieldController.js +++ /dev/null @@ -1,112 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * Controller to support the date-time entry field. - * - * Accepts a `format` property in the `structure` attribute - * which allows a date/time to be specified via its symbolic - * key (as will be used to look up said format from the - * `formatService`.) - * - * {@see FormatService} - * @constructor - * @memberof platform/commonUI/general - * @param $scope the Angular scope for this controller - * @param {FormatService} formatService the service to user to format - * domain values - * @param {string} defaultFormat the format to request when no - * format has been otherwise specified - */ - function DateTimeFieldController($scope, formatService, defaultFormat) { - var formatter = formatService.getFormat(defaultFormat); - - function updateFromModel(value) { - // Only reformat if the value is different from user - // input (to avoid reformatting valid input while typing.) - if (!formatter.validate($scope.textValue) - || formatter.parse($scope.textValue) !== value) { - $scope.textValue = formatter.format(value); - $scope.textInvalid = false; - $scope.lastValidValue = $scope.textValue; - } - - $scope.pickerModel = { value: value }; - } - - function updateFromView(textValue) { - $scope.textInvalid = !formatter.validate(textValue); - if (!$scope.textInvalid) { - $scope.ngModel[$scope.field] = - formatter.parse(textValue); - $scope.lastValidValue = $scope.textValue; - } - } - - function updateFromPicker(value) { - if (value !== $scope.ngModel[$scope.field]) { - $scope.ngModel[$scope.field] = value; - updateFromModel(value); - if ($scope.ngBlur) { - $scope.ngBlur(); - } - - // If picker is active, dismiss it when valid value has been selected - // This 'if' is to avoid unnecessary validation if picker is not active - if ($scope.picker.active) { - if ($scope.structure.validate && $scope.structure.validate($scope.ngModel[$scope.field])) { - $scope.picker.active = false; - } else if (!$scope.structure.validate) { - //If picker visible, but no validation function, hide picker - $scope.picker.active = false; - } - } - } - } - - function setFormat(format) { - formatter = formatService.getFormat(format || defaultFormat); - updateFromModel($scope.ngModel[$scope.field]); - } - - function restoreTextValue() { - $scope.textValue = $scope.lastValidValue; - updateFromView($scope.textValue); - } - - $scope.restoreTextValue = restoreTextValue; - - $scope.picker = { active: false }; - - $scope.$watch('structure.format', setFormat); - $scope.$watch('ngModel[field]', updateFromModel); - $scope.$watch('pickerModel.value', updateFromPicker); - $scope.$watch('textValue', updateFromView); - } - - return DateTimeFieldController; - } -); diff --git a/platform/commonUI/general/src/controllers/DateTimePickerController.js b/platform/commonUI/general/src/controllers/DateTimePickerController.js deleted file mode 100644 index 742e4b18bd..0000000000 --- a/platform/commonUI/general/src/controllers/DateTimePickerController.js +++ /dev/null @@ -1,207 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['moment'], - function (moment) { - - var TIME_NAMES = { - 'hours': "Hour", - 'minutes': "Minute", - 'seconds': "Second" - }, - MONTHS = moment.months(), - TIME_OPTIONS = (function makeRanges() { - var arr = []; - while (arr.length < 60) { - arr.push(arr.length); - } - - return { - hours: arr.slice(0, 24), - minutes: arr, - seconds: arr - }; - }()); - - /** - * Controller to support the date-time picker. - * - * Adds/uses the following properties in scope: - * * `year`: Year being displayed in picker - * * `month`: Month being displayed - * * `table`: Table being displayed; array of arrays of - * * `day`: Day of month - * * `dayOfYear`: Day of year - * * `month`: Month associated with the day - * * `year`: Year associated with the day. - * * `date`: Date chosen - * * `year`: Year selected - * * `month`: Month selected (0-indexed) - * * `day`: Day of month selected - * * `time`: Chosen time (hours/minutes/seconds) - * * `hours`: Hours chosen - * * `minutes`: Minutes chosen - * * `seconds`: Seconds chosen - * - * Months are zero-indexed, day-of-months are one-indexed. - */ - function DateTimePickerController($scope, now) { - var year, - month, // For picker state, not model state - interacted = false; - - function generateTable() { - var m = moment.utc({ - year: year, - month: month - }).day(0), - table = [], - row, - col; - - for (row = 0; row < 6; row += 1) { - table.push([]); - for (col = 0; col < 7; col += 1) { - table[row].push({ - year: m.year(), - month: m.month(), - day: m.date(), - dayOfYear: m.dayOfYear() - }); - m.add(1, 'days'); // Next day! - } - } - - return table; - } - - function updateScopeForMonth() { - $scope.month = MONTHS[month]; - $scope.year = year; - $scope.table = generateTable(); - } - - function updateFromModel(ngModel) { - var m; - - m = moment.utc(ngModel); - - $scope.date = { - year: m.year(), - month: m.month(), - day: m.date() - }; - $scope.time = { - hours: m.hour(), - minutes: m.minute(), - seconds: m.second() - }; - - //window.alert($scope.date.day + " " + ngModel); - - // Zoom to that date in the picker, but - // only if the user hasn't interacted with it yet. - if (!interacted) { - year = m.year(); - month = m.month(); - updateScopeForMonth(); - } - } - - function updateFromView() { - var m = moment.utc({ - year: $scope.date.year, - month: $scope.date.month, - day: $scope.date.day, - hour: $scope.time.hours, - minute: $scope.time.minutes, - second: $scope.time.seconds - }); - $scope.ngModel[$scope.field] = m.valueOf(); - } - - $scope.isInCurrentMonth = function (cell) { - return cell.month === month; - }; - - $scope.isSelected = function (cell) { - var date = $scope.date || {}; - - return cell.day === date.day - && cell.month === date.month - && cell.year === date.year; - }; - - $scope.select = function (cell) { - $scope.date = $scope.date || {}; - $scope.date.month = cell.month; - $scope.date.year = cell.year; - $scope.date.day = cell.day; - updateFromView(); - }; - - $scope.dateEquals = function (d1, d2) { - return d1.year === d2.year - && d1.month === d2.month - && d1.day === d2.day; - }; - - $scope.changeMonth = function (delta) { - month += delta; - if (month > 11) { - month = 0; - year += 1; - } - - if (month < 0) { - month = 11; - year -= 1; - } - - interacted = true; - updateScopeForMonth(); - }; - - $scope.nameFor = function (key) { - return TIME_NAMES[key]; - }; - - $scope.optionsFor = function (key) { - return TIME_OPTIONS[key]; - }; - - updateScopeForMonth(); - - // Ensure some useful default - $scope.ngModel[$scope.field] = - $scope.ngModel[$scope.field] === undefined - ? now() : $scope.ngModel[$scope.field]; - - $scope.$watch('ngModel[field]', updateFromModel); - $scope.$watchCollection('date', updateFromView); - $scope.$watchCollection('time', updateFromView); - } - - return DateTimePickerController; - } -); diff --git a/platform/commonUI/general/src/controllers/GetterSetterController.js b/platform/commonUI/general/src/controllers/GetterSetterController.js deleted file mode 100644 index 029812d937..0000000000 --- a/platform/commonUI/general/src/controllers/GetterSetterController.js +++ /dev/null @@ -1,89 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * This controller acts as an adapter to permit getter-setter - * functions to be used as ng-model arguments to controls, - * such as the input-filter. This is supported natively in - * Angular 1.3+ via `ng-model-options`, so this controller - * should be made obsolete after any upgrade to Angular 1.3. - * - * It expects to find in scope a value `ngModel` which is a - * function which, when called with no arguments, acts as a - * getter, and when called with one argument, acts as a setter. - * - * It also publishes into the scope a value `getterSetter.value` - * which is meant to be used as an assignable expression. - * - * This controller watches both of these; when one changes, - * it will update the other's value to match. Because of this, - * the `ngModel` function should be both stable and computationally - * inexpensive, as it will be invoked often. - * - * Getter-setter style models can be preferable when there - * is significant indirection between templates; "dotless" - * expressions in `ng-model` can behave unexpectedly due to the - * rules of scope, but dots are lost when passed in via `ng-model` - * (so if a control is internally implemented using regular - * form elements, it can't transparently pass through the `ng-model` - * parameter it received.) Getter-setter functions are never the - * target of a scope assignment and so avoid this problem. - * - * @memberof platform/commonUI/general - * @constructor - * @param {Scope} $scope the controller's scope - */ - function GetterSetterController($scope) { - - // Update internal assignable state based on changes - // to the getter-setter function. - function updateGetterSetter() { - if (typeof $scope.ngModel === 'function') { - $scope.getterSetter.value = $scope.ngModel(); - } - } - - // Update the external getter-setter based on changes - // to the assignable state. - function updateNgModel() { - if (typeof $scope.ngModel === 'function') { - $scope.ngModel($scope.getterSetter.value); - } - } - - // Watch for changes to both expressions - $scope.$watch("ngModel()", updateGetterSetter); - $scope.$watch("getterSetter.value", updateNgModel); - - // Publish an assignable field into scope. - $scope.getterSetter = {}; - - } - - return GetterSetterController; - - } -); diff --git a/platform/commonUI/general/src/controllers/ObjectInspectorController.js b/platform/commonUI/general/src/controllers/ObjectInspectorController.js deleted file mode 100644 index e3261e2159..0000000000 --- a/platform/commonUI/general/src/controllers/ObjectInspectorController.js +++ /dev/null @@ -1,119 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * Module defining ObjectInspectorController. Created by shale on 08/21/2015. - */ -define( - [], - function () { - - /** - * The ObjectInspectorController gets and formats the data for - * the inspector display - * - * @constructor - */ - function ObjectInspectorController($scope, objectService) { - $scope.primaryParents = []; - $scope.contextutalParents = []; - //$scope.isLink = false; - - // Gets an array of the contextual parents/ancestors of the selected object - function getContextualPath() { - var currentObj = $scope.domainObject, - currentParent, - parents = []; - - currentParent = currentObj - && currentObj.hasCapability('context') - && currentObj.getCapability('context').getParent(); - - while (currentParent && currentParent.getModel().type !== 'root' - && currentParent.hasCapability('context')) { - // Record this object - parents.unshift(currentParent); - - // Get the next one up the tree - currentObj = currentParent; - currentParent = currentObj.getCapability('context').getParent(); - } - - $scope.contextutalParents = parents; - } - - // Gets an array of the parents/ancestors of the selected object's - // primary location (locational of original non-link) - function getPrimaryPath(current) { - var location; - - // If this the the initial call of this recursive function - if (!current) { - current = $scope.domainObject; - $scope.primaryParents = []; - } - - location = current.getModel().location; - - if (location && location !== 'root') { - objectService.getObjects([location]).then(function (obj) { - var next = obj[location]; - - $scope.primaryParents.unshift(next); - getPrimaryPath(next); - }); - } - - } - - // Gets the metadata for the selected object - function getMetadata() { - $scope.metadata = $scope.domainObject - && $scope.domainObject.hasCapability('metadata') - && $scope.domainObject.useCapability('metadata'); - } - - // Set scope variables when the selected object changes - $scope.$watch('domainObject', function () { - $scope.isLink = $scope.domainObject - && $scope.domainObject.hasCapability('location') - && $scope.domainObject.getCapability('location').isLink(); - - if ($scope.isLink) { - getPrimaryPath(); - getContextualPath(); - } else { - $scope.primaryParents = []; - getContextualPath(); - } - - getMetadata(); - }); - - var mutation = $scope.domainObject.getCapability('mutation'); - var unlisten = mutation.listen(getMetadata); - $scope.$on('$destroy', unlisten); - } - - return ObjectInspectorController; - } -); diff --git a/platform/commonUI/general/src/controllers/SelectorController.js b/platform/commonUI/general/src/controllers/SelectorController.js deleted file mode 100644 index c8b71cefe0..0000000000 --- a/platform/commonUI/general/src/controllers/SelectorController.js +++ /dev/null @@ -1,164 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - var ROOT_ID = "ROOT"; - - /** - * Controller for the domain object selector control. - * @memberof platform/commonUI/general - * @constructor - * @param {ObjectService} objectService service from which to - * read domain objects - * @param $scope Angular scope for this controller - */ - function SelectorController(objectService, $scope) { - var treeModel = {}, - listModel = {}, - previousSelected, - self = this; - - // For watch; look at the user's selection in the tree - function getTreeSelection() { - return treeModel.selectedObject; - } - - // Store root object for subsequent exposure to template - function storeRoot(objects) { - self.rootObject = objects[ROOT_ID]; - } - - // Check that a selection is of the valid type - function validateTreeSelection(selectedObject) { - var type = selectedObject - && selectedObject.getCapability('type'); - - // Delegate type-checking to the capability... - if (!type || !type.instanceOf($scope.structure.type)) { - treeModel.selectedObject = previousSelected; - } - - // Track current selection to restore it if an invalid - // selection is made later. - previousSelected = treeModel.selectedObject; - } - - // Update the right-hand list of currently-selected objects - function updateList(ids) { - function updateSelectedObjects(objects) { - // Look up from the - function getObject(id) { - return objects[id]; - } - - self.selectedObjects = - ids.filter(getObject).map(getObject); - } - - // Look up objects by id, then populate right-hand list - objectService.getObjects(ids).then(updateSelectedObjects); - } - - // Reject attempts to select objects of the wrong type - $scope.$watch(getTreeSelection, validateTreeSelection); - - // Make sure right-hand list matches underlying model - $scope.$watchCollection(function () { - return self.getField(); - }, updateList); - - // Look up root object, then store it - objectService.getObjects([ROOT_ID]).then(storeRoot); - - this.$scope = $scope; - this.selectedObjects = []; - - // Expose tree/list model for use in template directly - this.treeModel = treeModel; - this.listModel = listModel; - } - - // Set the value of the field being edited - SelectorController.prototype.setField = function (value) { - this.$scope.ngModel[this.$scope.field] = value; - }; - - // Get the value of the field being edited - SelectorController.prototype.getField = function () { - return this.$scope.ngModel[this.$scope.field] || []; - }; - - /** - * Get the root object to show in the left-hand tree. - * @returns {DomainObject} the root object - */ - SelectorController.prototype.root = function () { - return this.rootObject; - }; - - /** - * Add a domain object to the list of selected objects. - * @param {DomainObject} the domain object to select - */ - SelectorController.prototype.select = function (domainObject) { - var id = domainObject && domainObject.getId(), - list = this.getField() || []; - // Only select if we have a valid id, - // and it isn't already selected - if (id && list.indexOf(id) === -1) { - this.setField(list.concat([id])); - } - }; - - /** - * Remove a domain object from the list of selected objects. - * @param {DomainObject} the domain object to select - */ - SelectorController.prototype.deselect = function (domainObject) { - var id = domainObject && domainObject.getId(), - list = this.getField() || []; - // Only change if this was a valid id, - // for an object which was already selected - if (id && list.indexOf(id) !== -1) { - // Filter it out of the current field - this.setField(list.filter(function (otherId) { - return otherId !== id; - })); - // Clear the current list selection - delete this.listModel.selectedObject; - } - }; - - /** - * Get the currently-selected domain objects. - * @returns {DomainObject[]} the current selection - */ - SelectorController.prototype.selected = function () { - return this.selectedObjects; - }; - - return SelectorController; - } -); diff --git a/platform/commonUI/general/src/controllers/TimeRangeController.js b/platform/commonUI/general/src/controllers/TimeRangeController.js deleted file mode 100644 index 5b4a55a632..0000000000 --- a/platform/commonUI/general/src/controllers/TimeRangeController.js +++ /dev/null @@ -1,313 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - -], function () { - - var TICK_SPACING_PX = 150; - - /* format number as percent; 0.0-1.0 to "0%"-"100%" */ - function toPercent(p) { - return (100 * p) + "%"; - } - - function clamp(value, low, high) { - return Math.max(low, Math.min(high, value)); - } - - function copyBounds(bounds) { - return { - start: bounds.start, - end: bounds.end - }; - } - - /** - * Controller used by the `time-controller` template. - * @memberof platform/commonUI/general - * @constructor - * @param $scope the Angular scope for this controller - * @param {FormatService} formatService the service to user to format - * domain values - * @param {string} defaultFormat the format to request when no - * format has been otherwise specified - * @param {Function} now a function to return current system time - */ - function TimeRangeController($scope, $timeout, formatService, defaultFormat, now) { - this.$scope = $scope; - this.formatService = formatService; - this.defaultFormat = defaultFormat; - this.now = now; - - this.tickCount = 2; - this.innerMinimumSpan = 1000; // 1 second - this.outerMinimumSpan = 1000; // 1 second - this.initialDragValue = undefined; - this.formatter = formatService.getFormat(defaultFormat); - this.formStartChanged = false; - this.formEndChanged = false; - this.$timeout = $timeout; - - this.$scope.ticks = []; - - this.updateViewFromModel(this.$scope.ngModel); - this.updateFormModel(); - - [ - 'updateViewFromModel', - 'updateSpanWidth', - 'updateOuterStart', - 'updateOuterEnd', - 'updateFormat', - 'validateStart', - 'validateEnd', - 'onFormStartChange', - 'onFormEndChange' - ].forEach(function (boundFn) { - this[boundFn] = this[boundFn].bind(this); - }, this); - - this.$scope.$watchCollection("ngModel", this.updateViewFromModel); - this.$scope.$watch("spanWidth", this.updateSpanWidth); - this.$scope.$watch("ngModel.outer.start", this.updateOuterStart); - this.$scope.$watch("ngModel.outer.end", this.updateOuterEnd); - this.$scope.$watch("parameters.format", this.updateFormat); - this.$scope.$watch("formModel.start", this.onFormStartChange); - this.$scope.$watch("formModel.end", this.onFormEndChange); - } - - TimeRangeController.prototype.formatTimestamp = function (ts) { - return this.formatter.format(ts); - }; - - TimeRangeController.prototype.updateTicks = function () { - var i, p, ts, start, end, span; - end = this.$scope.ngModel.outer.end; - start = this.$scope.ngModel.outer.start; - span = end - start; - this.$scope.ticks = []; - for (i = 0; i < this.tickCount; i += 1) { - p = i / (this.tickCount - 1); - ts = p * span + start; - this.$scope.ticks.push(this.formatTimestamp(ts)); - } - }; - - TimeRangeController.prototype.updateSpanWidth = function (w) { - this.tickCount = Math.max(Math.floor(w / TICK_SPACING_PX), 2); - this.updateTicks(); - }; - - TimeRangeController.prototype.updateViewForInnerSpanFromModel = function ( - ngModel - ) { - var span = ngModel.outer.end - ngModel.outer.start; - - // Expose readable dates for the knobs - this.$scope.startInnerText = this.formatTimestamp(ngModel.inner.start); - this.$scope.endInnerText = this.formatTimestamp(ngModel.inner.end); - - // And positions for the knobs - this.$scope.startInnerPct = - toPercent((ngModel.inner.start - ngModel.outer.start) / span); - this.$scope.endInnerPct = - toPercent((ngModel.outer.end - ngModel.inner.end) / span); - }; - - TimeRangeController.prototype.defaultBounds = function () { - var t = this.now(); - - return { - start: t - 24 * 3600 * 1000, // One day - end: t - }; - }; - - TimeRangeController.prototype.updateViewFromModel = function (ngModel) { - ngModel = ngModel || {}; - ngModel.outer = ngModel.outer || this.defaultBounds(); - ngModel.inner = ngModel.inner || copyBounds(ngModel.outer); - - // Stick it back is scope (in case we just set defaults) - this.$scope.ngModel = ngModel; - - this.updateViewForInnerSpanFromModel(ngModel); - this.updateTicks(); - }; - - TimeRangeController.prototype.startLeftDrag = function () { - this.initialDragValue = this.$scope.ngModel.inner.start; - }; - - TimeRangeController.prototype.startRightDrag = function () { - this.initialDragValue = this.$scope.ngModel.inner.end; - }; - - TimeRangeController.prototype.startMiddleDrag = function () { - this.initialDragValue = { - start: this.$scope.ngModel.inner.start, - end: this.$scope.ngModel.inner.end - }; - }; - - TimeRangeController.prototype.toMillis = function (pixels) { - var span = - this.$scope.ngModel.outer.end - this.$scope.ngModel.outer.start; - - return (pixels / this.$scope.spanWidth) * span; - }; - - TimeRangeController.prototype.leftDrag = function (pixels) { - var delta = this.toMillis(pixels); - this.$scope.ngModel.inner.start = clamp( - this.initialDragValue + delta, - this.$scope.ngModel.outer.start, - this.$scope.ngModel.inner.end - this.innerMinimumSpan - ); - this.updateViewFromModel(this.$scope.ngModel); - }; - - TimeRangeController.prototype.rightDrag = function (pixels) { - var delta = this.toMillis(pixels); - this.$scope.ngModel.inner.end = clamp( - this.initialDragValue + delta, - this.$scope.ngModel.inner.start + this.innerMinimumSpan, - this.$scope.ngModel.outer.end - ); - this.updateViewFromModel(this.$scope.ngModel); - }; - - TimeRangeController.prototype.middleDrag = function (pixels) { - var delta = this.toMillis(pixels), - edge = delta < 0 ? 'start' : 'end', - opposite = delta < 0 ? 'end' : 'start'; - - // Adjust the position of the edge in the direction of drag - this.$scope.ngModel.inner[edge] = clamp( - this.initialDragValue[edge] + delta, - this.$scope.ngModel.outer.start, - this.$scope.ngModel.outer.end - ); - // Adjust opposite knob to maintain span - this.$scope.ngModel.inner[opposite] = - this.$scope.ngModel.inner[edge] - + this.initialDragValue[opposite] - - this.initialDragValue[edge]; - - this.updateViewFromModel(this.$scope.ngModel); - }; - - TimeRangeController.prototype.updateFormModel = function () { - this.$scope.formModel = { - start: ((this.$scope.ngModel || {}).outer || {}).start, - end: ((this.$scope.ngModel || {}).outer || {}).end - }; - }; - - TimeRangeController.prototype.updateOuterStart = function () { - var ngModel = this.$scope.ngModel; - - ngModel.inner.start = - Math.max(ngModel.outer.start, ngModel.inner.start); - ngModel.inner.end = Math.max( - ngModel.inner.start + this.innerMinimumSpan, - ngModel.inner.end - ); - - this.updateFormModel(); - this.updateViewForInnerSpanFromModel(ngModel); - this.updateTicks(); - }; - - TimeRangeController.prototype.updateOuterEnd = function () { - var ngModel = this.$scope.ngModel; - - ngModel.inner.end = - Math.min(ngModel.outer.end, ngModel.inner.end); - ngModel.inner.start = Math.min( - ngModel.inner.end - this.innerMinimumSpan, - ngModel.inner.start - ); - - this.updateFormModel(); - this.updateViewForInnerSpanFromModel(ngModel); - this.updateTicks(); - }; - - TimeRangeController.prototype.updateFormat = function (key) { - this.formatter = this.formatService.getFormat(key || this.defaultFormat); - this.updateViewForInnerSpanFromModel(this.$scope.ngModel); - this.updateTicks(); - }; - - TimeRangeController.prototype.updateBoundsFromForm = function () { - var self = this; - - //Allow Angular to trigger watches and determine whether values have changed. - this.$timeout(function () { - if (self.formStartChanged) { - self.$scope.ngModel.outer.start = - self.$scope.ngModel.inner.start = - self.$scope.formModel.start; - self.formStartChanged = false; - } - - if (self.formEndChanged) { - self.$scope.ngModel.outer.end = - self.$scope.ngModel.inner.end = - self.$scope.formModel.end; - self.formEndChanged = false; - } - }); - }; - - TimeRangeController.prototype.onFormStartChange = function ( - newValue, - oldValue - ) { - if (!this.formStartChanged && newValue !== oldValue) { - this.formStartChanged = true; - } - }; - - TimeRangeController.prototype.onFormEndChange = function ( - newValue, - oldValue - ) { - if (!this.formEndChanged && newValue !== oldValue) { - this.formEndChanged = true; - } - }; - - TimeRangeController.prototype.validateStart = function (startValue) { - return startValue - <= this.$scope.formModel.end - this.outerMinimumSpan; - }; - - TimeRangeController.prototype.validateEnd = function (endValue) { - return endValue - >= this.$scope.formModel.start + this.outerMinimumSpan; - }; - - return TimeRangeController; -}); diff --git a/platform/commonUI/general/src/controllers/ToggleController.js b/platform/commonUI/general/src/controllers/ToggleController.js deleted file mode 100644 index 1b7f15e326..0000000000 --- a/platform/commonUI/general/src/controllers/ToggleController.js +++ /dev/null @@ -1,66 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * A ToggleController is used to activate/deactivate things. - * A common usage is for "twistie" - * - * @memberof platform/commonUI/general - * @constructor - */ - function ToggleController() { - this.state = false; - - this.setState = this.setState.bind(this); - } - - /** - * Get the current state of the toggle. - * @return {boolean} true if active - */ - ToggleController.prototype.isActive = function () { - return this.state; - }; - - /** - * Set a new state for the toggle. - * @return {boolean} true to activate - */ - ToggleController.prototype.setState = function (newState) { - this.state = newState; - }; - - /** - * Toggle the current state; activate if it is inactive, - * deactivate if it is active. - */ - ToggleController.prototype.toggle = function () { - this.state = !this.state; - }; - - return ToggleController; - } -); diff --git a/platform/commonUI/general/src/controllers/TreeNodeController.js b/platform/commonUI/general/src/controllers/TreeNodeController.js deleted file mode 100644 index 7a41c939cd..0000000000 --- a/platform/commonUI/general/src/controllers/TreeNodeController.js +++ /dev/null @@ -1,204 +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. - *****************************************************************************/ - -/** - * Module defining TreeNodeController. Created by vwoeltje on 11/10/14. - */ -define( - [], - function () { - - /** - * The TreeNodeController supports the tree node representation; - * a tree node has a label for the current object as well as a - * subtree which shows (and is not loaded until) the node is - * expanded. - * - * This controller tracks the following, so that the tree node - * template may update its state accordingly: - * - * * Whether or not the tree node has ever been expanded (this - * is used to lazily load, exactly once, the subtree) - * * Whether or not the node is currently the domain object - * of navigation (this gets highlighted differently to - * provide the user with visual feedback.) - * - * Additionally, this controller will automatically trigger - * node expansion when this tree node's _subtree_ will contain - * the navigated object (recursively, this becomes an - * expand-to-show-navigated-object behavior.) - * - * Finally, if a `callback` property is passed in through the - * `parameters` attribute of the `tree-node`, that callback - * will be invoked whenever a user clicks in a manner which - * would result in a selection. This callback is invoked - * even if the selection does not change (if you are only - * interested in changes, watch the `selectedObject` property - * of the object passed in `ng-model` instead.) - * - * @memberof platform/commonUI/general - * @constructor - */ - function TreeNodeController($scope, $timeout) { - var self = this, - selectedObject = ($scope.ngModel || {}).selectedObject; - - // Look up the id for a domain object. A convenience - // for mapping; additionally does some undefined-checking. - function getId(obj) { - return obj && obj.getId && obj.getId(); - } - - // Verify that id paths are equivalent, staring at - // index, ending at the end of the node path. - function checkPath(nodePath, navPath, index) { - index = index || 0; - - // The paths overlap if we have made it past the - // end of the node's path; otherwise, check the - // id at the current index for equality and perform - // a recursive step for subsequent ids in the paths, - // until we exceed path length or hit a mismatch. - return (index >= nodePath.length) - || ((navPath[index] === nodePath[index]) - && checkPath(nodePath, navPath, index + 1)); - } - - // Consider the currently-navigated object and update - // parameters which support display. - function checkSelection() { - var nodeObject = $scope.domainObject, - navObject = selectedObject, - nodeContext = nodeObject - && nodeObject.getCapability('context'), - navContext = navObject - && navObject.getCapability('context'), - nodePath, - navPath; - - // Deselect; we will reselect below, iff we are - // exactly at the end of the path. - self.isSelectedFlag = false; - - // Expand if necessary (if the navigated object will - // be in this node's subtree) - if (nodeContext && navContext) { - // Get the paths as arrays of identifiers - nodePath = nodeContext.getPath().map(getId); - navPath = navContext.getPath().map(getId); - - // Check to see if the node's path lies entirely - // within the navigation path; otherwise, navigation - // has happened in some other subtree. - if (navPath.length >= nodePath.length - && checkPath(nodePath, navPath)) { - - // nodePath is along the navPath; if it's - // at the end of the path, highlight; - // otherwise, expand. - if (nodePath.length === navPath.length) { - self.isSelectedFlag = true; - } else { // node path is shorter: Expand! - if ($scope.toggle) { - $scope.toggle.setState(true); - } - - self.trackExpansion(); - } - - } - } - } - - // Callback for the selection updates; track the currently - // navigated object and update display parameters as needed. - function setSelection(object) { - selectedObject = object; - checkSelection(); - } - - this.isSelectedFlag = false; - this.hasBeenExpandedFlag = false; - this.$timeout = $timeout; - this.$scope = $scope; - - // Listen for changes which will effect display parameters - $scope.$watch("ngModel.selectedObject", setSelection); - $scope.$watch("domainObject", checkSelection); - } - - /** - * Select the domain object represented by this node in the tree. - * This will both update the `selectedObject` property in - * the object passed in via `ng-model`, and will fire any `callback` - * passed in via `parameters`. - */ - TreeNodeController.prototype.select = function () { - if (this.$scope.ngModel) { - this.$scope.ngModel.selectedObject = - this.$scope.domainObject; - } - - if ((this.$scope.parameters || {}).callback) { - this.$scope.parameters.callback(this.$scope.domainObject); - } - }; - - /** - * This method should be called when a node is expanded - * to record that this has occurred, to support one-time - * lazy loading of the node's subtree. - */ - TreeNodeController.prototype.trackExpansion = function () { - var self = this; - if (!self.hasBeenExpanded()) { - // Run on a timeout; if a lot of expansion needs to - // occur (e.g. if the selection is several nodes deep) we - // want this to be spread across multiple digest cycles. - self.$timeout(function () { - self.hasBeenExpandedFlag = true; - }, 0); - } - }; - - /** - * Check if this not has ever been expanded. - * @returns true if it has been expanded - */ - TreeNodeController.prototype.hasBeenExpanded = function () { - return this.hasBeenExpandedFlag; - }; - - /** - * Check whether or not the domain object represented by - * this tree node should be highlighted. - * An object will be highlighted if it matches - * ngModel.selectedObject - * @returns true if this should be highlighted - */ - TreeNodeController.prototype.isSelected = function () { - return this.isSelectedFlag; - }; - - return TreeNodeController; - } -); diff --git a/platform/commonUI/general/src/controllers/ViewSwitcherController.js b/platform/commonUI/general/src/controllers/ViewSwitcherController.js deleted file mode 100644 index 2e96df1532..0000000000 --- a/platform/commonUI/general/src/controllers/ViewSwitcherController.js +++ /dev/null @@ -1,73 +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. - *****************************************************************************/ - -/** - * Module defining ViewSwitcherController. Created by vwoeltje on 11/7/14. - */ -define( - [], - function () { - - /** - * Controller for the view switcher; populates and maintains a list - * of applicable views for a represented domain object. - * @memberof platform/commonUI/general - * @constructor - */ - function ViewSwitcherController($scope, $timeout) { - // If the view capability gets refreshed, try to - // keep the same option chosen. - function findMatchingOption(options, selected) { - var i; - - if (selected) { - for (i = 0; i < options.length; i += 1) { - if (options[i].key === selected.key) { - return options[i]; - } - } - } - - return options[0]; - } - - // Get list of views, read from capability - function updateOptions(views) { - if (Array.isArray(views)) { - $timeout(function () { - $scope.ngModel.selected = findMatchingOption( - views, - ($scope.ngModel || {}).selected - ); - }, 0); - } - } - - // Update view options when the in-scope results of using the - // view capability change. - $scope.$watch("view", updateOptions); - } - - return ViewSwitcherController; - } -); - diff --git a/platform/commonUI/general/src/directives/MCTClickElsewhere.js b/platform/commonUI/general/src/directives/MCTClickElsewhere.js deleted file mode 100644 index dee34a66eb..0000000000 --- a/platform/commonUI/general/src/directives/MCTClickElsewhere.js +++ /dev/null @@ -1,77 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * The `mct-click-elsewhere` directive will evaluate its - * associated expression whenever a `mousedown` occurs anywhere - * outside of the element that has the `mct-click-elsewhere` - * directive attached. This is useful for dismissing popups - * and the like. - */ - function MCTClickElsewhere($document) { - - // Link; install event handlers. - function link(scope, element, attrs) { - // Keep a reference to the body, to attach/detach - // mouse event handlers; mousedown and mouseup cannot - // only be attached to the element being linked, as the - // mouse may leave this element during the drag. - var body = $document.find('body'); - - function clickBody(event) { - var x = event.clientX, - y = event.clientY, - rect = element[0].getBoundingClientRect(), - xMin = rect.left, - xMax = xMin + rect.width, - yMin = rect.top, - yMax = yMin + rect.height; - - if (x < xMin || x > xMax || y < yMin || y > yMax) { - scope.$apply(function () { - scope.$eval(attrs.mctClickElsewhere); - }); - } - } - - body.on("mousedown", clickBody); - scope.$on("$destroy", function () { - body.off("mousedown", clickBody); - }); - } - - return { - // mct-drag only makes sense as an attribute - restrict: "A", - // Link function, to install event handlers - link: link - }; - } - - return MCTClickElsewhere; - } -); - diff --git a/platform/commonUI/general/src/directives/MCTContainer.js b/platform/commonUI/general/src/directives/MCTContainer.js deleted file mode 100644 index ed66039fea..0000000000 --- a/platform/commonUI/general/src/directives/MCTContainer.js +++ /dev/null @@ -1,91 +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. - *****************************************************************************/ - -/** - * Module defining MCTContainer. Created by vwoeltje on 11/17/14. - */ -define( - [], - function () { - - /** - * The mct-container is similar to the mct-include directive - * insofar as it allows templates to be referenced by - * symbolic keys instead of by URL. Unlike mct-include, it - * supports transclusion. - * - * Unlike mct-include, mct-container accepts a key as a - * plain string attribute, instead of as an Angular - * expression. - * - * @memberof platform/commonUI/general - * @constructor - */ - function MCTContainer(containers) { - var containerMap = {}; - - // Initialize container map from extensions - containers.forEach(function (container) { - containerMap[container.key] = container; - }); - - return { - - // Allow only at the element level - restrict: 'E', - - // Support transclusion - transclude: true, - - // Create a new (non-isolate) scope - scope: true, - - // Populate initial scope based on attributes requested - // by the container definition - link: function (scope, element, attrs) { - var key = attrs.key, - container = containerMap[key], - alias = "container", - copiedAttributes = {}; - - if (container) { - alias = container.alias || alias; - (container.attributes || []).forEach(function (attr) { - copiedAttributes[attr] = attrs[attr]; - }); - } - - scope[alias] = copiedAttributes; - }, - - template: function (element, attrs) { - var key = attrs.key, - container = containerMap[key]; - - return container ? container.template : ""; - } - }; - } - - return MCTContainer; - } -); diff --git a/platform/commonUI/general/src/directives/MCTDrag.js b/platform/commonUI/general/src/directives/MCTDrag.js deleted file mode 100644 index d6b42ba2a0..0000000000 --- a/platform/commonUI/general/src/directives/MCTDrag.js +++ /dev/null @@ -1,177 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * The mct-drag directive allows drag functionality - * (in the mousedown-mousemove-mouseup sense, as opposed to - * the drag-and-drop sense) to be attached to specific - * elements. This takes the form of three attributes: - * - * * `mct-drag`: An Angular expression to evaluate during - * drag movement. - * * `mct-drag-down`: An Angular expression to evaluate - * when the drag begins. - * * `mct-drag-up`: An Angular expression to evaluate when - * dragging ends. - * - * In each case, a variable `delta` will be provided to the - * expression; this is a two-element array or the horizontal - * and vertical pixel offset of the current mouse position - * relative to the mouse position where dragging began. - * - * @memberof platform/commonUI/general - * @constructor - * - */ - function MCTDrag($document, agentService) { - - // Link; install event handlers. - function link(scope, element, attrs) { - // Keep a reference to the body, to attach/detach - // mouse event handlers; mousedown and mouseup cannot - // only be attached to the element being linked, as the - // mouse may leave this element during the drag. - var body = $document.find('body'), - isMobile = agentService.isMobile(), - touchEvents, - initialPosition, - $event, - delta; - - if (isMobile) { - touchEvents = { - start: 'touchstart', - end: 'touchend', - move: 'touchmove' - }; - } else { - touchEvents = { - start: 'mousedown', - end: "mouseup", - move: "mousemove" - }; - } - - // Utility function to cause evaluation of mctDrag, - // mctDragUp, etc - function fireListener(name) { - // Evaluate the expression, with current delta - scope.$eval(attrs[name], { - delta: delta, - $event: $event - }); - - // Trigger prompt digestion - scope.$apply(); - } - - // Update positions (both actual and relative) - // based on a new mouse event object. - function updatePosition(event) { - // Get the current position, as an array - var currentPosition = [event.pageX, event.pageY]; - - // Track the initial position, if one hasn't been observed - initialPosition = initialPosition || currentPosition; - - // Compute relative position - delta = currentPosition.map(function (v, i) { - return v - initialPosition[i]; - }); - - // Also track the plain event for firing listeners - $event = event; - } - - // Called during a drag, on mousemove - function continueDrag(event) { - updatePosition(event); - fireListener("mctDrag"); - - // Don't show selection highlights, etc - event.preventDefault(); - - return false; - } - - // Called only when the drag ends (on mouseup) - function endDrag(event) { - // Detach event handlers - body.off(touchEvents.end, endDrag); - body.off(touchEvents.move, continueDrag); - - // Also call continueDrag, to fire mctDrag - // and do its usual position update - continueDrag(event); - - fireListener("mctDragUp"); - - // Clear out start-of-drag position, target - initialPosition = undefined; - - // Don't show selection highlights, etc - event.preventDefault(); - - return false; - } - - // Called on mousedown on the element - function startDrag(event) { - // Listen for mouse events at the body level, - // since the mouse may leave the element during - // the drag. - body.on(touchEvents.end, endDrag); - body.on(touchEvents.move, continueDrag); - - // Set an initial position - updatePosition(event); - - // Fire listeners, including mctDrag - fireListener("mctDragDown"); - fireListener("mctDrag"); - - // Don't show selection highlights, etc - event.preventDefault(); - - return false; - } - - // Listen for start event on the element - element.on(touchEvents.start, startDrag); - } - - return { - // mct-drag only makes sense as an attribute - restrict: "A", - // Link function, to install event handlers - link: link - }; - } - - return MCTDrag; - } -); - diff --git a/platform/commonUI/general/src/directives/MCTIndicators.js b/platform/commonUI/general/src/directives/MCTIndicators.js deleted file mode 100644 index 6245a8901f..0000000000 --- a/platform/commonUI/general/src/directives/MCTIndicators.js +++ /dev/null @@ -1,40 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - function MCTIndicators(openmct) { - return { - restrict: "E", - link: function link(scope, element) { - openmct.indicators.indicatorElements - .forEach(function (indicatorElement) { - element.append(indicatorElement); - }); - } - }; - } - - return MCTIndicators; - } -); diff --git a/platform/commonUI/general/src/directives/MCTPopup.js b/platform/commonUI/general/src/directives/MCTPopup.js deleted file mode 100644 index 58b145dfe5..0000000000 --- a/platform/commonUI/general/src/directives/MCTPopup.js +++ /dev/null @@ -1,72 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - function () { - - var TEMPLATE = "
    "; - - /** - * The `mct-popup` directive may be used to display elements - * which "pop up" over other parts of the page. Typically, this is - * done in conjunction with an `ng-if` to control the visibility - * of the popup. - * - * Example of usage: - * - * - * These are the contents of the popup! - * - * - * @constructor - * @memberof platform/commonUI/general - * @param $compile Angular's $compile service - * @param {platform/commonUI/general.PopupService} popupService - */ - function MCTPopup($compile, popupService) { - function link(scope, element, attrs, ctrl, transclude) { - var div = $compile(TEMPLATE)(scope), - rect = element.parent()[0].getBoundingClientRect(), - position = [rect.left, rect.top], - popup = popupService.display(div, position); - - div.addClass('t-popup'); - transclude(function (clone) { - div.append(clone); - }); - - scope.$on('$destroy', function () { - popup.dismiss(); - }); - } - - return { - restrict: "E", - transclude: true, - link: link, - scope: {} - }; - } - - return MCTPopup; - } -); diff --git a/platform/commonUI/general/src/directives/MCTResize.js b/platform/commonUI/general/src/directives/MCTResize.js deleted file mode 100644 index 338021e4dc..0000000000 --- a/platform/commonUI/general/src/directives/MCTResize.js +++ /dev/null @@ -1,123 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - // Default resize interval - var DEFAULT_INTERVAL = 100; - - /** - * The mct-resize directive allows the size of a displayed - * HTML element to be tracked. This is done by polling, - * since the DOM API does not currently provide suitable - * events to watch this reliably. - * - * Attributes related to this directive are interpreted as - * follows: - * - * * `mct-resize`: An Angular expression to evaluate when - * the size changes; the variable `bounds` will be provided - * with two fields, `width` and `height`, both in pixels. - * * `mct-resize-interval`: Optional; the interval, in milliseconds, - * at which to watch for updates. In some cases checking for - * resize can carry a cost (it forces recalculation of - * positions within the document) so it may be preferable to watch - * infrequently. If omitted, a default of 100ms will be used. - * This is an Angular expression, and it will be re-evaluated after - * each interval. - * - * @memberof platform/commonUI/general - * @constructor - * - */ - function MCTResize($timeout) { - - // Link; start listening for changes to an element's size - function link(scope, element, attrs) { - var lastBounds, - linking = true, - active = true; - - // Determine how long to wait before the next update - function currentInterval() { - return attrs.mctResizeInterval - ? scope.$eval(attrs.mctResizeInterval) - : DEFAULT_INTERVAL; - } - - // Evaluate mct-resize with the current bounds - function fireEval(bounds) { - // Only update when bounds actually change - if (!lastBounds - || lastBounds.width !== bounds.width - || lastBounds.height !== bounds.height) { - scope.$eval(attrs.mctResize, { bounds: bounds }); - if (!linking) { // Avoid apply-in-a-digest - scope.$apply(); - } - - lastBounds = bounds; - } - } - - // Callback to fire after each timeout; - // update bounds and schedule another timeout - function onInterval() { - if (!active) { - return; - } - - fireEval({ - width: element[0].offsetWidth, - height: element[0].offsetHeight - }); - $timeout(onInterval, currentInterval(), false); - } - - // Stop running in the background - function deactivate() { - active = false; - } - - // Unregister once out-of-scope - scope.$on("$destroy", deactivate); - - // Handle the initial callback - onInterval(); - - // Trigger scope.$apply on subsequent changes - linking = false; - } - - return { - // mct-resize only makes sense as an attribute - restrict: "A", - // Link function, to begin watching for changes - link: link - }; - } - - return MCTResize; - } -); diff --git a/platform/commonUI/general/src/directives/MCTScroll.js b/platform/commonUI/general/src/directives/MCTScroll.js deleted file mode 100644 index 791ccb8170..0000000000 --- a/platform/commonUI/general/src/directives/MCTScroll.js +++ /dev/null @@ -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. - *****************************************************************************/ - -define( - [], - function () { - - /** - * Implements `mct-scroll-x` and `mct-scroll-y` directives. Listens - * for scroll events and publishes their results into scope; watches - * scope and updates scroll state to match. This varies for x- and y- - * directives only by the attribute name chosen to find the expression, - * and the property (scrollLeft or scrollTop) managed within the - * element. - * - * This is exposed as two directives in `bundle.json`; the difference - * is handled purely by parameterization. - * - * @memberof platform/commonUI/general - * @constructor - * @param $parse Angular's $parse - * @param {string} property property to manage within the HTML element - * @param {string} attribute attribute to look at for the assignable - * Angular expression - */ - function MCTScroll($parse, property, attribute) { - function link(scope, element, attrs) { - var expr = attrs[attribute], - parsed = $parse(expr); - - // Set the element's scroll to match the scope's state - function updateElement(value) { - element[0][property] = value; - } - - // Handle event; assign to scroll state to scope - function updateScope() { - parsed.assign(scope, element[0][property]); - scope.$apply(expr); - } - - // Initialize state in scope - parsed.assign(scope, element[0][property]); - - // Update element state when value in scope changes - scope.$watch(expr, updateElement); - - // Update state in scope when element is scrolled - element.on('scroll', updateScope); - } - - return { - // Restrict to attributes - restrict: "A", - // Use this link function - link: link - }; - } - - return MCTScroll; - - } -); diff --git a/platform/commonUI/general/src/directives/MCTSelectable.js b/platform/commonUI/general/src/directives/MCTSelectable.js deleted file mode 100644 index df914f7f60..0000000000 --- a/platform/commonUI/general/src/directives/MCTSelectable.js +++ /dev/null @@ -1,83 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * The mct-selectable directive allows selection functionality - * (click) to be attached to specific elements. - * - * Example of how to use the directive: - * - * mct-selectable="{ - * // item is an optional domain object. - * item: domainObject, - * // Can define other arbitrary properties. - * elementProxy: element, - * controller: fixedController - * }" - * - * @memberof platform/commonUI/general - * @constructor - */ - function MCTSelectable(openmct) { - - // Link; install event handlers. - function link(scope, element, attrs) { - var isDestroyed = false; - scope.$on("$destroy", function () { - isDestroyed = true; - }); - - openmct.$injector.get('$timeout')(function () { - if (isDestroyed) { - return; - } - - var removeSelectable = openmct.selection.selectable( - element[0], - scope.$eval(attrs.mctSelectable), - Object.prototype.hasOwnProperty.call(attrs, 'mctInitSelect') - && scope.$eval(attrs.mctInitSelect) !== false - ); - - scope.$on("$destroy", function () { - removeSelectable(); - }); - }); - - } - - return { - // mct-selectable only makes sense as an attribute - restrict: "A", - // Link function, to install event handlers - link: link - }; - - } - - return MCTSelectable; - } -); diff --git a/platform/commonUI/general/src/directives/MCTSplitPane.js b/platform/commonUI/general/src/directives/MCTSplitPane.js deleted file mode 100644 index 70b7078d6a..0000000000 --- a/platform/commonUI/general/src/directives/MCTSplitPane.js +++ /dev/null @@ -1,263 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - // Pixel width to allocate for the splitter itself - var DEFAULT_ANCHOR = 'left', - POLLING_INTERVAL = 15, // milliseconds - CHILDREN_WARNING_MESSAGE = [ - "Invalid mct-split-pane contents.", - "This element should contain exactly three", - "child elements, where the middle-most element", - "is an mct-splitter." - ].join(" "), - ANCHOR_WARNING_MESSAGE = [ - "Unknown anchor provided to mct-split-pane,", - "defaulting to", - DEFAULT_ANCHOR + "." - ].join(" "), - ANCHORS = { - left: { - edge: "left", - opposite: "right", - dimension: "width", - orientation: "vertical" - }, - right: { - edge: "right", - opposite: "left", - dimension: "width", - orientation: "vertical", - reversed: true - }, - top: { - edge: "top", - opposite: "bottom", - dimension: "height", - orientation: "horizontal" - }, - bottom: { - edge: "bottom", - opposite: "top", - dimension: "height", - orientation: "horizontal", - reversed: true - } - }; - - /** - * Implements `mct-split-pane` directive. - * - * This takes the following attributes: - * * `position`: Two-way bound scope variable which will contain - * the pixel position of the splitter, offset from the appropriate - * edge. - * * `anchor`: Plain string, one of "left", "right", "top", - * or "bottom". - * - * When used, an `mct-split-pane` element should contain exactly - * three child elements, where the middle is an `mct-splitter` - * element. These should be included in either left-to-right - * or top-to-bottom order (depending on anchoring.) If the contents - * do not match this form, `mct-split-pane` will issue a warning - * and its behavior will be undefined. - * - * This directive works by setting the width of the element - * nearest the anchor edge, and then positioning the other elements - * based on its observed width. As such, `min-width`, `max-width`, - * etc. can be set on that element to control the splitter's - * allowable positions. - * - * @memberof platform/commonUI/general - * @constructor - */ - function MCTSplitPane($parse, $log, $interval, $window) { - function controller($scope, $element, $attrs) { - var anchorKey = $attrs.anchor || DEFAULT_ANCHOR, - positionParsed = $parse($attrs.position), - anchor, - activeInterval, - position, - splitterSize, - - alias = $attrs.alias !== undefined - ? "mctSplitPane-" + $attrs.alias : undefined, - - //convert string to number from localStorage - userWidthPreference = $window.localStorage.getItem(alias) === null - ? undefined : Number($window.localStorage.getItem(alias)); - - // Get relevant size (height or width) of DOM element - function getSize(domElement) { - return (anchor.orientation === 'vertical' - ? domElement.offsetWidth : domElement.offsetHeight); - } - - // Apply styles to child elements - function updateChildren(children) { - position = userWidthPreference || position; - - // Pick out correct elements to update, flowing from - // selected anchor edge. - var first = children.eq(anchor.reversed ? 2 : 0), - splitter = children.eq(1), - last = children.eq(anchor.reversed ? 0 : 2), - firstSize; - - splitterSize = getSize(splitter[0]); - first.css(anchor.edge, "0px"); - first.css(anchor.dimension, position + 'px'); - - // Get actual size (to obey min-width etc.) - firstSize = getSize(first[0]); - first.css(anchor.dimension, firstSize + 'px'); - splitter.css(anchor.edge, firstSize + 'px'); - splitter.css(anchor.opposite, "auto"); - - last.css(anchor.edge, firstSize + splitterSize + 'px'); - last.css(anchor.opposite, '0px'); - position = firstSize; - } - - // Update positioning of contained elements - function updateElementPositions() { - var children = $element.children(); - - // Check to make sure contents are well-formed - if (children.length !== 3 - || children[1].nodeName.toLowerCase() !== 'mct-splitter') { - $log.warn(CHILDREN_WARNING_MESSAGE); - - return; - } - - updateChildren(children); - } - - // Enforce minimum/maximum positions - function enforceExtrema() { - position = Math.max(position, 0); - position = Math.min(position, getSize($element[0])); - } - - // Getter-setter for the pixel offset of the splitter, - // relative to the current edge. - function getSetPosition(value) { - var prior = position; - if (typeof value === 'number') { - position = value; - enforceExtrema(); - updateElementPositions(); - - // Pass change up so this state can be shared - if (positionParsed.assign && position !== prior) { - positionParsed.assign($scope, position); - } - } - - return position; - } - - function setUserWidthPreference(value) { - if (alias) { - userWidthPreference = value; - } - } - - function persistToLocalStorage(value) { - if (alias) { - $window.localStorage.setItem(alias, value); - } - } - - // Dynamically apply a CSS class to elements when the user - // is actively resizing - function toggleClass(classToToggle) { - $element.children().toggleClass(classToToggle); - } - - // Make sure anchor parameter is something we know - if (!ANCHORS[anchorKey]) { - $log.warn(ANCHOR_WARNING_MESSAGE); - anchorKey = DEFAULT_ANCHOR; - } - - anchor = ANCHORS[anchorKey]; - - $scope.$watch($attrs.position, getSetPosition); - - $element.addClass("split-layout"); - $element.addClass(anchor.orientation); - - // Initialize positions - getSetPosition(getSize( - $element.children().eq(anchor.reversed ? 2 : 0)[0] - )); - - // And poll for position changes enforced by styles - activeInterval = $interval(function () { - getSetPosition(getSetPosition()); - }, POLLING_INTERVAL, 0, false); - // ...and stop polling when we're destroyed. - $scope.$on('$destroy', function () { - $interval.cancel(activeInterval); - }); - - // Interface exposed by controller, for mct-splitter to user - return { - anchor: function () { - return anchor; - }, - position: function (newPosition) { - if (arguments.length === 0) { - return getSetPosition(); - } - - setUserWidthPreference(newPosition); - - return getSetPosition(newPosition); - }, - startResizing: function () { - toggleClass('resizing'); - }, - endResizing: function (finalPosition) { - persistToLocalStorage(finalPosition); - toggleClass('resizing'); - } - }; - } - - return { - // Restrict to attributes - restrict: "E", - // Expose its controller - controller: ['$scope', '$element', '$attrs', controller] - }; - } - - return MCTSplitPane; - - } -); diff --git a/platform/commonUI/general/src/directives/MCTSplitter.js b/platform/commonUI/general/src/directives/MCTSplitter.js deleted file mode 100644 index 65c439d754..0000000000 --- a/platform/commonUI/general/src/directives/MCTSplitter.js +++ /dev/null @@ -1,90 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - // Pixel width to allocate for the splitter itself - var SPLITTER_TEMPLATE = "
    "; - - /** - * Implements `mct-splitter` directive. - * @memberof platform/commonUI/general - * @constructor - */ - function MCTSplitter() { - function link(scope, element, attrs, mctSplitPane) { - var initialPosition, - newPosition; - - element.addClass("splitter"); - - scope.splitter = { - // Begin moving this splitter - startMove: function () { - mctSplitPane.startResizing(); - initialPosition = mctSplitPane.position(); - }, - // Handle user changes to splitter position - move: function (delta) { - var anchor = mctSplitPane.anchor(), - index = anchor.orientation === "vertical" ? 0 : 1, - pixelDelta = delta[index] - * (anchor.reversed ? -1 : 1); - - // Update the position of this splitter - newPosition = initialPosition + pixelDelta; - - if (initialPosition !== newPosition) { - mctSplitPane.position(newPosition); - } - }, - // Grab the event when the user is done moving - // the splitter and pass it on - endMove: function () { - mctSplitPane.endResizing(newPosition); - } - }; - } - - return { - // Restrict to attributes - restrict: "E", - // Utilize the mct-split-pane controller - require: "^mctSplitPane", - // Expose its controller - link: link, - // Use the template defined above - template: SPLITTER_TEMPLATE, - // Create a new scope to put the splitter into - scope: true - }; - } - - return MCTSplitter; - - } -); diff --git a/platform/commonUI/general/src/directives/MCTTree.js b/platform/commonUI/general/src/directives/MCTTree.js deleted file mode 100644 index 1e930acac6..0000000000 --- a/platform/commonUI/general/src/directives/MCTTree.js +++ /dev/null @@ -1,86 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - 'angular', - '../ui/TreeView' -], function (angular, TreeView) { - function MCTTree(gestureService, openmct) { - function link(scope, element) { - if (!scope.allowSelection) { - scope.allowSelection = function () { - return true; - }; - } - - if (!scope.onSelection) { - scope.onSelection = function () {}; - } - - var currentSelection = scope.selectedObject; - var treeView = new TreeView(gestureService, openmct); - - function setSelection(domainObject, event) { - if (currentSelection === domainObject) { - return; - } - - if (!scope.allowSelection(domainObject)) { - treeView.value(currentSelection); - - return; - } - - currentSelection = domainObject; - scope.onSelection(domainObject); - scope.selectedObject = domainObject; - if (event && event instanceof MouseEvent) { - scope.$apply(); - } - } - - var unobserve = treeView.observe(setSelection); - - element.append(angular.element(treeView.elements())); - - scope.$watch('selectedObject', function (object) { - currentSelection = object; - treeView.value(object); - }); - scope.$watch('rootObject', treeView.model.bind(treeView)); - scope.$on('$destroy', unobserve); - } - - return { - restrict: "E", - link: link, - scope: { - rootObject: "=", - selectedObject: "=", - onSelection: "=?", - allowSelection: "=?" - } - }; - } - - return MCTTree; -}); diff --git a/platform/commonUI/general/src/filters/ReverseFilter.js b/platform/commonUI/general/src/filters/ReverseFilter.js deleted file mode 100644 index dce477d73e..0000000000 --- a/platform/commonUI/general/src/filters/ReverseFilter.js +++ /dev/null @@ -1,42 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define(function () { - - /** - * Implements the `reverse` filter, which reverses text strings. - * Useful in cases where text should be reversed for presentational - * reasons (e.g. in conjunction with CSS tricks involving text direction), - * allowing such behavior to be handled independently from the controller - * layer. - * - * @constructor - * @memberof platform/commonUI/general - */ - function ReverseFilter() { - return function reverse(value) { - return value && value.toString().split('').reverse().join(''); - }; - } - - return ReverseFilter; -}); diff --git a/platform/commonUI/general/src/services/Overlay.js b/platform/commonUI/general/src/services/Overlay.js deleted file mode 100644 index e1f6d1ebde..0000000000 --- a/platform/commonUI/general/src/services/Overlay.js +++ /dev/null @@ -1,186 +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. - *****************************************************************************/ - -/** - * Module defining OverlayService. Created by deeptailor on 03/29/2018 - */ - -define(['zepto'], function ($) { - var OVERLAY_TEMPLATE = '' -+ '
    ' -+ '
    ' -+ ' ' -+ '
    ' -+ '
    ' -+ '
    ' -+ ' Done' -+ '
    ' -+ '
    ' -+ '
    '; - - /* - * An Overlay Service when instantiated creates an overlay dialog. - * @param {Object} options The options object required to instantiate the overlay service - * options = { - * $document: document object, - * $scope: angular $scope object, - * element: node to be injected into overlay as a view, - * overlayWillMount: callback executed before overlay is injected, - * overlayWillUnmount: callback executed before overlay is removed, - * overlayDidMount: callback executed after overlay is injected, - * overlayDidUnmount: callback executed after overlay is removed - * browseBarButtons: an array of desired buttons to be added to the browse bar of the overlay. - * the array should consist of button objects containing: - * a) class - css class to be added to the button div - * b) title - desired button title - * c) clickHandler - callback to be added to the click event listener of the button - * } - * $document, $scope and element are required - */ - - function Overlay(options) { - this.element = options.$element; - this.document = options.$document[0]; - this.$scope = options.$scope; - - this.overlayWillMount = options.overlayWillMount; - this.overlayWillUnmount = options.overlayWillUnmount; - - this.overlayDidMount = options.overlayDidMount; - this.overlayDidUnmount = options.overlayDidUnmount; - - this.browseBarButtons = options.browseBarButtons || []; - this.buttons = []; - - this.openOverlay = this.openOverlay.bind(this); - this.closeOverlay = this.closeOverlay.bind(this); - this.toggleOverlay = this.toggleOverlay.bind(this); - this.removeButtons = this.removeButtons.bind(this); - - this.isOverlayOpen = false; - } - - Overlay.prototype.openOverlay = function () { - - if (this.overlayWillMount && typeof this.overlayWillMount === 'function') { - this.overlayWillMount(); - } - - this.overlay = this.document.createElement('div'); - $(this.overlay).addClass('abs overlay l-large-view'); - this.overlay.innerHTML = OVERLAY_TEMPLATE; - - this.overlayContainer = this.overlay.querySelector('.t-contents'); - - this.closeButton = this.overlay.querySelector('a.close'); - this.closeButton.addEventListener('click', this.toggleOverlay); - - this.doneButton = this.overlay.querySelector('a.t-done'); - this.doneButton.addEventListener('click', this.toggleOverlay); - - this.blocker = this.overlay.querySelector('.abs.blocker'); - this.blocker.addEventListener('click', this.toggleOverlay); - - this.document.body.appendChild(this.overlay); - - this.overlayContainer.appendChild(this.element); - - this.browseBar = this.overlay.querySelector('.object-browse-bar .right'); - - if (this.browseBarButtons && Array.isArray(this.browseBarButtons)) { - this.browseBarButtons.forEach(function (buttonObject) { - var button = newButtonTemplate(buttonObject.class, buttonObject.title); - this.browseBar.prepend(button); - button.addEventListener('click', buttonObject.clickHandler); - this.buttons.push(button); - }.bind(this)); - } - - if (this.overlayDidMount && typeof this.overlayDidMount === 'function') { - this.overlayDidMount(); - } - }; - - Overlay.prototype.closeOverlay = function () { - - if (this.overlayWillUnmount && typeof this.overlayWillUnmount === 'function') { - this.overlayWillUnmount(); - } - - this.overlayContainer.removeChild(this.element); - this.document.body.removeChild(this.overlay); - - this.closeButton.removeEventListener('click', this.toggleOverlay); - this.closeButton = undefined; - - this.doneButton.removeEventListener('click', this.toggleOverlay); - this.doneButton = undefined; - - this.blocker.removeEventListener('click', this.toggleOverlay); - this.blocker = undefined; - - this.overlayContainer = undefined; - this.overlay = undefined; - - this.removeButtons(); - - if (this.overlayDidUnmount && typeof this.overlayDidUnmount === 'function') { - this.overlayDidUnmount(); - } - }; - - Overlay.prototype.toggleOverlay = function (event) { - if (event) { - event.stopPropagation(); - } - - if (!this.isOverlayOpen) { - this.openOverlay(); - this.isOverlayOpen = true; - } else { - this.closeOverlay(); - this.isOverlayOpen = false; - } - }; - - Overlay.prototype.removeButtons = function () { - this.buttons.forEach(function (button) { - button.remove(); - }.bind(this)); - - this.buttons = []; - }; - - function newButtonTemplate(classString, title) { - var NEW_BUTTON_TEMPLATE = '' - + '' + title + '' - + ''; - - var button = document.createElement('div'); - $(button).addClass('holder flex-elem'); - button.innerHTML = NEW_BUTTON_TEMPLATE; - - return button; - } - - return Overlay; -}); diff --git a/platform/commonUI/general/src/services/Popup.js b/platform/commonUI/general/src/services/Popup.js deleted file mode 100644 index ec3a0ffa34..0000000000 --- a/platform/commonUI/general/src/services/Popup.js +++ /dev/null @@ -1,87 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - function () { - - /** - * A popup is an element that has been displayed at a particular - * location within the page. - * @constructor - * @memberof platform/commonUI/general - * @param element the jqLite-wrapped element - * @param {object} styles an object containing key-value pairs - * of styles used to position the element. - */ - function Popup(element, styles) { - this.styles = styles; - this.element = element; - - element.css(styles); - } - - /** - * Stop showing this popup. - */ - Popup.prototype.dismiss = function () { - this.element.remove(); - }; - - /** - * Check if this popup is positioned such that it appears to the - * left of its original location. - * @returns {boolean} true if the popup goes left - */ - Popup.prototype.goesLeft = function () { - return !this.styles.left; - }; - - /** - * Check if this popup is positioned such that it appears to the - * right of its original location. - * @returns {boolean} true if the popup goes right - */ - Popup.prototype.goesRight = function () { - return !this.styles.right; - }; - - /** - * Check if this popup is positioned such that it appears above - * its original location. - * @returns {boolean} true if the popup goes up - */ - Popup.prototype.goesUp = function () { - return !this.styles.top; - }; - - /** - * Check if this popup is positioned such that it appears below - * its original location. - * @returns {boolean} true if the popup goes down - */ - Popup.prototype.goesDown = function () { - return !this.styles.bottom; - }; - - return Popup; - } -); diff --git a/platform/commonUI/general/src/services/PopupService.js b/platform/commonUI/general/src/services/PopupService.js deleted file mode 100644 index b2094a86fc..0000000000 --- a/platform/commonUI/general/src/services/PopupService.js +++ /dev/null @@ -1,124 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['./Popup'], - function (Popup) { - - /** - * Displays popup elements at specific positions within the document. - * @memberof platform/commonUI/general - * @constructor - */ - function PopupService($document, $window) { - this.$document = $document; - this.$window = $window; - } - - /** - * Options controlling how the popup is displayed. - * - * @typedef PopupOptions - * @memberof platform/commonUI/general - * @property {number} [offsetX] the horizontal distance, in pixels, - * to offset the element in whichever direction it is - * displayed. Defaults to 0. - * @property {number} [offsetY] the vertical distance, in pixels, - * to offset the element in whichever direction it is - * displayed. Defaults to 0. - * @property {number} [marginX] the horizontal position, in pixels, - * after which to prefer to display the element to the left. - * If negative, this is relative to the right edge of the - * page. Defaults to half the window's width. - * @property {number} [marginY] the vertical position, in pixels, - * after which to prefer to display the element upward. - * If negative, this is relative to the right edge of the - * page. Defaults to half the window's height. - * @property {string} [leftClass] class to apply when shifting to the left - * @property {string} [rightClass] class to apply when shifting to the right - * @property {string} [upClass] class to apply when shifting upward - * @property {string} [downClass] class to apply when shifting downward - */ - - /** - * Display a popup at a particular location. The location chosen will - * be the corner of the element; the element will be positioned either - * to the left or the right of this point depending on available - * horizontal space, and will similarly be shifted upward or downward - * depending on available vertical space. - * - * @param element the jqLite-wrapped DOM element to pop up - * @param {number[]} position x,y position of the element, in - * pixel coordinates. Negative values are interpreted as - * relative to the right or bottom of the window. - * @param {PopupOptions} [options] additional options to control - * positioning of the popup - * @returns {platform/commonUI/general.Popup} the popup - */ - PopupService.prototype.display = function (element, position, options) { - var $document = this.$document, - $window = this.$window, - body = $document.find('body'), - winDim = [$window.innerWidth, $window.innerHeight], - styles = { position: 'absolute' }, - margin, - offset; - - function adjustNegatives(value, index) { - return value < 0 ? (value + winDim[index]) : value; - } - - // Defaults - options = options || {}; - offset = [ - options.offsetX !== undefined ? options.offsetX : 0, - options.offsetY !== undefined ? options.offsetY : 0 - ]; - margin = [options.marginX, options.marginY].map(function (m, i) { - return m === undefined ? (winDim[i] / 2) : m; - }).map(adjustNegatives); - - position = position.map(adjustNegatives); - - if (position[0] > margin[0]) { - styles.right = (winDim[0] - position[0] + offset[0]) + 'px'; - } else { - styles.left = (position[0] + offset[0]) + 'px'; - } - - if (position[1] > margin[1]) { - styles.bottom = (winDim[1] - position[1] + offset[1]) + 'px'; - } else { - styles.top = (position[1] + offset[1]) + 'px'; - } - - // Add the menu to the body - body.append(element); - - // Return a function to dismiss the bubble - return new Popup(element, styles); - }; - - return PopupService; - } -); - diff --git a/platform/commonUI/general/src/services/UrlService.js b/platform/commonUI/general/src/services/UrlService.js deleted file mode 100644 index 72de4de1c2..0000000000 --- a/platform/commonUI/general/src/services/UrlService.js +++ /dev/null @@ -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. - *****************************************************************************/ - -/** - * Module defining UrlService. - */ -define( - [], - function () { - - /** - * The url service handles calls for url paths - * using domain objects. - * @constructor - * @memberof platform/commonUI/general - */ - function UrlService($location) { - this.$location = $location; - } - - /** - * Returns the Url path for a specific domain object - * without the index.html path and the view path - * @param {string} mode value of browse or edit mode - * for the path - * @param {DomainObject} value of the domain object - * to get the path of - * @returns {string} URL for the domain object - */ - UrlService.prototype.urlForLocation = function (mode, domainObject) { - var context = domainObject - && domainObject.getCapability('context'), - objectPath = context ? context.getPath() : [], - ids = objectPath.map(function (domainObj) { - return domainObj.getId(); - }); - - // Parses the path together. Starts with the - // default index.html file, then the mode passed - // into the service, followed by ids in the url - // joined by '/', and lastly the view path from - // the current location - return mode + "/" + ids.slice(1).join("/"); - }; - - /** - * Returns the Url path for a specific domain object - * including the index.html path and the view path - * allowing a new tab to hold the correct characteristics - * @param {string} mode value of browse or edit mode - * for the path - * @param {DomainObject} value of the domain object - * to get the path of - * @returns {string} URL for the domain object - */ - UrlService.prototype.urlForNewTab = function (mode, domainObject) { - var search = this.$location.search(), - arr = []; - for (var key in search) { - if (Object.prototype.hasOwnProperty.call(search, key)) { - arr.push(key + '=' + search[key]); - } - } - - var searchPath = "?" + arr.join('&'), - newTabPath = - "#" + this.urlForLocation(mode, domainObject) - + searchPath; - - return newTabPath; - }; - - return UrlService; - } -); diff --git a/platform/commonUI/general/src/ui/ToggleView.js b/platform/commonUI/general/src/ui/ToggleView.js deleted file mode 100644 index 4f53614ccb..0000000000 --- a/platform/commonUI/general/src/ui/ToggleView.js +++ /dev/null @@ -1,65 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - 'zepto', - '../../res/templates/tree/toggle.html' -], function ($, toggleTemplate) { - function ToggleView(state) { - this.expanded = Boolean(state); - this.callbacks = []; - this.el = $(toggleTemplate); - this.el.on('click', function () { - this.value(!this.expanded); - }.bind(this)); - } - - ToggleView.prototype.value = function (state) { - this.expanded = state; - - if (state) { - this.el.addClass('c-disclosure-triangle--expanded'); - } else { - this.el.removeClass('c-disclosure-triangle--expanded'); - } - - this.callbacks.forEach(function (callback) { - callback(state); - }); - }; - - ToggleView.prototype.observe = function (callback) { - this.callbacks.push(callback); - - return function () { - this.callbacks = this.callbacks.filter(function (c) { - return c !== callback; - }); - }.bind(this); - }; - - ToggleView.prototype.elements = function () { - return this.el; - }; - - return ToggleView; -}); diff --git a/platform/commonUI/general/src/ui/TreeLabelView.js b/platform/commonUI/general/src/ui/TreeLabelView.js deleted file mode 100644 index 491b219d18..0000000000 --- a/platform/commonUI/general/src/ui/TreeLabelView.js +++ /dev/null @@ -1,97 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - 'zepto', - '../../res/templates/tree/tree-label.html' -], function ($, labelTemplate) { - - function TreeLabelView(gestureService) { - this.el = $(labelTemplate); - this.gestureService = gestureService; - } - - function isLink(domainObject) { - var location = domainObject.getCapability('location'); - - return location.isLink(); - } - - function getClass(domainObject) { - var type = domainObject.getCapability('type'); - - return type.getCssClass(); - } - - function removePreviousIconClass(el) { - $(el).removeClass(function (index, className) { - return (className.match (/\bicon-\S+/g) || []).join(' '); - }); - } - - TreeLabelView.prototype.updateView = function (domainObject) { - var titleEl = this.el.find('.t-title-label'), - iconEl = this.el.find('.t-item-icon'); - - removePreviousIconClass(iconEl); - - titleEl.text(domainObject ? domainObject.getModel().name : ""); - iconEl.addClass(domainObject ? getClass(domainObject) : ""); - - if (domainObject && isLink(domainObject)) { - iconEl.addClass('l-icon-link'); - } else { - iconEl.removeClass('l-icon-link'); - } - }; - - TreeLabelView.prototype.model = function (domainObject) { - if (this.unlisten) { - this.unlisten(); - delete this.unlisten; - } - - if (this.activeGestures) { - this.activeGestures.destroy(); - delete this.activeGestures; - } - - this.updateView(domainObject); - - if (domainObject) { - this.unlisten = domainObject.getCapability('mutation') - .listen(this.updateView.bind(this, domainObject)); - - this.activeGestures = this.gestureService.attachGestures( - this.elements(), - domainObject, - ['info', 'menu', 'drag'] - ); - } - }; - - TreeLabelView.prototype.elements = function () { - return this.el; - }; - - return TreeLabelView; -}); diff --git a/platform/commonUI/general/src/ui/TreeNodeView.js b/platform/commonUI/general/src/ui/TreeNodeView.js deleted file mode 100644 index e9cb38b26f..0000000000 --- a/platform/commonUI/general/src/ui/TreeNodeView.js +++ /dev/null @@ -1,158 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - 'zepto', - '../../res/templates/tree/node.html', - './ToggleView', - './TreeLabelView' -], function ($, nodeTemplate, ToggleView, TreeLabelView) { - - function TreeNodeView(gestureService, subtreeFactory, selectFn, openmct) { - this.li = $('
  • '); - this.openmct = openmct; - this.statusClasses = []; - - this.toggleView = new ToggleView(false); - this.toggleView.observe(function (state) { - if (state) { - if (!this.subtreeView) { - this.subtreeView = subtreeFactory(); - this.subtreeView.model(this.activeObject); - this.li.find('.c-tree__item-subtree').eq(0) - .append($(this.subtreeView.elements())); - } - - $(this.subtreeView.elements()).removeClass('hidden'); - } else if (this.subtreeView) { - $(this.subtreeView.elements()).addClass('hidden'); - } - }.bind(this)); - - this.labelView = new TreeLabelView(gestureService); - - $(this.labelView.elements()).on('click', function (event) { - selectFn(this.activeObject, event); - }.bind(this)); - - this.li.append($(nodeTemplate)); - this.li.find('span').eq(0) - .append($(this.toggleView.elements())) - .append($(this.labelView.elements())); - - this.model(undefined); - } - - TreeNodeView.prototype.updateStatusClasses = function (statuses) { - this.statusClasses.forEach(function (statusClass) { - this.li.removeClass(statusClass); - }.bind(this)); - - this.statusClasses = statuses.map(function (status) { - return 's-status-' + status; - }); - - this.statusClasses.forEach(function (statusClass) { - this.li.addClass(statusClass); - }.bind(this)); - }; - - TreeNodeView.prototype.model = function (domainObject) { - if (this.unlisten) { - this.unlisten(); - } - - this.activeObject = domainObject; - if (domainObject && domainObject.hasCapability('adapter')) { - var obj = domainObject.useCapability('adapter'); - var hasComposition = this.openmct.composition.get(obj) !== undefined; - if (hasComposition) { - $(this.toggleView.elements()).addClass('is-enabled'); - } else { - $(this.toggleView.elements()).removeClass('is-enabled'); - } - } - - if (domainObject && domainObject.hasCapability('status')) { - this.unlisten = domainObject.getCapability('status') - .listen(this.updateStatusClasses.bind(this)); - this.updateStatusClasses( - domainObject.getCapability('status').list() - ); - } - - this.labelView.model(domainObject); - if (this.subtreeView) { - this.subtreeView.model(domainObject); - } - }; - - function getIdPath(domainObject) { - var context = domainObject && domainObject.getCapability('context'); - - function getId(domainObj) { - return domainObj.getId(); - } - - return context ? context.getPath().map(getId) : []; - } - - TreeNodeView.prototype.value = function (domainObject) { - var activeIdPath = getIdPath(this.activeObject), - selectedIdPath = getIdPath(domainObject); - - if (this.onSelectionPath) { - this.li.find('.js-tree__item').eq(0).removeClass('is-selected'); - if (this.subtreeView) { - this.subtreeView.value(undefined); - } - } - - this.onSelectionPath = - Boolean(domainObject) - && Boolean(this.activeObject) - && (activeIdPath.length <= selectedIdPath.length) - && activeIdPath.every(function (id, index) { - return selectedIdPath[index] === id; - }); - - if (this.onSelectionPath) { - if (activeIdPath.length === selectedIdPath.length) { - this.li.find('.js-tree__item').eq(0).addClass('is-selected'); - } else { - // Expand to reveal the selection - this.toggleView.value(true); - this.subtreeView.value(domainObject); - } - } - }; - - /** - * - * @returns {HTMLElement[]} - */ - TreeNodeView.prototype.elements = function () { - return this.li; - }; - - return TreeNodeView; -}); diff --git a/platform/commonUI/general/src/ui/TreeView.js b/platform/commonUI/general/src/ui/TreeView.js deleted file mode 100644 index a00042ffb3..0000000000 --- a/platform/commonUI/general/src/ui/TreeView.js +++ /dev/null @@ -1,141 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - 'zepto', - './TreeNodeView', - '../../res/templates/tree/wait-node.html' -], function ($, TreeNodeView, spinnerTemplate) { - - function TreeView(gestureService, openmct, selectFn) { - this.ul = $('
      '); - this.nodeViews = []; - this.callbacks = []; - this.selectFn = selectFn || this.value.bind(this); - this.gestureService = gestureService; - this.pending = false; - this.openmct = openmct; - } - - TreeView.prototype.newTreeView = function () { - return new TreeView(this.gestureService, this.openmct, this.selectFn); - }; - - TreeView.prototype.setSize = function (sz) { - var nodeView; - - while (this.nodeViews.length < sz) { - nodeView = new TreeNodeView( - this.gestureService, - this.newTreeView.bind(this), - this.selectFn, - this.openmct - ); - this.nodeViews.push(nodeView); - this.ul.append($(nodeView.elements())); - } - - while (this.nodeViews.length > sz) { - nodeView = this.nodeViews.pop(); - $(nodeView.elements()).remove(); - } - }; - - TreeView.prototype.loadComposition = function () { - var self = this, - domainObject = this.activeObject; - - function addNode(domainObj, index) { - self.nodeViews[index].model(domainObj); - } - - function addNodes(domainObjects) { - if (self.pending) { - self.pending = false; - self.nodeViews = []; - self.ul.empty(); - } - - if (domainObject === self.activeObject) { - self.setSize(domainObjects.length); - domainObjects.forEach(addNode); - self.updateNodeViewSelection(); - } - } - - domainObject.useCapability('composition') - .then(addNodes); - }; - - TreeView.prototype.model = function (domainObject) { - if (this.unlisten) { - this.unlisten(); - } - - this.activeObject = domainObject; - this.ul.empty(); - - if (domainObject && domainObject.hasCapability('composition')) { - this.pending = true; - this.ul.append($(spinnerTemplate)); - this.unlisten = domainObject.getCapability('mutation') - .listen(this.loadComposition.bind(this)); - this.loadComposition(domainObject); - } else { - this.setSize(0); - } - }; - - TreeView.prototype.updateNodeViewSelection = function () { - this.nodeViews.forEach(function (nodeView) { - nodeView.value(this.selectedObject); - }.bind(this)); - }; - - TreeView.prototype.value = function (domainObject, event) { - this.selectedObject = domainObject; - this.updateNodeViewSelection(); - this.callbacks.forEach(function (callback) { - callback(domainObject, event); - }); - }; - - TreeView.prototype.observe = function (callback) { - this.callbacks.push(callback); - - return function () { - this.callbacks = this.callbacks.filter(function (c) { - return c !== callback; - }); - }.bind(this); - }; - - /** - * - * @returns {HTMLElement[]} - */ - TreeView.prototype.elements = function () { - return this.ul; - }; - - return TreeView; -}); diff --git a/platform/commonUI/general/test/SplashScreenManagerSpec.js b/platform/commonUI/general/test/SplashScreenManagerSpec.js deleted file mode 100644 index 3aa1ee7a85..0000000000 --- a/platform/commonUI/general/test/SplashScreenManagerSpec.js +++ /dev/null @@ -1,90 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - '../src/SplashScreenManager' -], function (SplashScreenManager) { - - describe('SplashScreenManager', function () { - var $document, - splashElement; - - beforeEach(function () { - $document = jasmine.createSpyObj( - '$document', - ['querySelectorAll'] - ); - - splashElement = jasmine.createSpyObj( - 'splashElement', - ['addEventListener'] - ); - - splashElement.parentNode = jasmine.createSpyObj( - 'splashParent', - ['removeChild'] - ); - - splashElement.className = 'some-class-name'; - - $document.querySelectorAll.and.returnValue([splashElement]); - }); - - describe('when element exists', function () { - beforeEach(function () { - $document.querySelectorAll.and.returnValue([splashElement]); - - return new SplashScreenManager([$document]); - }); - - it('adds fade out class', function () { - expect(splashElement.className).toBe('some-class-name fadeout'); - }); - - it('removes the element when the transition ends', function () { - expect(splashElement.addEventListener) - .toHaveBeenCalledWith( - 'transitionend', - jasmine.any(Function) - ); - expect(splashElement.parentNode.removeChild) - .not - .toHaveBeenCalled(); - - splashElement.addEventListener.calls.mostRecent().args[1](); - expect(splashElement.parentNode.removeChild) - .toHaveBeenCalledWith(splashElement); - }); - }); - - it('does not error when element doesn\'t exist', function () { - $document.querySelectorAll.and.returnValue([]); - - function run() { - return new SplashScreenManager([$document]); - } - - expect(run).not.toThrow(); - }); - }); -}); - diff --git a/platform/commonUI/general/test/StyleSheetLoaderSpec.js b/platform/commonUI/general/test/StyleSheetLoaderSpec.js deleted file mode 100644 index e9fa8d51fb..0000000000 --- a/platform/commonUI/general/test/StyleSheetLoaderSpec.js +++ /dev/null @@ -1,120 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/StyleSheetLoader"], - function (StyleSheetLoader) { - - describe("The style sheet loader", function () { - var testStyleSheets, - mockDocument, - mockPlainDocument, - mockHead, - mockElement, - testBundle, - loader; // eslint-disable-line - - beforeEach(function () { - testBundle = { - path: "a/b", - resources: "c" - }; - - testStyleSheets = [ - { - stylesheetUrl: "d.css", - bundle: testBundle - }, - { - stylesheetUrl: "e.css", - bundle: testBundle - }, - { - stylesheetUrl: "f.css", - bundle: testBundle - } - ]; - - mockPlainDocument = - jasmine.createSpyObj("document", ["createElement"]); - mockDocument = [mockPlainDocument]; - mockDocument.find = jasmine.createSpy("$document.find"); - mockHead = jasmine.createSpyObj("head", ["append"]); - mockElement = jasmine.createSpyObj("link", ["setAttribute"]); - - mockDocument.find.and.returnValue(mockHead); - mockPlainDocument.createElement.and.returnValue(mockElement); - - loader = new StyleSheetLoader(testStyleSheets, mockDocument); - }); - - it("appends one link per stylesheet extension", function () { - expect(mockHead.append.calls.count()) - .toEqual(testStyleSheets.length); - }); - - it("appends links to the head", function () { - expect(mockDocument.find).toHaveBeenCalledWith('head'); - }); - - it("adjusts link locations", function () { - expect(mockElement.setAttribute) - .toHaveBeenCalledWith('href', "./a/b/c/d.css"); - }); - - describe("for themed stylesheets", function () { - var testTheme = "test-theme"; - - beforeEach(function () { - testStyleSheets = [{ - stylesheetUrl: "themed.css", - bundle: testBundle, - theme: testTheme - }, { - stylesheetUrl: "bad-theme.css", - bundle: testBundle, - theme: 'bad-theme' - }]; - - loader = new StyleSheetLoader( - testStyleSheets, - mockDocument, - testTheme - ); - }); - - it("includes matching themes", function () { - expect(mockElement.setAttribute) - .toHaveBeenCalledWith('href', "./a/b/c/themed.css"); - }); - - it("excludes mismatching themes", function () { - expect(mockElement.setAttribute) - .not - .toHaveBeenCalledWith('href', "./a/b/c/bad-theme.css"); - }); - }); - - }); - } -); - diff --git a/platform/commonUI/general/test/controllers/ActionGroupControllerSpec.js b/platform/commonUI/general/test/controllers/ActionGroupControllerSpec.js deleted file mode 100644 index 4b10e500f8..0000000000 --- a/platform/commonUI/general/test/controllers/ActionGroupControllerSpec.js +++ /dev/null @@ -1,114 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/controllers/ActionGroupController"], - function (ActionGroupController) { - - describe("The action group controller", function () { - var mockScope, - mockActions, - controller; - - function mockAction(metadata, index) { - var action = jasmine.createSpyObj( - "action" + index, - ["perform", "getMetadata"] - ); - action.getMetadata.and.returnValue(metadata); - - return action; - } - - beforeEach(function () { - mockActions = jasmine.createSpyObj("action", ["getActions"]); - mockScope = jasmine.createSpyObj("$scope", ["$watch"]); - controller = new ActionGroupController(mockScope); - }); - - it("watches scope that may change applicable actions", function () { - // The action capability - expect(mockScope.$watch).toHaveBeenCalledWith( - "action", - jasmine.any(Function) - ); - // The category of action to load - expect(mockScope.$watch).toHaveBeenCalledWith( - "parameters.category", - jasmine.any(Function) - ); - }); - - it("populates the scope with grouped and ungrouped actions", function () { - mockScope.action = mockActions; - mockScope.parameters = { category: "test" }; - - mockActions.getActions.and.returnValue([ - { - group: "a", - someKey: 0 - }, - { - group: "a", - someKey: 1 - }, - { - group: "b", - someKey: 2 - }, - { - group: "a", - someKey: 3 - }, - { - group: "b", - someKey: 4 - }, - { someKey: 5 }, - { someKey: 6 }, - { - group: "a", - someKey: 7 - }, - { someKey: 8 } - ].map(mockAction)); - - // Call the watch - mockScope.$watch.calls.mostRecent().args[1](); - - // Should have grouped and ungrouped actions in scope now - expect(mockScope.groups.length).toEqual(2); - expect(mockScope.groups[0].length).toEqual(4); // a - expect(mockScope.groups[1].length).toEqual(2); // b - expect(mockScope.ungrouped.length).toEqual(3); // ungrouped - }); - - it("provides empty arrays when no action capability is available", function () { - // Call the watch - mockScope.$watch.calls.mostRecent().args[1](); - - expect(mockScope.groups.length).toEqual(0); - expect(mockScope.ungrouped.length).toEqual(0); - }); - }); - } -); diff --git a/platform/commonUI/general/test/controllers/ClickAwayControllerSpec.js b/platform/commonUI/general/test/controllers/ClickAwayControllerSpec.js deleted file mode 100644 index 9eeefdea62..0000000000 --- a/platform/commonUI/general/test/controllers/ClickAwayControllerSpec.js +++ /dev/null @@ -1,92 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/controllers/ClickAwayController"], - function (ClickAwayController) { - - describe("The click-away controller", function () { - var mockDocument, - mockTimeout, - controller; - - beforeEach(function () { - mockDocument = jasmine.createSpyObj( - "$document", - ["on", "off"] - ); - mockTimeout = jasmine.createSpy('timeout'); - controller = new ClickAwayController( - mockDocument, - mockTimeout - ); - }); - - it("is initially inactive", function () { - expect(controller.isActive()).toBe(false); - }); - - it("does not listen to the document before being toggled", function () { - expect(mockDocument.on).not.toHaveBeenCalled(); - }); - - it("tracks enabled/disabled state when toggled", function () { - controller.toggle(); - expect(controller.isActive()).toBe(true); - controller.toggle(); - expect(controller.isActive()).toBe(false); - controller.toggle(); - expect(controller.isActive()).toBe(true); - controller.toggle(); - expect(controller.isActive()).toBe(false); - }); - - it("allows active state to be explicitly specified", function () { - controller.setState(true); - expect(controller.isActive()).toBe(true); - controller.setState(true); - expect(controller.isActive()).toBe(true); - controller.setState(false); - expect(controller.isActive()).toBe(false); - controller.setState(false); - expect(controller.isActive()).toBe(false); - }); - - it("registers a mouse listener when activated", function () { - controller.setState(true); - expect(mockDocument.on).toHaveBeenCalled(); - }); - - it("deactivates and detaches listener on document click", function () { - var callback, timeout; - controller.setState(true); - callback = mockDocument.on.calls.mostRecent().args[1]; - callback(); - timeout = mockTimeout.calls.mostRecent().args[0]; - timeout(); - expect(controller.isActive()).toEqual(false); - expect(mockDocument.off).toHaveBeenCalledWith("mouseup", callback); - }); - - }); - } -); diff --git a/platform/commonUI/general/test/controllers/DateTimeFieldControllerSpec.js b/platform/commonUI/general/test/controllers/DateTimeFieldControllerSpec.js deleted file mode 100644 index b2a54017a4..0000000000 --- a/platform/commonUI/general/test/controllers/DateTimeFieldControllerSpec.js +++ /dev/null @@ -1,221 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/controllers/DateTimeFieldController", "moment"], - function (DateTimeFieldController, moment) { - - var TEST_FORMAT = "YYYY-MM-DD HH:mm:ss"; - - describe("The DateTimeFieldController", function () { - var mockScope, - mockFormatService, - mockFormat, - controller; - - function fireWatch(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']); - mockFormatService = - jasmine.createSpyObj('formatService', ['getFormat']); - mockFormat = jasmine.createSpyObj('format', [ - 'parse', - 'validate', - 'format' - ]); - - mockFormatService.getFormat.and.returnValue(mockFormat); - - mockFormat.validate.and.callFake(function (text) { - return moment.utc(text, TEST_FORMAT).isValid(); - }); - mockFormat.parse.and.callFake(function (text) { - return moment.utc(text, TEST_FORMAT).valueOf(); - }); - mockFormat.format.and.callFake(function (value) { - return moment.utc(value).format(TEST_FORMAT); - }); - - mockScope.ngModel = { testField: 12321 }; - mockScope.field = "testField"; - mockScope.structure = { format: "someFormat" }; - mockScope.ngBlur = jasmine.createSpy('blur'); - - controller = new DateTimeFieldController( - mockScope, - mockFormatService - ); - fireWatch("ngModel[field]", mockScope.ngModel.testField); - }); - - it("updates text from model values", function () { - var testTime = mockFormat.parse("1977-05-25 17:30:00"); - mockScope.ngModel.testField = testTime; - fireWatch("ngModel[field]", testTime); - expect(mockScope.textValue).toEqual("1977-05-25 17:30:00"); - }); - - describe("when valid text is entered", function () { - var newText; - - beforeEach(function () { - newText = "1977-05-25 17:30:00"; - mockScope.textValue = newText; - fireWatch("textValue", newText); - }); - - it("updates models from user-entered text", function () { - expect(mockScope.ngModel.testField) - .toEqual(mockFormat.parse(newText)); - expect(mockScope.textInvalid).toBeFalsy(); - }); - - it("does not indicate a blur event", function () { - expect(mockScope.ngBlur).not.toHaveBeenCalled(); - }); - }); - - describe("when a date is chosen via the date picker", function () { - var newValue; - - beforeEach(function () { - newValue = 12345654321; - mockScope.pickerModel.value = newValue; - fireWatch("pickerModel.value", newValue); - }); - - it("updates models", function () { - expect(mockScope.ngModel.testField).toEqual(newValue); - }); - - it("fires a blur event", function () { - expect(mockScope.ngBlur).toHaveBeenCalled(); - }); - }); - - it("exposes toggle state for date-time picker", function () { - expect(mockScope.picker.active).toBe(false); - }); - - describe("when user input is invalid", function () { - var newText, oldText, oldValue; - - beforeEach(function () { - newText = "Not a date"; - oldValue = mockScope.ngModel.testField; - oldText = mockScope.textValue; - mockScope.textValue = newText; - fireWatch("textValue", newText); - }); - - it("displays error state", function () { - expect(mockScope.textInvalid).toBeTruthy(); - }); - - it("does not modify model state", function () { - expect(mockScope.ngModel.testField).toEqual(oldValue); - }); - - it("does not modify user input", function () { - expect(mockScope.textValue).toEqual(newText); - }); - - it("restores valid text values on request", function () { - mockScope.restoreTextValue(); - expect(mockScope.textValue).toEqual(oldText); - }); - }); - - it("does not modify valid but irregular user input", function () { - // Don't want the controller "fixing" bad or - // irregularly-formatted input out from under - // the user's fingertips. - var newText = "2015-3-3 01:02:04", - oldValue = mockScope.ngModel.testField; - - mockFormat.validate.and.returnValue(true); - mockFormat.parse.and.returnValue(42); - mockScope.textValue = newText; - fireWatch("textValue", newText); - - expect(mockScope.textValue).toEqual(newText); - expect(mockScope.ngModel.testField).toEqual(42); - expect(mockScope.ngModel.testField).not.toEqual(oldValue); - }); - - it("obtains a format from the format service", function () { - fireWatch('structure.format', mockScope.structure.format); - expect(mockFormatService.getFormat) - .toHaveBeenCalledWith(mockScope.structure.format); - }); - - it("throws an error for unknown formats", function () { - mockFormatService.getFormat.and.returnValue(undefined); - expect(function () { - fireWatch("structure.format", "some-format"); - }).toThrow(); - }); - - describe("using the obtained format", function () { - var testValue = 1234321, - testText = "some text"; - - beforeEach(function () { - mockFormat.validate.and.returnValue(true); - mockFormat.parse.and.returnValue(testValue); - mockFormat.format.and.returnValue(testText); - }); - - it("parses user input", function () { - var newText = "some other new text"; - mockScope.textValue = newText; - fireWatch("textValue", newText); - expect(mockFormat.parse).toHaveBeenCalledWith(newText); - expect(mockScope.ngModel.testField).toEqual(testValue); - }); - - it("validates user input", function () { - var newText = "some other new text"; - mockScope.textValue = newText; - fireWatch("textValue", newText); - expect(mockFormat.validate).toHaveBeenCalledWith(newText); - }); - - it("formats model data for display", function () { - var newValue = 42; - mockScope.ngModel.testField = newValue; - fireWatch("ngModel[field]", newValue); - expect(mockFormat.format).toHaveBeenCalledWith(newValue); - expect(mockScope.textValue).toEqual(testText); - }); - }); - - }); - } -); diff --git a/platform/commonUI/general/test/controllers/DateTimePickerControllerSpec.js b/platform/commonUI/general/test/controllers/DateTimePickerControllerSpec.js deleted file mode 100644 index 24c771d7a6..0000000000 --- a/platform/commonUI/general/test/controllers/DateTimePickerControllerSpec.js +++ /dev/null @@ -1,193 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/controllers/DateTimePickerController", "moment"], - function (DateTimePickerController, moment) { - - describe("The DateTimePickerController", function () { - var mockScope, - mockNow, - controller; - - function fireWatch(expr, value) { - mockScope.$watch.calls.all().forEach(function (call) { - if (call.args[0] === expr) { - call.args[1](value); - } - }); - } - - function fireWatchCollection(expr, value) { - mockScope.$watchCollection.calls.all().forEach(function (call) { - if (call.args[0] === expr) { - call.args[1](value); - } - }); - } - - beforeEach(function () { - mockScope = jasmine.createSpyObj( - "$scope", - ["$apply", "$watch", "$watchCollection"] - ); - mockScope.ngModel = {}; - mockScope.field = "testField"; - mockNow = jasmine.createSpy('now'); - controller = new DateTimePickerController(mockScope, mockNow); - }); - - it("watches the model that was passed in", function () { - expect(mockScope.$watch).toHaveBeenCalledWith( - "ngModel[field]", - jasmine.any(Function) - ); - }); - - it("updates value in model when values in scope change", function () { - mockScope.date = { - year: 1998, - month: 0, - day: 6 - }; - mockScope.time = { - hours: 12, - minutes: 34, - seconds: 56 - }; - fireWatchCollection("date", mockScope.date); - expect(mockScope.ngModel[mockScope.field]) - .toEqual(moment.utc("1998-01-06 12:34:56").valueOf()); - }); - - describe("once initialized with model state", function () { - var testTime = moment.utc("1998-01-06 12:34:56").valueOf(); - - beforeEach(function () { - fireWatch("ngModel[field]", testTime); - }); - - it("exposes date/time values in scope", function () { - expect(mockScope.date.year).toEqual(1998); - expect(mockScope.date.month).toEqual(0); // Months are zero-indexed - expect(mockScope.date.day).toEqual(6); - expect(mockScope.time.hours).toEqual(12); - expect(mockScope.time.minutes).toEqual(34); - expect(mockScope.time.seconds).toEqual(56); - }); - - it("provides names for time properties", function () { - Object.keys(mockScope.time).forEach(function (key) { - expect(mockScope.nameFor(key)) - .toEqual(jasmine.any(String)); - }); - }); - - it("provides options for time properties", function () { - Object.keys(mockScope.time).forEach(function (key) { - expect(mockScope.optionsFor(key)) - .toEqual(jasmine.any(Array)); - }); - }); - - it("exposes times to populate calendar as a table", function () { - // Verify that data structure is as expected by template - expect(mockScope.table).toEqual(jasmine.any(Array)); - expect(mockScope.table[0]).toEqual(jasmine.any(Array)); - expect(mockScope.table[0][0]).toEqual({ - year: jasmine.any(Number), - month: jasmine.any(Number), - day: jasmine.any(Number), - dayOfYear: jasmine.any(Number) - }); - }); - - it("contains the current date in its initial table", function () { - var matchingCell; - // Should be able to find the selected date - mockScope.table.forEach(function (row) { - row.forEach(function (cell) { - if (cell.dayOfYear === 6) { - matchingCell = cell; - } - }); - }); - expect(matchingCell).toEqual({ - year: 1998, - month: 0, - day: 6, - dayOfYear: 6 - }); - }); - - it("allows the displayed month to be advanced", function () { - // Around the edges of the displayed calendar we - // may be in previous or subsequent month, so - // test around the middle. - var i, originalMonth = mockScope.table[2][0].month; - - function mod12(month) { - return ((month % 12) + 12) % 12; - } - - for (i = 1; i <= 12; i += 1) { - mockScope.changeMonth(1); - expect(mockScope.table[2][0].month) - .toEqual(mod12(originalMonth + i)); - } - - for (i = 11; i >= -12; i -= 1) { - mockScope.changeMonth(-1); - expect(mockScope.table[2][0].month) - .toEqual(mod12(originalMonth + i)); - } - }); - - it("allows checking if a cell is in the current month", function () { - expect(mockScope.isInCurrentMonth(mockScope.table[2][0])) - .toBe(true); - }); - - it("allows cells to be selected", function () { - mockScope.select(mockScope.table[2][0]); - expect(mockScope.isSelected(mockScope.table[2][0])) - .toBe(true); - mockScope.select(mockScope.table[2][1]); - expect(mockScope.isSelected(mockScope.table[2][0])) - .toBe(false); - expect(mockScope.isSelected(mockScope.table[2][1])) - .toBe(true); - }); - - it("allows cells to be compared", function () { - var table = mockScope.table; - expect(mockScope.dateEquals(table[2][0], table[2][1])) - .toBe(false); - expect(mockScope.dateEquals(table[2][1], table[2][1])) - .toBe(true); - }); - - }); - - }); - } -); diff --git a/platform/commonUI/general/test/controllers/GetterSetterControllerSpec.js b/platform/commonUI/general/test/controllers/GetterSetterControllerSpec.js deleted file mode 100644 index bf6ea38913..0000000000 --- a/platform/commonUI/general/test/controllers/GetterSetterControllerSpec.js +++ /dev/null @@ -1,83 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/controllers/GetterSetterController"], - function (GetterSetterController) { - - describe("The getter-setter controller", function () { - var mockScope, - mockModel, - controller; - - beforeEach(function () { - mockScope = jasmine.createSpyObj("$scope", ["$watch"]); - mockModel = jasmine.createSpy("ngModel"); - mockScope.ngModel = mockModel; - controller = new GetterSetterController(mockScope); - }); - - it("watches for changes to external and internal mode", function () { - expect(mockScope.$watch).toHaveBeenCalledWith( - "ngModel()", - jasmine.any(Function) - ); - expect(mockScope.$watch).toHaveBeenCalledWith( - "getterSetter.value", - jasmine.any(Function) - ); - }); - - it("updates an external function when changes are detected", function () { - mockScope.getterSetter.value = "some new value"; - // Verify precondition - expect(mockScope.ngModel) - .not.toHaveBeenCalledWith("some new value"); - // Fire the matching watcher - mockScope.$watch.calls.all().forEach(function (call) { - if (call.args[0] === "getterSetter.value") { - call.args[1](mockScope.getterSetter.value); - } - }); - // Verify getter-setter was notified - expect(mockScope.ngModel) - .toHaveBeenCalledWith("some new value"); - }); - - it("updates internal state when external changes are detected", function () { - mockScope.ngModel.and.returnValue("some other new value"); - // Verify precondition - expect(mockScope.getterSetter.value).toBeUndefined(); - // Fire the matching watcher - mockScope.$watch.calls.all().forEach(function (call) { - if (call.args[0] === "ngModel()") { - call.args[1]("some other new value"); - } - }); - // Verify state in scope was updated - expect(mockScope.getterSetter.value) - .toEqual("some other new value"); - }); - - }); - } -); diff --git a/platform/commonUI/general/test/controllers/ObjectInspectorControllerSpec.js b/platform/commonUI/general/test/controllers/ObjectInspectorControllerSpec.js deleted file mode 100644 index 5934ee6504..0000000000 --- a/platform/commonUI/general/test/controllers/ObjectInspectorControllerSpec.js +++ /dev/null @@ -1,113 +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. - *****************************************************************************/ - -/** - * Created by shale on 08/24/2015. - */ -define( - ["../../src/controllers/ObjectInspectorController"], - function (ObjectInspectorController) { - - describe("The object inspector controller ", function () { - var mockScope, - mockObjectService, - mockPromise, - mockDomainObject, - mockContextCapability, - mockLocationCapability, - controller; - - beforeEach(function () { - mockScope = jasmine.createSpyObj( - "$scope", - ["$watch", "$on"] - ); - - mockObjectService = jasmine.createSpyObj( - "objectService", - ["getObjects"] - ); - mockPromise = jasmine.createSpyObj( - "promise", - ["then"] - ); - mockObjectService.getObjects.and.returnValue(mockPromise); - - mockDomainObject = jasmine.createSpyObj( - "selectedObject", - ["hasCapability", "getCapability", "useCapability", "getModel"] - ); - mockDomainObject.getModel.and.returnValue({location: 'somewhere'}); - mockDomainObject.hasCapability.and.returnValue(true); - - mockContextCapability = jasmine.createSpyObj( - "context capability", - ["getParent"] - ); - mockLocationCapability = jasmine.createSpyObj( - "location capability", - ["isLink"] - ); - - mockDomainObject.getCapability.and.callFake(function (param) { - if (param === 'location') { - return mockLocationCapability; - } else if (param === 'context') { - return mockContextCapability; - } else if (param === 'mutation') { - return { - listen: function () { - return true; - } - }; - } - }); - - mockScope.domainObject = mockDomainObject; - controller = new ObjectInspectorController(mockScope, mockObjectService); - }); - - it("watches for changes to the selected object", function () { - expect(mockScope.$watch).toHaveBeenCalledWith('domainObject', jasmine.any(Function)); - }); - - it("looks for contextual parent objects", function () { - mockScope.$watch.calls.mostRecent().args[1](); - expect(mockContextCapability.getParent).toHaveBeenCalled(); - }); - - it("if link, looks for primary parent objects", function () { - mockLocationCapability.isLink.and.returnValue(true); - - mockScope.$watch.calls.mostRecent().args[1](); - expect(mockDomainObject.getModel).toHaveBeenCalled(); - expect(mockObjectService.getObjects).toHaveBeenCalled(); - mockPromise.then.calls.mostRecent().args[0]({'somewhere': mockDomainObject}); - }); - - it("gets metadata", function () { - mockScope.$watch.calls.mostRecent().args[1](); - expect(mockDomainObject.useCapability).toHaveBeenCalledWith('metadata'); - }); - }); - } -); diff --git a/platform/commonUI/general/test/controllers/SelectorControllerSpec.js b/platform/commonUI/general/test/controllers/SelectorControllerSpec.js deleted file mode 100644 index b2b2397709..0000000000 --- a/platform/commonUI/general/test/controllers/SelectorControllerSpec.js +++ /dev/null @@ -1,186 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/controllers/SelectorController"], - function (SelectorController) { - - describe("The controller for the 'selector' control", function () { - var mockObjectService, - mockScope, - mockDomainObject, - mockType, - mockDomainObjects, - controller; - - function promiseOf(v) { - return (v || {}).then ? v : { - then: function (callback) { - return promiseOf(callback(v)); - } - }; - } - - function makeMockObject(id) { - var mockObject = jasmine.createSpyObj( - 'object-' + id, - ['getId'] - ); - mockObject.getId.and.returnValue(id); - - return mockObject; - } - - beforeEach(function () { - mockObjectService = jasmine.createSpyObj( - 'objectService', - ['getObjects'] - ); - mockScope = jasmine.createSpyObj( - '$scope', - ['$watch', '$watchCollection'] - ); - mockDomainObject = jasmine.createSpyObj( - 'domainObject', - ['getCapability', 'hasCapability'] - ); - mockType = jasmine.createSpyObj( - 'type', - ['instanceOf'] - ); - mockDomainObjects = {}; - - ["ROOT", "abc", "def", "xyz"].forEach(function (id) { - mockDomainObjects[id] = makeMockObject(id); - }); - - mockDomainObject.getCapability.and.returnValue(mockType); - mockObjectService.getObjects.and.returnValue(promiseOf(mockDomainObjects)); - mockScope.field = "testField"; - mockScope.ngModel = {}; - - controller = new SelectorController( - mockObjectService, - mockScope - ); - }); - - it("loads the root object", function () { - expect(mockObjectService.getObjects) - .toHaveBeenCalledWith(["ROOT"]); - }); - - it("watches for changes in selection in left-hand tree", function () { - var testObject = { - a: 123, - b: 456 - }; - // This test is sensitive to ordering of watch calls - expect(mockScope.$watch.calls.count()).toEqual(1); - // Make sure we're watching the correct object - controller.treeModel.selectedObject = testObject; - expect(mockScope.$watch.calls.all()[0].args[0]()).toBe(testObject); - }); - - it("watches for changes in controlled property", function () { - var testValue = ["a", "b", 1, 2]; - // This test is sensitive to ordering of watch calls - expect(mockScope.$watchCollection.calls.count()).toEqual(1); - // Make sure we're watching the correct object - mockScope.ngModel = { testField: testValue }; - expect(mockScope.$watchCollection.calls.all()[0].args[0]()).toBe(testValue); - }); - - it("rejects selection of incorrect types", function () { - mockScope.structure = { type: "someType" }; - mockType.instanceOf.and.returnValue(false); - controller.treeModel.selectedObject = mockDomainObject; - // Fire the watch - mockScope.$watch.calls.all()[0].args[1](mockDomainObject); - // Should have cleared the selection - expect(controller.treeModel.selectedObject).toBeUndefined(); - // Verify interaction (that instanceOf got a useful argument) - expect(mockType.instanceOf).toHaveBeenCalledWith("someType"); - }); - - it("permits selection of matching types", function () { - mockScope.structure = { type: "someType" }; - mockType.instanceOf.and.returnValue(true); - controller.treeModel.selectedObject = mockDomainObject; - // Fire the watch - mockScope.$watch.calls.all()[0].args[1](mockDomainObject); - // Should have preserved the selection - expect(controller.treeModel.selectedObject).toEqual(mockDomainObject); - // Verify interaction (that instanceOf got a useful argument) - expect(mockType.instanceOf).toHaveBeenCalledWith("someType"); - }); - - it("loads objects when the underlying list changes", function () { - var testIds = ["abc", "def", "xyz"]; - // This test is sensitive to ordering of watch calls - expect(mockScope.$watchCollection.calls.count()).toEqual(1); - // Make sure we're watching the correct object - mockScope.ngModel = { testField: testIds }; - // Fire the watch - mockScope.$watchCollection.calls.all()[0].args[1](testIds); - // Should have loaded the corresponding objects - expect(mockObjectService.getObjects).toHaveBeenCalledWith(testIds); - }); - - it("exposes the root object to populate the left-hand tree", function () { - expect(controller.root()).toEqual(mockDomainObjects.ROOT); - }); - - it("adds objects to the underlying model", function () { - expect(mockScope.ngModel.testField).toBeUndefined(); - controller.select(mockDomainObjects.def); - expect(mockScope.ngModel.testField).toEqual(["def"]); - controller.select(mockDomainObjects.abc); - expect(mockScope.ngModel.testField).toEqual(["def", "abc"]); - }); - - it("removes objects to the underlying model", function () { - controller.select(mockDomainObjects.def); - controller.select(mockDomainObjects.abc); - expect(mockScope.ngModel.testField).toEqual(["def", "abc"]); - controller.deselect(mockDomainObjects.def); - expect(mockScope.ngModel.testField).toEqual(["abc"]); - }); - - it("provides a list of currently-selected objects", function () { - // Verify precondition - expect(controller.selected()).toEqual([]); - // Select some objects - controller.select(mockDomainObjects.def); - controller.select(mockDomainObjects.abc); - // Fire the watch for the id changes... - mockScope.$watchCollection.calls.all()[0].args[1]( - mockScope.$watchCollection.calls.all()[0].args[0]() - ); - // Should have loaded and exposed those objects - expect(controller.selected()).toEqual( - [mockDomainObjects.def, mockDomainObjects.abc] - ); - }); - }); - } -); diff --git a/platform/commonUI/general/test/controllers/TimeRangeControllerSpec.js b/platform/commonUI/general/test/controllers/TimeRangeControllerSpec.js deleted file mode 100644 index 3b59d82fdd..0000000000 --- a/platform/commonUI/general/test/controllers/TimeRangeControllerSpec.js +++ /dev/null @@ -1,317 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/controllers/TimeRangeController", "moment"], - function (TimeRangeController, moment) { - - var SEC = 1000, - MIN = 60 * SEC, - HOUR = 60 * MIN, - DAY = 24 * HOUR; - - describe("The TimeRangeController", function () { - var mockScope, - mockFormatService, - testDefaultFormat, - mockTimeout, - mockNow, - mockFormat, - controller; - - function fireWatch(expr, value) { - mockScope.$watch.calls.all().forEach(function (call) { - if (call.args[0] === expr) { - call.args[1](value); - } - }); - } - - function fireWatchCollection(expr, value) { - mockScope.$watchCollection.calls.all().forEach(function (call) { - if (call.args[0] === expr) { - call.args[1](value); - } - }); - } - - beforeEach(function () { - mockTimeout = function (fn) { - return fn(); - }; - - mockScope = jasmine.createSpyObj( - "$scope", - ["$apply", "$watch", "$watchCollection"] - ); - mockFormatService = jasmine.createSpyObj( - "formatService", - ["getFormat"] - ); - testDefaultFormat = 'utc'; - mockFormat = jasmine.createSpyObj( - "format", - ["validate", "format", "parse"] - ); - - mockFormatService.getFormat.and.returnValue(mockFormat); - - mockFormat.format.and.callFake(function (value) { - return moment.utc(value).format("YYYY-MM-DD HH:mm:ss"); - }); - - mockNow = jasmine.createSpy('now'); - - controller = new TimeRangeController( - mockScope, - mockTimeout, - mockFormatService, - testDefaultFormat, - mockNow - ); - }); - - it("watches the model that was passed in", function () { - expect(mockScope.$watchCollection) - .toHaveBeenCalledWith("ngModel", jasmine.any(Function)); - }); - - it("exposes start time validator", function () { - var testValue = 42000000; - mockScope.formModel = { end: testValue }; - expect(controller.validateStart(testValue + 1)) - .toBe(false); - expect(controller.validateStart(testValue - 60 * 60 * 1000 - 1)) - .toBe(true); - }); - - it("exposes end time validator", function () { - var testValue = 42000000; - mockScope.formModel = { start: testValue }; - expect(controller.validateEnd(testValue - 1)) - .toBe(false); - expect(controller.validateEnd(testValue + 60 * 60 * 1000 + 1)) - .toBe(true); - }); - - describe("when changes are made via form entry", function () { - beforeEach(function () { - mockScope.ngModel = { - outer: { - start: DAY * 2, - end: DAY * 3 - }, - inner: { - start: DAY * 2.25, - end: DAY * 2.75 - } - }; - mockScope.formModel = { - start: DAY * 10000, - end: DAY * 11000 - }; - }); - - it('updates all changed bounds when requested', function () { - fireWatchCollection("formModel", mockScope.formModel); - fireWatch("formModel.start", mockScope.formModel.start); - fireWatch("formModel.end", mockScope.formModel.end); - - expect(mockScope.ngModel.outer.start) - .not.toEqual(mockScope.formModel.start); - expect(mockScope.ngModel.inner.start) - .not.toEqual(mockScope.formModel.start); - - expect(mockScope.ngModel.outer.end) - .not.toEqual(mockScope.formModel.end); - expect(mockScope.ngModel.inner.end) - .not.toEqual(mockScope.formModel.end); - - controller.updateBoundsFromForm(); - - expect(mockScope.ngModel.outer.start) - .toEqual(mockScope.formModel.start); - expect(mockScope.ngModel.inner.start) - .toEqual(mockScope.formModel.start); - - expect(mockScope.ngModel.outer.end) - .toEqual(mockScope.formModel.end); - expect(mockScope.ngModel.inner.end) - .toEqual(mockScope.formModel.end); - }); - - it('updates changed start bound when requested', function () { - fireWatchCollection("formModel", mockScope.formModel); - fireWatch("formModel.start", mockScope.formModel.start); - - expect(mockScope.ngModel.outer.start) - .not.toEqual(mockScope.formModel.start); - expect(mockScope.ngModel.inner.start) - .not.toEqual(mockScope.formModel.start); - - expect(mockScope.ngModel.outer.end) - .not.toEqual(mockScope.formModel.end); - expect(mockScope.ngModel.inner.end) - .not.toEqual(mockScope.formModel.end); - - controller.updateBoundsFromForm(); - - expect(mockScope.ngModel.outer.start) - .toEqual(mockScope.formModel.start); - expect(mockScope.ngModel.inner.start) - .toEqual(mockScope.formModel.start); - - expect(mockScope.ngModel.outer.end) - .not.toEqual(mockScope.formModel.end); - expect(mockScope.ngModel.inner.end) - .not.toEqual(mockScope.formModel.end); - }); - - it('updates changed end bound when requested', function () { - fireWatchCollection("formModel", mockScope.formModel); - fireWatch("formModel.end", mockScope.formModel.end); - - expect(mockScope.ngModel.outer.start) - .not.toEqual(mockScope.formModel.start); - expect(mockScope.ngModel.inner.start) - .not.toEqual(mockScope.formModel.start); - - expect(mockScope.ngModel.outer.end) - .not.toEqual(mockScope.formModel.end); - expect(mockScope.ngModel.inner.end) - .not.toEqual(mockScope.formModel.end); - - controller.updateBoundsFromForm(); - - expect(mockScope.ngModel.outer.start) - .not.toEqual(mockScope.formModel.start); - expect(mockScope.ngModel.inner.start) - .not.toEqual(mockScope.formModel.start); - - expect(mockScope.ngModel.outer.end) - .toEqual(mockScope.formModel.end); - expect(mockScope.ngModel.inner.end) - .toEqual(mockScope.formModel.end); - }); - }); - - describe("when dragged", function () { - beforeEach(function () { - mockScope.ngModel = { - outer: { - start: DAY * 1000, - end: DAY * 1001 - }, - inner: { - start: DAY * 1000 + HOUR * 3, - end: DAY * 1001 - HOUR * 3 - } - }; - mockScope.spanWidth = 1000; - fireWatch("spanWidth", mockScope.spanWidth); - fireWatchCollection("ngModel", mockScope.ngModel); - }); - - it("updates the start time for left drags", function () { - controller.startLeftDrag(); - controller.leftDrag(250); - expect(mockScope.ngModel.inner.start) - .toEqual(DAY * 1000 + HOUR * 9); - }); - - it("updates the end time for right drags", function () { - controller.startRightDrag(); - controller.rightDrag(-250); - expect(mockScope.ngModel.inner.end) - .toEqual(DAY * 1000 + HOUR * 15); - }); - - it("updates both start and end for middle drags", function () { - controller.startMiddleDrag(); - controller.middleDrag(-125); - expect(mockScope.ngModel.inner).toEqual({ - start: DAY * 1000, - end: DAY * 1000 + HOUR * 18 - }); - controller.middleDrag(250); - expect(mockScope.ngModel.inner).toEqual({ - start: DAY * 1000 + HOUR * 6, - end: DAY * 1001 - }); - }); - - it("enforces a minimum inner span", function () { - controller.startRightDrag(); - controller.rightDrag(-9999999); - expect(mockScope.ngModel.inner.end) - .toBeGreaterThan(mockScope.ngModel.inner.start); - }); - }); - - describe("when outer bounds are changed", function () { - beforeEach(function () { - mockScope.ngModel = { - outer: { - start: DAY * 1000, - end: DAY * 1001 - }, - inner: { - start: DAY * 1000 + HOUR * 3, - end: DAY * 1001 - HOUR * 3 - } - }; - mockScope.spanWidth = 1000; - fireWatch("spanWidth", mockScope.spanWidth); - fireWatchCollection("ngModel", mockScope.ngModel); - }); - - it("enforces a minimum inner span when outer span changes", function () { - mockScope.ngModel.outer.end = - mockScope.ngModel.outer.start - DAY * 100; - fireWatch( - "ngModel.outer.end", - mockScope.ngModel.outer.end - ); - expect(mockScope.ngModel.inner.end) - .toBeGreaterThan(mockScope.ngModel.inner.start); - }); - - }); - - it("watches for changes in format selection", function () { - expect(mockFormatService.getFormat) - .not.toHaveBeenCalledWith('test-format'); - fireWatch("parameters.format", 'test-format'); - expect(mockFormatService.getFormat) - .toHaveBeenCalledWith('test-format'); - }); - - it("throws an error for unknown formats", function () { - mockFormatService.getFormat.and.returnValue(undefined); - expect(function () { - fireWatch("parameters.format", "some-format"); - }).toThrow(); - }); - - }); - } -); diff --git a/platform/commonUI/general/test/controllers/ToggleControllerSpec.js b/platform/commonUI/general/test/controllers/ToggleControllerSpec.js deleted file mode 100644 index 9d7c8c4e04..0000000000 --- a/platform/commonUI/general/test/controllers/ToggleControllerSpec.js +++ /dev/null @@ -1,62 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/controllers/ToggleController"], - function (ToggleController) { - - describe("The toggle controller", function () { - var controller; - - beforeEach(function () { - controller = new ToggleController(); - }); - - it("is initially inactive", function () { - expect(controller.isActive()).toBe(false); - }); - - it("tracks enabled/disabled state when toggled", function () { - controller.toggle(); - expect(controller.isActive()).toBe(true); - controller.toggle(); - expect(controller.isActive()).toBe(false); - controller.toggle(); - expect(controller.isActive()).toBe(true); - controller.toggle(); - expect(controller.isActive()).toBe(false); - }); - - it("allows active state to be explicitly specified", function () { - controller.setState(true); - expect(controller.isActive()).toBe(true); - controller.setState(true); - expect(controller.isActive()).toBe(true); - controller.setState(false); - expect(controller.isActive()).toBe(false); - controller.setState(false); - expect(controller.isActive()).toBe(false); - }); - - }); - } -); diff --git a/platform/commonUI/general/test/controllers/TreeNodeControllerSpec.js b/platform/commonUI/general/test/controllers/TreeNodeControllerSpec.js deleted file mode 100644 index 2793a1799e..0000000000 --- a/platform/commonUI/general/test/controllers/TreeNodeControllerSpec.js +++ /dev/null @@ -1,209 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/controllers/TreeNodeController"], - function (TreeNodeController) { - - describe("The tree node controller", function () { - var mockScope, - mockTimeout, - mockDomainObject, - controller; - - function TestObject(id, context) { - return { - getId: function () { - return id; - }, - getCapability: function (key) { - return key === 'context' ? context : undefined; - } - }; - } - - beforeEach(function () { - mockScope = jasmine.createSpyObj("$scope", ["$watch", "$on", "$emit"]); - mockTimeout = jasmine.createSpy("$timeout"); - mockDomainObject = jasmine.createSpyObj( - "domainObject", - ["getId", "getCapability", "getModel", "useCapability"] - ); - - controller = new TreeNodeController(mockScope, mockTimeout); - }); - - it("allows tracking of expansion state", function () { - // The tree node tracks whether or not it has ever - // been expanded in order to lazily load the expanded - // portion of the tree. - expect(controller.hasBeenExpanded()).toBeFalsy(); - controller.trackExpansion(); - - // Expansion is tracked on a timeout, because too - // much expansion can result in an unstable digest. - expect(mockTimeout).toHaveBeenCalled(); - mockTimeout.calls.mostRecent().args[0](); - - expect(controller.hasBeenExpanded()).toBeTruthy(); - controller.trackExpansion(); - expect(controller.hasBeenExpanded()).toBeTruthy(); - }); - - it("tracks whether or not the represented object is currently navigated-to", function () { - // This is needed to highlight the current selection - var mockContext = jasmine.createSpyObj( - "context", - ["getParent", "getPath", "getRoot"] - ), - obj = new TestObject("test-object", mockContext); - - mockContext.getPath.and.returnValue([obj]); - - // Verify precondition - expect(controller.isSelected()).toBeFalsy(); - - // Change the represented domain object - mockScope.domainObject = obj; - - // Invoke the watch with the new selection - mockScope.$watch.calls.all()[0].args[1](obj); - - expect(controller.isSelected()).toBeTruthy(); - }); - - it("expands a node if it is on the navigation path", function () { - var mockParentContext = jasmine.createSpyObj( - "parentContext", - ["getParent", "getPath", "getRoot"] - ), - mockChildContext = jasmine.createSpyObj( - "childContext", - ["getParent", "getPath", "getRoot"] - ), - parent = new TestObject("parent", mockParentContext), - child = new TestObject("child", mockChildContext); - - mockChildContext.getParent.and.returnValue(parent); - mockChildContext.getPath.and.returnValue([parent, child]); - mockParentContext.getPath.and.returnValue([parent]); - - // Set up such that we are on, but not at the end of, a path - mockScope.ngModel = { selectedObject: child }; - mockScope.domainObject = parent; - mockScope.toggle = jasmine.createSpyObj("toggle", ["setState"]); - - // Invoke the watch with the new selection - mockScope.$watch.calls.all()[0].args[1](child); - - // Expansion is tracked on a timeout, because too - // much expansion can result in an unstable digest. - // Trigger that timeout. - expect(mockTimeout).toHaveBeenCalled(); - mockTimeout.calls.mostRecent().args[0](); - - expect(mockScope.toggle.setState).toHaveBeenCalledWith(true); - expect(controller.hasBeenExpanded()).toBeTruthy(); - expect(controller.isSelected()).toBeFalsy(); - - }); - - it("does not expand a node if it is not on the navigation path", function () { - var mockParentContext = jasmine.createSpyObj( - "parentContext", - ["getParent", "getPath", "getRoot"] - ), - mockChildContext = jasmine.createSpyObj( - "childContext", - ["getParent", "getPath", "getRoot"] - ), - parent = new TestObject("parent", mockParentContext), - child = new TestObject("child", mockChildContext); - - mockChildContext.getParent.and.returnValue(parent); - mockChildContext.getPath.and.returnValue([child, child]); - mockParentContext.getPath.and.returnValue([parent]); - - // Set up such that we are on, but not at the end of, a path - mockScope.ngModel = { selectedObject: child }; - mockScope.domainObject = parent; - mockScope.toggle = jasmine.createSpyObj("toggle", ["setState"]); - - // Invoke the watch with the new selection - mockScope.$watch.calls.all()[0].args[1](child); - - // Expansion is tracked on a timeout, because too - // much expansion can result in an unstable digest. - // We want to make sure no timeouts are pending here. - expect(mockTimeout).not.toHaveBeenCalled(); - expect(controller.hasBeenExpanded()).toBeFalsy(); - expect(controller.isSelected()).toBeFalsy(); - }); - - it("does not expand a node if no context is available", function () { - var mockParentContext = jasmine.createSpyObj( - "parentContext", - ["getParent", "getPath", "getRoot"] - ), - mockChildContext = jasmine.createSpyObj( - "childContext", - ["getParent", "getPath", "getRoot"] - ), - parent = new TestObject("parent", mockParentContext), - child = new TestObject("child", undefined); - - mockChildContext.getParent.and.returnValue(parent); - mockChildContext.getPath.and.returnValue([parent, child]); - mockParentContext.getPath.and.returnValue([parent]); - - // Set up such that we are on, but not at the end of, a path - mockScope.ngModel = { selectedObject: child }; - mockScope.domainObject = parent; - mockScope.toggle = jasmine.createSpyObj("toggle", ["setState"]); - - // Invoke the watch with the new selection - mockScope.$watch.calls.all()[0].args[1](child); - - expect(mockScope.toggle.setState).not.toHaveBeenCalled(); - expect(controller.hasBeenExpanded()).toBeFalsy(); - expect(controller.isSelected()).toBeFalsy(); - - }); - - it("exposes selected objects in scope", function () { - mockScope.domainObject = mockDomainObject; - mockScope.ngModel = {}; - controller.select(); - expect(mockScope.ngModel.selectedObject) - .toEqual(mockDomainObject); - }); - - it("invokes optional callbacks upon selection", function () { - mockScope.parameters = - { callback: jasmine.createSpy('callback') }; - controller.select(); - expect(mockScope.parameters.callback).toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/commonUI/general/test/controllers/ViewSwitcherControllerSpec.js b/platform/commonUI/general/test/controllers/ViewSwitcherControllerSpec.js deleted file mode 100644 index a6b104068f..0000000000 --- a/platform/commonUI/general/test/controllers/ViewSwitcherControllerSpec.js +++ /dev/null @@ -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. - *****************************************************************************/ - -/** - * MCTRepresentationSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/controllers/ViewSwitcherController"], - function (ViewSwitcherController) { - - describe("The view switcher controller", function () { - var mockScope, - mockTimeout, - controller; - - beforeEach(function () { - mockScope = jasmine.createSpyObj("$scope", ["$watch"]); - mockTimeout = jasmine.createSpy("$timeout"); - mockTimeout.and.callFake(function (cb) { - cb(); - }); - mockScope.ngModel = {}; - controller = new ViewSwitcherController(mockScope, mockTimeout); - }); - - it("watches for changes in applicable views", function () { - // The view capability is used by associated - // representations, so "view" in scope should always - // be the list of applicable views. The view switcher - // controller should be watching this. - expect(mockScope.$watch).toHaveBeenCalledWith( - "view", - jasmine.any(Function) - ); - }); - - it("maintains the current selection when views change", function () { - var views = [ - { - key: "a", - name: "View A" - }, - { - key: "b", - name: "View B" - }, - { - key: "c", - name: "View C" - }, - { - key: "d", - name: "View D" - } - ]; - mockScope.$watch.calls.mostRecent().args[1](views); - mockScope.ngModel.selected = views[1]; - - // Change the set of applicable views - mockScope.$watch.calls.mostRecent().args[1]([ - { - key: "a", - name: "View A" - }, - { - key: "b", - name: "View B" - }, - { - key: "x", - name: "View X" - } - ]); - - // "b" is still in there, should remain selected - expect(mockScope.ngModel.selected).toEqual(views[1]); - }); - - it("chooses a default if a selected view becomes inapplicable", function () { - var views = [ - { - key: "a", - name: "View A" - }, - { - key: "b", - name: "View B" - }, - { - key: "c", - name: "View C" - }, - { - key: "d", - name: "View D" - } - ]; - mockScope.$watch.calls.mostRecent().args[1](views); - mockScope.ngModel.selected = views[1]; - - // Change the set of applicable views - mockScope.$watch.calls.mostRecent().args[1]([ - { - key: "a", - name: "View A" - }, - { - key: "c", - name: "View C" - }, - { - key: "x", - name: "View X" - } - ]); - - // "b" is still in there, should remain selected - expect(mockScope.ngModel.selected).not.toEqual(views[1]); - }); - - // Use of a timeout avoids infinite digest problems when deeply - // nesting switcher-driven views (e.g. in a layout.) See WTD-689 - it("updates initial selection on a timeout", function () { - // Verify precondition - expect(mockTimeout).not.toHaveBeenCalled(); - - // Invoke the watch for set of views - mockScope.$watch.calls.mostRecent().args[1]([]); - - // Should have run on a timeout - expect(mockTimeout).toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/commonUI/general/test/directives/MCTClickElsewhereSpec.js b/platform/commonUI/general/test/directives/MCTClickElsewhereSpec.js deleted file mode 100644 index 3c1c7bdadd..0000000000 --- a/platform/commonUI/general/test/directives/MCTClickElsewhereSpec.js +++ /dev/null @@ -1,129 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/directives/MCTClickElsewhere"], - function (MCTClickElsewhere) { - - var JQLITE_METHODS = ["on", "off", "find", "parent"]; - - describe("The mct-click-elsewhere directive", function () { - var mockDocument, - mockScope, - mockElement, - testAttrs, - mockBody, - mockPlainEl, - testRect, - mctClickElsewhere; - - function testEvent(x, y) { - return { - clientX: x, - clientY: y, - preventDefault: jasmine.createSpy("preventDefault") - }; - } - - beforeEach(function () { - mockDocument = - jasmine.createSpyObj("$document", JQLITE_METHODS); - mockScope = - jasmine.createSpyObj("$scope", ["$eval", "$apply", "$on"]); - mockElement = - jasmine.createSpyObj("element", JQLITE_METHODS); - mockBody = - jasmine.createSpyObj("body", JQLITE_METHODS); - mockPlainEl = - jasmine.createSpyObj("htmlElement", ["getBoundingClientRect"]); - - testAttrs = { - mctClickElsewhere: "some Angular expression" - }; - testRect = { - left: 20, - top: 42, - width: 60, - height: 75 - }; - mockElement[0] = mockPlainEl; - mockPlainEl.getBoundingClientRect.and.returnValue(testRect); - - mockDocument.find.and.returnValue(mockBody); - - mctClickElsewhere = new MCTClickElsewhere(mockDocument); - mctClickElsewhere.link(mockScope, mockElement, testAttrs); - }); - - it("is valid as an attribute", function () { - expect(mctClickElsewhere.restrict).toEqual("A"); - }); - - it("detaches listeners when destroyed", function () { - expect(mockBody.off).not.toHaveBeenCalled(); - mockScope.$on.calls.all().forEach(function (call) { - if (call.args[0] === '$destroy') { - call.args[1](); - } - }); - expect(mockBody.off).toHaveBeenCalled(); - expect(mockBody.off.calls.mostRecent().args) - .toEqual(mockBody.on.calls.mostRecent().args); - }); - - it("listens for mousedown on the document's body", function () { - expect(mockBody.on) - .toHaveBeenCalledWith('mousedown', jasmine.any(Function)); - }); - - describe("when a click occurs outside the element's bounds", function () { - beforeEach(function () { - mockBody.on.calls.mostRecent().args[1](testEvent( - testRect.left + testRect.width + 10, - testRect.top + testRect.height + 10 - )); - }); - - it("triggers an evaluation of its related Angular expression", function () { - expect(mockScope.$apply).toHaveBeenCalled(); - mockScope.$apply.calls.mostRecent().args[0](); - expect(mockScope.$eval) - .toHaveBeenCalledWith(testAttrs.mctClickElsewhere); - }); - }); - - describe("when a click occurs within the element's bounds", function () { - beforeEach(function () { - mockBody.on.calls.mostRecent().args[1](testEvent( - testRect.left + testRect.width / 2, - testRect.top + testRect.height / 2 - )); - }); - - it("triggers no evaluation", function () { - expect(mockScope.$eval).not.toHaveBeenCalled(); - }); - }); - - }); - } -); diff --git a/platform/commonUI/general/test/directives/MCTContainerSpec.js b/platform/commonUI/general/test/directives/MCTContainerSpec.js deleted file mode 100644 index 4ba6b016ab..0000000000 --- a/platform/commonUI/general/test/directives/MCTContainerSpec.js +++ /dev/null @@ -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. - *****************************************************************************/ - -define( - ["../../src/directives/MCTContainer"], - function (MCTContainer) { - - describe("The mct-container directive", function () { - var testContainers = [ - { - bundle: { - path: "a", - resources: "b" - }, - template: "
      foo
      ", - key: "abc" - }, - { - bundle: { - path: "x", - resources: "y" - }, - template: "bar", - key: "xyz", - attributes: ["someAttr", "someOtherAttr"] - } - ], - mctContainer; - - beforeEach(function () { - mctContainer = new MCTContainer(testContainers); - }); - - it("is applicable to elements", function () { - expect(mctContainer.restrict).toEqual("E"); - }); - - it("creates a new (non-isolate) scope", function () { - expect(mctContainer.scope).toBe(true); - }); - - it("chooses a template based on key", function () { - expect(mctContainer.template( - undefined, - { key: "abc" } - )).toEqual(testContainers[0].template); - - expect(mctContainer.template( - undefined, - { key: "xyz" } - )).toEqual(testContainers[1].template); - }); - - it("copies attributes needed by the container", function () { - var scope = {}; - - mctContainer.link( - scope, - undefined, - { - key: "xyz", - someAttr: "some value", - someOtherAttr: "some other value", - someExtraAttr: "should not be present" - } - ); - - expect(scope.container.someAttr).toEqual("some value"); - expect(scope.container.someOtherAttr).toEqual("some other value"); - expect(scope.container.someExtraAttr).toBeUndefined(); - }); - - }); - } -); diff --git a/platform/commonUI/general/test/directives/MCTDragSpec.js b/platform/commonUI/general/test/directives/MCTDragSpec.js deleted file mode 100644 index 2691691c6a..0000000000 --- a/platform/commonUI/general/test/directives/MCTDragSpec.js +++ /dev/null @@ -1,303 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/directives/MCTDrag"], - function (MCTDrag) { - - var JQLITE_METHODS = ["on", "off", "find"]; - - describe("The mct-drag directive in Mobile", function () { - var mockDocument, - mockAgentService, - mockScope, - mockElement, - testAttrs, - mockBody, - mctDrag; - - function testEvent(x, y) { - return { - pageX: x, - pageY: y, - preventDefault: jasmine.createSpy("preventDefault") - }; - } - - beforeEach(function () { - mockDocument = - jasmine.createSpyObj("$document", JQLITE_METHODS); - mockAgentService = - jasmine.createSpyObj("agentService", ["isMobile"]); - mockScope = - jasmine.createSpyObj("$scope", ["$eval", "$apply"]); - mockElement = - jasmine.createSpyObj("element", JQLITE_METHODS); - mockBody = - jasmine.createSpyObj("body", JQLITE_METHODS); - - testAttrs = { - mctDragDown: "starting a drag", - mctDrag: "continuing a drag", - mctDragUp: "ending a drag" - }; - - mockDocument.find.and.returnValue(mockBody); - mockAgentService.isMobile.and.returnValue(true); - - mctDrag = new MCTDrag(mockDocument, mockAgentService); - mctDrag.link(mockScope, mockElement, testAttrs); - }); - - it("is valid as an attribute", function () { - expect(mctDrag.restrict).toEqual("A"); - }); - - it("listens for touchstart on its element", function () { - expect(mockElement.on).toHaveBeenCalledWith( - "touchstart", - jasmine.any(Function) - ); - - // Verify no interactions with body as well - expect(mockBody.on).not.toHaveBeenCalled(); - }); - - it("invokes mctDragDown when dragging begins", function () { - var event = testEvent(42, 60); - mockElement.on.calls.mostRecent().args[1](event); - expect(mockScope.$eval).toHaveBeenCalledWith( - testAttrs.mctDragDown, - { - delta: [0, 0], - $event: event - } - ); - }); - - it("listens for touchmove after dragging begins", function () { - mockElement.on.calls.mostRecent().args[1](testEvent(42, 60)); - expect(mockBody.on).toHaveBeenCalledWith( - "touchmove", - jasmine.any(Function) - ); - expect(mockBody.on).toHaveBeenCalledWith( - "touchend", - jasmine.any(Function) - ); - }); - - it("invokes mctDrag expression during drag", function () { - var event; - - mockElement.on.calls.mostRecent().args[1](testEvent(42, 60)); - - // Find and invoke the touchmove listener - mockBody.on.calls.all().forEach(function (call) { - if (call.args[0] === 'touchmove') { - call.args[1](event = testEvent(52, 200)); - } - }); - - // Should have passed that delta to mct-drag expression - expect(mockScope.$eval).toHaveBeenCalledWith( - testAttrs.mctDrag, - { - delta: [10, 140], - $event: event - } - ); - }); - - it("invokes mctDragUp expression after drag", function () { - var event; - - mockElement.on.calls.mostRecent().args[1](testEvent(42, 60)); - - // Find and invoke the touchmove listener - mockBody.on.calls.all().forEach(function (call) { - if (call.args[0] === 'touchmove') { - call.args[1](testEvent(52, 200)); - } - }); - // Find and invoke the touchmove listener - mockBody.on.calls.all().forEach(function (call) { - if (call.args[0] === 'touchend') { - call.args[1](event = testEvent(40, 71)); - } - }); - - // Should have passed that delta to mct-drag-up expression - // and that delta should have been relative to the - // initial position - expect(mockScope.$eval).toHaveBeenCalledWith( - testAttrs.mctDragUp, - { - delta: [-2, 11], - $event: event - } - ); - - // Should also have unregistered listeners - expect(mockBody.off).toHaveBeenCalled(); - }); - - }); - - describe("The mct-drag directive in Desktop", function () { - var mockDocument, - mockAgentService, - mockScope, - mockElement, - testAttrs, - mockBody, - mctDrag; - - function testEvent(x, y) { - return { - pageX: x, - pageY: y, - preventDefault: jasmine.createSpy("preventDefault") - }; - } - - beforeEach(function () { - mockDocument = - jasmine.createSpyObj("$document", JQLITE_METHODS); - mockAgentService = - jasmine.createSpyObj("agentService", ["isMobile"]); - mockScope = - jasmine.createSpyObj("$scope", ["$eval", "$apply"]); - mockElement = - jasmine.createSpyObj("element", JQLITE_METHODS); - mockBody = - jasmine.createSpyObj("body", JQLITE_METHODS); - - testAttrs = { - mctDragDown: "starting a drag", - mctDrag: "continuing a drag", - mctDragUp: "ending a drag" - }; - - mockDocument.find.and.returnValue(mockBody); - mockAgentService.isMobile.and.returnValue(false); - - mctDrag = new MCTDrag(mockDocument, mockAgentService); - mctDrag.link(mockScope, mockElement, testAttrs); - }); - - it("is valid as an attribute", function () { - expect(mctDrag.restrict).toEqual("A"); - }); - - it("listens for mousedown on its element", function () { - expect(mockElement.on).toHaveBeenCalledWith( - "mousedown", - jasmine.any(Function) - ); - - // Verify no interactions with body as well - expect(mockBody.on).not.toHaveBeenCalled(); - }); - - it("invokes mctDragDown when dragging begins", function () { - var event = testEvent(42, 60); - mockElement.on.calls.mostRecent().args[1](event); - expect(mockScope.$eval).toHaveBeenCalledWith( - testAttrs.mctDragDown, - { - delta: [0, 0], - $event: event - } - ); - }); - - it("listens for mousemove after dragging begins", function () { - mockElement.on.calls.mostRecent().args[1](testEvent(42, 60)); - expect(mockBody.on).toHaveBeenCalledWith( - "mousemove", - jasmine.any(Function) - ); - expect(mockBody.on).toHaveBeenCalledWith( - "mouseup", - jasmine.any(Function) - ); - }); - - it("invokes mctDrag expression during drag", function () { - var event; - - mockElement.on.calls.mostRecent().args[1](testEvent(42, 60)); - - // Find and invoke the mousemove listener - mockBody.on.calls.all().forEach(function (call) { - if (call.args[0] === 'mousemove') { - call.args[1](event = testEvent(52, 200)); - } - }); - - // Should have passed that delta to mct-drag expression - expect(mockScope.$eval).toHaveBeenCalledWith( - testAttrs.mctDrag, - { - delta: [10, 140], - $event: event - } - ); - }); - - it("invokes mctDragUp expression after drag", function () { - var event; - - mockElement.on.calls.mostRecent().args[1](testEvent(42, 60)); - - // Find and invoke the mousemove listener - mockBody.on.calls.all().forEach(function (call) { - if (call.args[0] === 'mousemove') { - call.args[1](testEvent(52, 200)); - } - }); - // Find and invoke the mousemove listener - mockBody.on.calls.all().forEach(function (call) { - if (call.args[0] === 'mouseup') { - call.args[1](event = testEvent(40, 71)); - } - }); - - // Should have passed that delta to mct-drag-up expression - // and that delta should have been relative to the - // initial position - expect(mockScope.$eval).toHaveBeenCalledWith( - testAttrs.mctDragUp, - { - delta: [-2, 11], - $event: event - } - ); - - // Should also have unregistered listeners - expect(mockBody.off).toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/commonUI/general/test/directives/MCTPopupSpec.js b/platform/commonUI/general/test/directives/MCTPopupSpec.js deleted file mode 100644 index d2a50e53d2..0000000000 --- a/platform/commonUI/general/test/directives/MCTPopupSpec.js +++ /dev/null @@ -1,132 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/directives/MCTPopup"], - function (MCTPopup) { - - var JQLITE_METHODS = [ - "on", - "off", - "find", - "parent", - "css", - "addClass", - "append" - ]; - - describe("The mct-popup directive", function () { - var mockCompile, - mockPopupService, - mockPopup, - mockScope, - mockElement, - testAttrs, - mockTransclude, - mockParentEl, - mockNewElement, - testRect, - mctPopup; - - beforeEach(function () { - mockCompile = - jasmine.createSpy("$compile"); - mockPopupService = - jasmine.createSpyObj("popupService", ["display"]); - mockPopup = - jasmine.createSpyObj("popup", ["dismiss"]); - mockScope = - jasmine.createSpyObj("$scope", ["$eval", "$apply", "$on"]); - mockElement = - jasmine.createSpyObj("element", JQLITE_METHODS); - mockTransclude = - jasmine.createSpy("transclude"); - mockParentEl = - jasmine.createSpyObj("parent", ["getBoundingClientRect"]); - mockNewElement = - jasmine.createSpyObj("newElement", JQLITE_METHODS); - - testAttrs = { - mctClickElsewhere: "some Angular expression" - }; - testRect = { - left: 20, - top: 42, - width: 60, - height: 75 - }; - - mockCompile.and.callFake(function () { - var mockFn = jasmine.createSpy(); - mockFn.and.returnValue(mockNewElement); - - return mockFn; - }); - mockElement.parent.and.returnValue([mockParentEl]); - mockParentEl.getBoundingClientRect.and.returnValue(testRect); - mockPopupService.display.and.returnValue(mockPopup); - - mctPopup = new MCTPopup(mockCompile, mockPopupService); - - mctPopup.link( - mockScope, - mockElement, - testAttrs, - null, - mockTransclude - ); - }); - - it("is valid as an element", function () { - expect(mctPopup.restrict).toEqual("E"); - }); - - describe("creates an element which", function () { - it("displays as a popup", function () { - expect(mockPopupService.display).toHaveBeenCalledWith( - mockNewElement, - [testRect.left, testRect.top] - ); - }); - - it("displays transcluded content", function () { - var mockClone = - jasmine.createSpyObj('clone', JQLITE_METHODS); - mockTransclude.calls.mostRecent().args[0](mockClone); - expect(mockNewElement.append) - .toHaveBeenCalledWith(mockClone); - }); - - it("is removed when its containing scope is destroyed", function () { - expect(mockPopup.dismiss).not.toHaveBeenCalled(); - mockScope.$on.calls.all().forEach(function (call) { - if (call.args[0] === '$destroy') { - call.args[1](); - } - }); - expect(mockPopup.dismiss).toHaveBeenCalled(); - }); - }); - - }); - } -); diff --git a/platform/commonUI/general/test/directives/MCTResizeSpec.js b/platform/commonUI/general/test/directives/MCTResizeSpec.js deleted file mode 100644 index 4927360494..0000000000 --- a/platform/commonUI/general/test/directives/MCTResizeSpec.js +++ /dev/null @@ -1,166 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/directives/MCTResize"], - function (MCTResize) { - - describe("The mct-resize directive", function () { - var mockTimeout, - mockScope, - testElement, - testAttrs, - mctResize; - - beforeEach(function () { - mockTimeout = jasmine.createSpy("$timeout"); - mockScope = jasmine.createSpyObj("$scope", ["$eval", "$on", "$apply"]); - - testElement = { - offsetWidth: 100, - offsetHeight: 200 - }; - testAttrs = { mctResize: "some-expr" }; - - mctResize = new MCTResize(mockTimeout); - }); - - it("is applicable as an attribute only", function () { - expect(mctResize.restrict).toEqual("A"); - }); - - it("starts tracking size changes upon link", function () { - expect(mockTimeout).not.toHaveBeenCalled(); - mctResize.link(mockScope, [testElement], testAttrs); - expect(mockTimeout).toHaveBeenCalledWith( - jasmine.any(Function), - jasmine.any(Number), - false - ); - expect(mockScope.$eval).toHaveBeenCalledWith( - testAttrs.mctResize, - { - bounds: { - width: 100, - height: 200 - } - } - ); - }); - - it("reports size changes on a timeout", function () { - mctResize.link(mockScope, [testElement], testAttrs); - - // Change the element's apparent size - testElement.offsetWidth = 300; - testElement.offsetHeight = 350; - - // Shouldn't know about this yet... - expect(mockScope.$eval).not.toHaveBeenCalledWith( - testAttrs.mctResize, - { - bounds: { - width: 300, - height: 350 - } - } - ); - - // Fire the timeout - mockTimeout.calls.mostRecent().args[0](); - - // Should have triggered an evaluation of mctResize - // with the new width & height - expect(mockScope.$eval).toHaveBeenCalledWith( - testAttrs.mctResize, - { - bounds: { - width: 300, - height: 350 - } - } - ); - }); - - it("stops size checking for size changes after destroy", function () { - mctResize.link(mockScope, [testElement], testAttrs); - - // First, make sure there's a $destroy observer - expect(mockScope.$on) - .toHaveBeenCalledWith("$destroy", jasmine.any(Function)); - - // Should have scheduled the first timeout - expect(mockTimeout.calls.count()).toEqual(1); - - // Fire the timeout - mockTimeout.calls.mostRecent().args[0](); - - // Should have scheduled another timeout - expect(mockTimeout.calls.count()).toEqual(2); - - // Broadcast a destroy event - mockScope.$on.calls.mostRecent().args[1](); - - testElement.offsetWidth = 300; - testElement.offsetHeight = 350; - mockScope.$eval.calls.reset(); - - // Fire the timeout - mockTimeout.calls.mostRecent().args[0](); - - // Should NOT have scheduled another timeout - expect(mockTimeout.calls.count()).toEqual(2); - expect(mockScope.$eval).not.toHaveBeenCalled(); - }); - - it("triggers a digest cycle when size changes", function () { - var applyCount; - mctResize.link(mockScope, [testElement], testAttrs); - applyCount = mockScope.$apply.calls.count(); - - // Change the element's apparent size - testElement.offsetWidth = 300; - testElement.offsetHeight = 350; - - // Fire the timeout - mockTimeout.calls.mostRecent().args[0](); - - // No more apply calls - expect(mockScope.$apply.calls.count()) - .toBeGreaterThan(applyCount); - }); - - it("does not trigger a digest cycle when size does not change", function () { - var applyCount; - mctResize.link(mockScope, [testElement], testAttrs); - applyCount = mockScope.$apply.calls.count(); - - // Fire the timeout - mockTimeout.calls.mostRecent().args[0](); - - // No more apply calls - expect(mockScope.$apply.calls.count()).toEqual(applyCount); - }); - - }); - } -); diff --git a/platform/commonUI/general/test/directives/MCTScrollSpec.js b/platform/commonUI/general/test/directives/MCTScrollSpec.js deleted file mode 100644 index cd0f3d1654..0000000000 --- a/platform/commonUI/general/test/directives/MCTScrollSpec.js +++ /dev/null @@ -1,114 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ -define( - ['../../src/directives/MCTScroll'], - function (MCTScroll) { - - var EVENT_PROPERTY = "testProperty", - ATTRIBUTE = "testAttribute", - EXPRESSION = "some.expression"; - - // MCTScroll is the commonality between mct-scroll-x and - // mct-scroll-y; it gets the event property to watch and - // the attribute which contains the associated assignable - // expression. - describe("An mct-scroll-* directive", function () { - var mockParse, - mockParsed, - mockScope, - mockElement, - testAttrs, - mctScroll; - - beforeEach(function () { - mockParse = jasmine.createSpy('$parse'); - mockParsed = jasmine.createSpy('parsed'); - mockParsed.assign = jasmine.createSpy('assign'); - - mockScope = jasmine.createSpyObj('$scope', ['$watch', '$apply']); - mockElement = [{ testProperty: 42 }]; - mockElement.on = jasmine.createSpy('on'); - - mockParse.and.returnValue(mockParsed); - - testAttrs = {}; - testAttrs[ATTRIBUTE] = EXPRESSION; - - mctScroll = new MCTScroll( - mockParse, - EVENT_PROPERTY, - ATTRIBUTE - ); - mctScroll.link(mockScope, mockElement, testAttrs); - }); - - it("is available for attributes", function () { - expect(mctScroll.restrict).toEqual('A'); - }); - - it("does not create an isolate scope", function () { - expect(mctScroll.scope).toBeUndefined(); - }); - - it("watches for changes in observed expression", function () { - expect(mockScope.$watch).toHaveBeenCalledWith( - EXPRESSION, - jasmine.any(Function) - ); - // Should have been only watch (other tests need this to be true) - expect(mockScope.$watch.calls.count()).toEqual(1); - }); - - it("listens for scroll events", function () { - expect(mockElement.on).toHaveBeenCalledWith( - 'scroll', - jasmine.any(Function) - ); - // Should have been only listener (other tests need this to be true) - expect(mockElement.on.calls.count()).toEqual(1); - }); - - it("publishes initial scroll state", function () { - expect(mockParse).toHaveBeenCalledWith(EXPRESSION); - expect(mockParsed.assign).toHaveBeenCalledWith(mockScope, 42); - }); - - it("updates scroll state when scope changes", function () { - mockScope.$watch.calls.mostRecent().args[1](64); - expect(mockElement[0].testProperty).toEqual(64); - }); - - it("updates scope when scroll state changes", function () { - mockElement[0].testProperty = 12321; - mockElement.on.calls.mostRecent().args[1]({ target: mockElement[0] }); - expect(mockParsed.assign).toHaveBeenCalledWith(mockScope, 12321); - expect(mockScope.$apply).toHaveBeenCalledWith(EXPRESSION); - }); - - // This would trigger an infinite digest exception - it("does not call $apply during construction", function () { - expect(mockScope.$apply).not.toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/commonUI/general/test/directives/MCTSplitPaneSpec.js b/platform/commonUI/general/test/directives/MCTSplitPaneSpec.js deleted file mode 100644 index a72539ede0..0000000000 --- a/platform/commonUI/general/test/directives/MCTSplitPaneSpec.js +++ /dev/null @@ -1,233 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/directives/MCTSplitPane"], - function (MCTSplitPane) { - - var JQLITE_METHODS = [ - 'on', - 'addClass', - 'children', - 'eq', - 'toggleClass', - 'css' - ]; - - describe("The mct-split-pane directive", function () { - var mockParse, - mockLog, - mockInterval, - mockParsed, - mctSplitPane, - mockWindow = {}; - - beforeEach(function () { - mockParse = jasmine.createSpy('$parse'); - mockLog = - jasmine.createSpyObj('$log', ['warn', 'info', 'debug']); - mockInterval = jasmine.createSpy('$interval'); - mockInterval.cancel = jasmine.createSpy('mockCancel'); - mockParsed = jasmine.createSpy('parsed'); - mockParsed.assign = jasmine.createSpy('assign'); - mockParse.and.returnValue(mockParsed); - - mockWindow.localStorage = { - store: {}, - setItem: function (key, value) { - this.store[key] = value; - }, - getItem: function (key) { - return this.store[key]; - } - }; - - mctSplitPane = new MCTSplitPane( - mockParse, - mockLog, - mockInterval, - mockWindow - ); - }); - - it("is only applicable as an element", function () { - expect(mctSplitPane.restrict).toEqual("E"); - }); - - describe("when its controller is applied", function () { - var mockScope, - mockElement, - testAttrs, - mockChildren, - mockFirstPane, - mockSplitter, - mockSecondPane, - controller; - - function fireOn(eventType) { - mockScope.$on.calls.all().forEach(function (call) { - if (call.args[0] === eventType) { - call.args[1](); - } - }); - } - - beforeEach(function () { - mockScope = - jasmine.createSpyObj('$scope', ['$apply', '$watch', '$on']); - mockElement = - jasmine.createSpyObj('element', JQLITE_METHODS); - testAttrs = {alias: 'rightSide'}; - mockChildren = - jasmine.createSpyObj('children', JQLITE_METHODS); - mockFirstPane = - jasmine.createSpyObj('firstPane', JQLITE_METHODS); - mockSplitter = - jasmine.createSpyObj('splitter', JQLITE_METHODS); - mockSecondPane = - jasmine.createSpyObj('secondPane', JQLITE_METHODS); - - mockElement.children.and.returnValue(mockChildren); - mockElement[0] = { - offsetWidth: 12321, - offsetHeight: 45654 - }; - mockChildren.eq.and.callFake(function (i) { - return [mockFirstPane, mockSplitter, mockSecondPane][i]; - }); - mockFirstPane[0] = { - offsetWidth: 123, - offsetHeight: 456 - }; - mockSplitter[0] = { - nodeName: 'mct-splitter', - offsetWidth: 10, - offsetHeight: 456 - }; - mockSecondPane[0] = { - offsetWidth: 10, - offsetHeight: 456 - }; - - mockChildren[0] = mockFirstPane[0]; - mockChildren[1] = mockSplitter[0]; - mockChildren[3] = mockSecondPane[0]; - mockChildren.length = 3; - - controller = mctSplitPane.controller[3]( - mockScope, - mockElement, - testAttrs - ); - }); - - it("sets an interval which does not trigger digests", function () { - expect(mockInterval.calls.mostRecent().args[3]).toBe(false); - }); - - it("exposes its splitter's initial position", function () { - expect(controller.position()).toEqual( - mockFirstPane[0].offsetWidth - ); - }); - - it("exposes the current anchoring mode", function () { - expect(controller.anchor()).toEqual({ - edge: 'left', - opposite: 'right', - dimension: 'width', - orientation: 'vertical' - }); - }); - - it("applies resizing class to children when resizing", function () { - controller.startResizing(); - expect(mockChildren.toggleClass).toHaveBeenCalledWith('resizing'); - }); - - it("removes resizing class from children when resizing action ends", function () { - controller.endResizing(0); - expect(mockChildren.toggleClass).toHaveBeenCalledWith('resizing'); - }); - - it("allows positions to be set", function () { - var testValue = mockChildren[0].offsetWidth + 50; - controller.position(testValue); - expect(mockFirstPane.css).toHaveBeenCalledWith( - 'width', - (testValue) + 'px' - ); - }); - - it("issues no warnings under nominal usage", function () { - expect(mockLog.warn).not.toHaveBeenCalled(); - }); - - it("warns if no mct-splitter is present", function () { - mockSplitter[0].nodeName = "not-mct-splitter"; - controller = mctSplitPane.controller[3]( - mockScope, - mockElement, - testAttrs - ); - expect(mockLog.warn).toHaveBeenCalled(); - }); - - it("warns if an unknown anchor key is given", function () { - testAttrs.anchor = "middle"; - controller = mctSplitPane.controller[3]( - mockScope, - mockElement, - testAttrs - ); - expect(mockLog.warn).toHaveBeenCalled(); - }); - - it("updates positions on a timer", function () { - mockFirstPane[0].offsetWidth += 100; - // Should not reflect the change yet - expect(controller.position()).not.toEqual( - mockFirstPane[0].offsetWidth - ); - mockInterval.calls.mostRecent().args[0](); - expect(controller.position()).toEqual( - mockFirstPane[0].offsetWidth - ); - }); - - it("cancels the active interval when scope is destroyed", function () { - expect(mockInterval.cancel).not.toHaveBeenCalled(); - fireOn('$destroy'); - expect(mockInterval.cancel).toHaveBeenCalled(); - }); - - it("saves user preference to localStorage when user is done resizing", function () { - controller.endResizing(100); - expect(Number(mockWindow.localStorage.getItem('mctSplitPane-rightSide'))).toEqual(100); - }); - - }); - - }); - - } -); diff --git a/platform/commonUI/general/test/directives/MCTSplitterSpec.js b/platform/commonUI/general/test/directives/MCTSplitterSpec.js deleted file mode 100644 index 73ab9a2e2d..0000000000 --- a/platform/commonUI/general/test/directives/MCTSplitterSpec.js +++ /dev/null @@ -1,110 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/directives/MCTSplitter"], - function (MCTSplitter) { - - describe("The mct-splitter directive", function () { - var mctSplitter; - - beforeEach(function () { - mctSplitter = new MCTSplitter(); - }); - - it("is applicable to elements", function () { - expect(mctSplitter.restrict).toEqual("E"); - }); - - it("depends on the mct-split-pane controller", function () { - expect(mctSplitter.require).toEqual("^mctSplitPane"); - }); - - describe("when linked", function () { - var mockScope, - mockElement, - testAttrs, - mockSplitPane; - - beforeEach(function () { - mockScope = jasmine.createSpyObj( - '$scope', - ['$on', '$watch'] - ); - mockElement = jasmine.createSpyObj( - 'element', - ['addClass'] - ); - testAttrs = {}; - mockSplitPane = jasmine.createSpyObj( - 'mctSplitPane', - ['position', 'startResizing', 'endResizing', 'anchor'] - ); - - mctSplitter.link( - mockScope, - mockElement, - testAttrs, - mockSplitPane - ); - }); - - it("adds a splitter class", function () { - expect(mockElement.addClass) - .toHaveBeenCalledWith('splitter'); - }); - - describe("and then manipulated", function () { - var testPosition; - - beforeEach(function () { - testPosition = 12321; - mockSplitPane.position.and.returnValue(testPosition); - mockSplitPane.anchor.and.returnValue({ - orientation: 'vertical', - reversed: false - }); - mockScope.splitter.startMove(); - }); - - it("tell's the splitter when it is resizing", function () { - expect(mockSplitPane.startResizing) - .toHaveBeenCalled(); - }); - - it("repositions during drag", function () { - mockScope.splitter.move([10, 0]); - expect(mockSplitPane.position) - .toHaveBeenCalledWith(testPosition + 10); - }); - - it("tell's the splitter when it is done resizing", function () { - mockScope.splitter.move([10, 0]); - mockScope.splitter.endMove(); - expect(mockSplitPane.endResizing).toHaveBeenCalledWith(testPosition + 10); - }); - - }); - }); - }); - } -); diff --git a/platform/commonUI/general/test/directives/MCTTreeSpec.js b/platform/commonUI/general/test/directives/MCTTreeSpec.js deleted file mode 100644 index 7b7b71b808..0000000000 --- a/platform/commonUI/general/test/directives/MCTTreeSpec.js +++ /dev/null @@ -1,146 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - '../../src/directives/MCTTree', - '../../src/ui/TreeView' -], function (MCTTree, TreeView) { - describe("The mct-tree directive", function () { - var mockParse, - mockGestureService, - mockExpr, - mctTree; - - function makeMockDomainObject(id) { - var mockDomainObject = jasmine.createSpyObj('domainObject-' + id, [ - 'getId', - 'getModel', - 'getCapability', - 'hasCapability' - ]); - mockDomainObject.getId.and.returnValue(id); - mockDomainObject.getModel.and.returnValue({}); - - return mockDomainObject; - } - - beforeEach(function () { - mockGestureService = jasmine.createSpyObj( - 'gestureService', - ['attachGestures'] - ); - mockParse = jasmine.createSpy('$parse'); - mockExpr = jasmine.createSpy('expr'); - mockExpr.assign = jasmine.createSpy('assign'); - mockParse.and.returnValue(mockExpr); - spyOn(TreeView.prototype, 'observe').and.callThrough(); - - mctTree = new MCTTree(mockParse, mockGestureService); - }); - - it("is applicable as an element", function () { - expect(mctTree.restrict).toEqual("E"); - }); - - it("two-way binds", function () { - expect(mctTree.scope).toEqual({ - rootObject: "=", - selectedObject: "=", - allowSelection: "=?", - onSelection: "=?" - }); - }); - - describe("link", function () { - var mockScope, - mockElement, - testAttrs; - - beforeEach(function () { - mockScope = - jasmine.createSpyObj('$scope', ['$watch', '$on', '$apply']); - mockElement = jasmine.createSpyObj('element', ['append']); - testAttrs = { mctModel: "some-expression" }; - mockScope.$parent = - jasmine.createSpyObj('$scope', ['$watch', '$on']); - mctTree.link(mockScope, mockElement, testAttrs); - }); - - it("populates the mct-tree element", function () { - expect(mockElement.append).toHaveBeenCalled(); - }); - - it("watches for selected-object expression in the parent", function () { - expect(mockScope.$watch).toHaveBeenCalledWith( - "selectedObject", - jasmine.any(Function) - ); - }); - - it("watches for changes to root-object", function () { - expect(mockScope.$watch).toHaveBeenCalledWith( - "rootObject", - jasmine.any(Function) - ); - }); - - it("listens for the $destroy event", function () { - expect(mockScope.$on).toHaveBeenCalledWith( - "$destroy", - jasmine.any(Function) - ); - }); - - it("watches for changes in tree view", function () { - - }); - - // https://github.com/nasa/openmct/issues/1114 - it("does not trigger $apply during $watches", function () { - mockScope.mctObject = makeMockDomainObject('root'); - mockScope.mctMode = makeMockDomainObject('selection'); - mockScope.$watch.calls.all().forEach(function (call) { - call.args[1](mockScope[call.args[0]]); - }); - expect(mockScope.$apply).not.toHaveBeenCalled(); - }); - it("does trigger $apply from tree manipulation", function () { - if (/PhantomJS/g.test(window.navigator.userAgent)) { - console.log('Unable to run test in PhantomJS due to lack of support for event constructors'); - - return; - } - - // White-boxy; we know this is the setter for the tree's value - var treeValueFn = TreeView.prototype.observe.calls.all()[0].args[0]; - - mockScope.mctObject = makeMockDomainObject('root'); - mockScope.mctMode = makeMockDomainObject('selection'); - - treeValueFn(makeMockDomainObject('other'), new MouseEvent("click")); - - expect(mockScope.$apply).toHaveBeenCalled(); - }); - }); - }); - -}); diff --git a/platform/commonUI/general/test/services/PopupServiceSpec.js b/platform/commonUI/general/test/services/PopupServiceSpec.js deleted file mode 100644 index 45d1d2edca..0000000000 --- a/platform/commonUI/general/test/services/PopupServiceSpec.js +++ /dev/null @@ -1,98 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/services/PopupService"], - function (PopupService) { - - describe("PopupService", function () { - var mockDocument, - testWindow, - mockBody, - mockElement, - popupService; - - beforeEach(function () { - mockDocument = jasmine.createSpyObj('$document', ['find']); - testWindow = { - innerWidth: 1000, - innerHeight: 800 - }; - mockBody = jasmine.createSpyObj('body', ['append']); - mockElement = jasmine.createSpyObj('element', [ - 'css', - 'remove' - ]); - - mockDocument.find.and.callFake(function (query) { - return query === 'body' && mockBody; - }); - - popupService = new PopupService(mockDocument, testWindow); - }); - - it("adds elements to the body of the document", function () { - popupService.display(mockElement, [0, 0]); - expect(mockBody.append).toHaveBeenCalledWith(mockElement); - }); - - describe("when positioned in appropriate quadrants", function () { - it("orients elements relative to the top-left", function () { - popupService.display(mockElement, [25, 50]); - expect(mockElement.css).toHaveBeenCalledWith({ - position: 'absolute', - left: '25px', - top: '50px' - }); - }); - - it("orients elements relative to the top-right", function () { - popupService.display(mockElement, [800, 50]); - expect(mockElement.css).toHaveBeenCalledWith({ - position: 'absolute', - right: '200px', - top: '50px' - }); - }); - - it("orients elements relative to the bottom-right", function () { - popupService.display(mockElement, [800, 650]); - expect(mockElement.css).toHaveBeenCalledWith({ - position: 'absolute', - right: '200px', - bottom: '150px' - }); - }); - - it("orients elements relative to the bottom-left", function () { - popupService.display(mockElement, [120, 650]); - expect(mockElement.css).toHaveBeenCalledWith({ - position: 'absolute', - left: '120px', - bottom: '150px' - }); - }); - }); - - }); - } -); diff --git a/platform/commonUI/general/test/services/PopupSpec.js b/platform/commonUI/general/test/services/PopupSpec.js deleted file mode 100644 index 1f2b6b98f6..0000000000 --- a/platform/commonUI/general/test/services/PopupSpec.js +++ /dev/null @@ -1,74 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/services/Popup"], - function (Popup) { - - describe("Popup", function () { - var mockElement, - testStyles, - popup; - - beforeEach(function () { - mockElement = - jasmine.createSpyObj('element', ['css', 'remove']); - testStyles = { - left: '12px', - top: '14px' - }; - popup = new Popup(mockElement, testStyles); - }); - - it("applies CSS styles when instantiated", function () { - expect(mockElement.css) - .toHaveBeenCalledWith(testStyles); - }); - - it("reports the orientation of the popup", function () { - var otherStyles = { - right: '12px', - bottom: '14px' - }, - otherPopup = new Popup(mockElement, otherStyles); - - expect(popup.goesLeft()).toBeFalsy(); - expect(popup.goesRight()).toBeTruthy(); - expect(popup.goesUp()).toBeFalsy(); - expect(popup.goesDown()).toBeTruthy(); - - expect(otherPopup.goesLeft()).toBeTruthy(); - expect(otherPopup.goesRight()).toBeFalsy(); - expect(otherPopup.goesUp()).toBeTruthy(); - expect(otherPopup.goesDown()).toBeFalsy(); - }); - - it("removes elements when dismissed", function () { - expect(mockElement.remove).not.toHaveBeenCalled(); - popup.dismiss(); - expect(mockElement.remove).toHaveBeenCalled(); - }); - - }); - - } -); diff --git a/platform/commonUI/general/test/services/UrlServiceSpec.js b/platform/commonUI/general/test/services/UrlServiceSpec.js deleted file mode 100644 index 1086084c27..0000000000 --- a/platform/commonUI/general/test/services/UrlServiceSpec.js +++ /dev/null @@ -1,98 +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. - *****************************************************************************/ - -/** - * MCTRepresentationSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/services/UrlService"], - function (UrlService) { - - describe("The url service", function () { - var urlService, - mockLocation, - mockDomainObject, - mockContext, - mockMode, - testViews; - - beforeEach(function () { - // Creates a mockLocation, used to - // do the view search - mockLocation = jasmine.createSpyObj( - "$location", - ["path", "search"] - ); - - // The mockDomainObject is initialized as a - // spy object to ultimately be passed into the - // urlService urlFor function - mockDomainObject = jasmine.createSpyObj( - "domainObject", - ["getId", "getCapability", "getModel", "useCapability"] - ); - mockContext = jasmine.createSpyObj('context', ['getPath']); - testViews = [ - { key: 'abc' }, - { - key: 'def', - someKey: 'some value' - }, - { key: 'xyz' } - ]; - mockMode = "browse"; - - // The mockContext is set a path - // for the mockDomainObject - mockContext.getPath.and.returnValue( - [mockDomainObject] - ); - - // view capability used with the testviews made - mockDomainObject.useCapability.and.callFake(function (c) { - return (c === 'view') && testViews; - }); - - // context capability used with the mockContext created - // so the variables including context in the urlFor are - // initialized and reached - mockDomainObject.getCapability.and.callFake(function (c) { - return c === 'context' && mockContext; - }); - - // Uses the mockLocation to get the current - // "mock" website's view - mockLocation.search.and.returnValue({ view: 'def' }); - - urlService = new UrlService(mockLocation); - }); - - it("get url for a location using domainObject and mode", function () { - urlService.urlForLocation(mockMode, mockDomainObject); - }); - - it("get url for a new tab using domainObject and mode", function () { - urlService.urlForNewTab(mockMode, mockDomainObject); - }); - }); - } -); diff --git a/platform/commonUI/general/test/suite.json b/platform/commonUI/general/test/suite.json deleted file mode 100644 index 09d0bfd097..0000000000 --- a/platform/commonUI/general/test/suite.json +++ /dev/null @@ -1,29 +0,0 @@ -[ - "controllers/ActionGroupController", - "controllers/BottomBarController", - "controllers/ClickAwayController", - "controllers/ContextMenuController", - "controllers/DateTimeFieldController", - "controllers/DateTimePickerController", - "controllers/GetterSetterController", - "controllers/ObjectInspectorController", - "controllers/SelectorController", - "controllers/TimeRangeController", - "controllers/ToggleController", - "controllers/TreeNodeController", - "controllers/ViewSwitcherController", - "directives/MCTClickElsewhere", - "directives/MCTContainer", - "directives/MCTDrag", - "directives/MCTPopup", - "directives/MCTResize", - "directives/MCTScroll", - "directives/MCTSplitPane", - "directives/MCTSplitter", - "filters/ReverseFilter", - "services/Popup", - "services/PopupService", - "services/UrlService", - "StyleSheetLoader", - "UnsupportedBrowserWarning" -] diff --git a/platform/commonUI/general/test/ui/TreeViewSpec.js b/platform/commonUI/general/test/ui/TreeViewSpec.js deleted file mode 100644 index b99d20661f..0000000000 --- a/platform/commonUI/general/test/ui/TreeViewSpec.js +++ /dev/null @@ -1,290 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - '../../src/ui/TreeView', - 'zepto' -], function (TreeView, $) { - - xdescribe("TreeView", function () { - var mockGestureService, - mockGestureHandle, - mockDomainObject, - mockMutation, - mockUnlisten, - testCapabilities, - treeView; - - function makeMockDomainObject(id, model, capabilities) { - var mockDomainObj = jasmine.createSpyObj( - 'domainObject-' + id, - [ - 'getId', - 'getModel', - 'getCapability', - 'hasCapability', - 'useCapability' - ] - ); - mockDomainObj.getId.and.returnValue(id); - mockDomainObj.getModel.and.returnValue(model); - mockDomainObj.hasCapability.and.callFake(function (c) { - return Boolean(capabilities[c]); - }); - mockDomainObj.getCapability.and.callFake(function (c) { - return capabilities[c]; - }); - mockDomainObj.useCapability.and.callFake(function (c) { - return capabilities[c] && capabilities[c].invoke(); - }); - - return mockDomainObj; - } - - beforeEach(function () { - mockGestureService = jasmine.createSpyObj( - 'gestureService', - ['attachGestures'] - ); - - mockGestureHandle = jasmine.createSpyObj('gestures', ['destroy']); - - mockGestureService.attachGestures.and.returnValue(mockGestureHandle); - - mockMutation = jasmine.createSpyObj('mutation', ['listen']); - mockUnlisten = jasmine.createSpy('unlisten'); - mockMutation.listen.and.returnValue(mockUnlisten); - - testCapabilities = { mutation: mockMutation }; - - mockDomainObject = - makeMockDomainObject('parent', {}, testCapabilities); - - treeView = new TreeView(mockGestureService); - }); - - describe("elements", function () { - var elements; - - beforeEach(function () { - elements = treeView.elements(); - }); - - it("is an unordered list", function () { - expect(elements[0].tagName.toLowerCase()) - .toEqual('ul'); - }); - }); - - describe("model", function () { - var mockComposition; - - function makeGenericCapabilities() { - var mockStatus = - jasmine.createSpyObj('status', ['listen', 'list']); - - mockStatus.list.and.returnValue([]); - - return { - context: jasmine.createSpyObj('context', ['getPath']), - type: jasmine.createSpyObj('type', ['getCssClass']), - location: jasmine.createSpyObj('location', ['isLink']), - mutation: jasmine.createSpyObj('mutation', ['listen']), - status: mockStatus - }; - } - - beforeEach(function () { - mockComposition = ['a', 'b', 'c'].map(function (id) { - var testCaps = makeGenericCapabilities(), - mockChild = - makeMockDomainObject(id, {}, testCaps); - - testCaps.context.getPath - .and.returnValue([mockDomainObject, mockChild]); - - return mockChild; - }); - - testCapabilities.composition = - jasmine.createSpyObj('composition', ['invoke']); - testCapabilities.composition.invoke - .and.returnValue(Promise.resolve(mockComposition)); - - treeView.model(mockDomainObject); - - return testCapabilities.composition.invoke(); - }); - - it("adds one node per composition element", function () { - expect(treeView.elements()[0].childElementCount) - .toEqual(mockComposition.length); - }); - - it("listens for mutation", function () { - expect(testCapabilities.mutation.listen) - .toHaveBeenCalledWith(jasmine.any(Function)); - }); - - describe("when mutation occurs", function () { - beforeEach(function () { - mockComposition.pop(); - testCapabilities.mutation.listen - .calls.mostRecent().args[0](mockDomainObject.getModel()); - - return testCapabilities.composition.invoke(); - }); - - it("continues to show one node per composition element", function () { - expect(treeView.elements()[0].childElementCount) - .toEqual(mockComposition.length); - }); - }); - - describe("when replaced with a non-compositional domain object", function () { - beforeEach(function () { - delete testCapabilities.composition; - treeView.model(mockDomainObject); - }); - - it("stops listening for mutation", function () { - expect(mockUnlisten).toHaveBeenCalled(); - }); - - it("removes all tree nodes", function () { - expect(treeView.elements()[0].childElementCount) - .toEqual(0); - }); - }); - - describe("when selection state changes", function () { - var selectionIndex = 1; - - beforeEach(function () { - treeView.value(mockComposition[selectionIndex]); - }); - - it("communicates selection state to an appropriate node", function () { - var selected = $(treeView.elements()[0]).find('.selected'); - expect(selected.length).toEqual(1); - }); - }); - - describe("when a context-less object is selected", function () { - beforeEach(function () { - var testCaps = makeGenericCapabilities(), - mockDomainObj = - makeMockDomainObject('xyz', {}, testCaps); - delete testCaps.context; - treeView.value(mockDomainObj); - }); - - it("clears all selection state", function () { - var selected = $(treeView.elements()[0]).find('.selected'); - expect(selected.length).toEqual(0); - }); - }); - - describe("when children contain children", function () { - beforeEach(function () { - var newCapabilities = makeGenericCapabilities(), - gcCapabilities = makeGenericCapabilities(), - mockNewChild = - makeMockDomainObject('d', {}, newCapabilities), - mockGrandchild = - makeMockDomainObject('gc', {}, gcCapabilities); - - newCapabilities.composition = - jasmine.createSpyObj('composition', ['invoke']); - newCapabilities.composition.invoke - .and.returnValue(Promise.resolve([mockGrandchild])); - mockComposition.push(mockNewChild); - - newCapabilities.context.getPath.and.returnValue([ - mockDomainObject, - mockNewChild - ]); - gcCapabilities.context.getPath.and.returnValue([ - mockDomainObject, - mockNewChild, - mockGrandchild - ]); - - testCapabilities.mutation.listen - .calls.mostRecent().args[0](mockDomainObject); - - return testCapabilities.composition.invoke().then(function () { - treeView.value(mockGrandchild); - - return newCapabilities.composition.invoke(); - }); - }); - - it("creates inner trees", function () { - expect($(treeView.elements()[0]).find('ul').length) - .toEqual(1); - }); - }); - - describe("when status changes", function () { - var testStatuses; - - beforeEach(function () { - var mockStatus = mockComposition[1].getCapability('status'); - - testStatuses = ['foo']; - - mockStatus.list.and.returnValue(testStatuses); - mockStatus.listen.calls.mostRecent().args[0](testStatuses); - }); - - it("reflects the status change in the tree", function () { - expect($(treeView.elements()).find('.s-status-foo').length) - .toEqual(1); - }); - }); - }); - - describe("observe", function () { - var mockCallback, - unobserve; - - beforeEach(function () { - mockCallback = jasmine.createSpy('callback'); - unobserve = treeView.observe(mockCallback); - }); - - it("notifies listeners when value is changed", function () { - treeView.value(mockDomainObject, {some: event}); - expect(mockCallback) - .toHaveBeenCalledWith(mockDomainObject, {some: event}); - }); - - it("does not notify listeners when deactivated", function () { - unobserve(); - treeView.value(mockDomainObject); - expect(mockCallback).not.toHaveBeenCalled(); - }); - }); - }); - -}); diff --git a/platform/commonUI/inspect/bundle.js b/platform/commonUI/inspect/bundle.js deleted file mode 100644 index d06017e182..0000000000 --- a/platform/commonUI/inspect/bundle.js +++ /dev/null @@ -1,117 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/gestures/InfoGesture", - "./src/gestures/InfoButtonGesture", - "./src/services/InfoService", - "./res/info-table.html", - "./res/info-bubble.html", - "./res/bubble.html", - "./res/templates/info-button.html" -], function ( - InfoGesture, - InfoButtonGesture, - InfoService, - infoTableTemplate, - infoBubbleTemplate, - bubbleTemplate, - infoButtonTemplate -) { - - return { - name: "platform/commonUI/inspect", - definition: { - "extensions": { - "templates": [ - { - "key": "info-table", - "template": infoTableTemplate - }, - { - "key": "info-bubble", - "template": infoBubbleTemplate - } - ], - "containers": [ - { - "key": "bubble", - "template": bubbleTemplate, - "attributes": [ - "bubbleTitle", - "bubbleLayout" - ], - "alias": "bubble" - } - ], - "gestures": [ - { - "key": "info", - "implementation": InfoGesture, - "depends": [ - "$timeout", - "agentService", - "infoService", - "INFO_HOVER_DELAY" - ] - }, - { - "key": "infobutton", - "implementation": InfoButtonGesture, - "depends": [ - "$document", - "agentService", - "infoService" - ] - } - ], - "services": [ - { - "key": "infoService", - "implementation": InfoService, - "depends": [ - "$compile", - "$rootScope", - "popupService", - "agentService" - ] - } - ], - "constants": [ - { - "key": "INFO_HOVER_DELAY", - "value": 2000 - } - ], - "representations": [ - { - "key": "info-button", - "template": infoButtonTemplate, - "gestures": [ - "infobutton" - ] - } - ] - } - } - }; -}); diff --git a/platform/commonUI/inspect/res/bubble.html b/platform/commonUI/inspect/res/bubble.html deleted file mode 100644 index f528d6432f..0000000000 --- a/platform/commonUI/inspect/res/bubble.html +++ /dev/null @@ -1,9 +0,0 @@ -
      -
      -
      - {{bubble.bubbleTitle}} -
      - -
      -
      diff --git a/platform/commonUI/inspect/res/info-bubble.html b/platform/commonUI/inspect/res/info-bubble.html deleted file mode 100644 index 82545cb29e..0000000000 --- a/platform/commonUI/inspect/res/info-bubble.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/platform/commonUI/inspect/res/info-table.html b/platform/commonUI/inspect/res/info-table.html deleted file mode 100644 index 0c89a498d9..0000000000 --- a/platform/commonUI/inspect/res/info-table.html +++ /dev/null @@ -1,8 +0,0 @@ - - - - - -
      {{property.name}} - {{property.value}} -
      diff --git a/platform/commonUI/inspect/res/infobubble.html b/platform/commonUI/inspect/res/infobubble.html deleted file mode 100644 index 3afb65c71c..0000000000 --- a/platform/commonUI/inspect/res/infobubble.html +++ /dev/null @@ -1,71 +0,0 @@ - -
      - - -
      -
      -
      -
      {{title}} -
      - - - - - -
      {{property.label}}{{property.value}}
      -
      -
      -
      diff --git a/platform/commonUI/inspect/res/templates/info-button.html b/platform/commonUI/inspect/res/templates/info-button.html deleted file mode 100644 index 5e22b5d6c7..0000000000 --- a/platform/commonUI/inspect/res/templates/info-button.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - diff --git a/platform/commonUI/inspect/src/InfoConstants.js b/platform/commonUI/inspect/src/InfoConstants.js deleted file mode 100644 index 0a66b51e4d..0000000000 --- a/platform/commonUI/inspect/src/InfoConstants.js +++ /dev/null @@ -1,47 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * This bundle provides support for object inspection (specifically, metadata - * show in bubbles on hover.) - * @namespace platform/commonUI/inspect - */ - -define({ - BUBBLE_TEMPLATE: "" - + "" - + "" - + "", - // Options and classes for bubble - BUBBLE_OPTIONS: { - offsetX: 0, - offsetY: -26 - }, - BUBBLE_MOBILE_POSITION: [0, -25], - // Max width and margins allowed for bubbles; - // defined in /platform/commonUI/general/res/sass/_constants.scss - BUBBLE_MARGIN_LR: 10, - BUBBLE_MAX_WIDTH: 300 -}); diff --git a/platform/commonUI/inspect/src/gestures/InfoButtonGesture.js b/platform/commonUI/inspect/src/gestures/InfoButtonGesture.js deleted file mode 100644 index 7c9482e073..0000000000 --- a/platform/commonUI/inspect/src/gestures/InfoButtonGesture.js +++ /dev/null @@ -1,115 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * The `info` gesture displays domain object metadata in a - * bubble on hover. - * - * @constructor - * @param $document Angular's `$document` - * @param {InfoService} infoService a service which shows info bubbles - * @param element jqLite-wrapped DOM element - * @param {DomainObject} domainObject the domain object for which to - * show information - */ - function InfoGestureButton($document, agentService, infoService, element, domainObject) { - var dismissBubble, - touchPosition, - body = $document.find('body'); - - function trackPosition(event) { - // Record touch position, so bubble can be shown at latest - // touch position, also offset by 22px to left (accounts for - // a finger-sized touch on the info button) - touchPosition = [event.clientX - 22, event.clientY]; - } - - // Hides the bubble and detaches the - // body hidebubble listener - function hideBubble() { - // If a bubble is showing, dismiss it - if (dismissBubble) { - dismissBubble(); - dismissBubble = undefined; - } - - // Detaches body touch listener - body.off('touchstart', hideBubble); - } - - // Displays the bubble by tracking position of - // touch, using infoService to display the bubble, - // and then on any body touch the bubble is dismissed - function showBubble(event) { - trackPosition(event); - event.stopPropagation(); - // Show the bubble, but on any touchstart on the - // body (anywhere) call hidebubble - dismissBubble = infoService.display( - "info-table", - domainObject.getModel().name, - domainObject.useCapability('metadata'), - touchPosition - ); - - // On any touch on the body, default body touches/events - // are prevented, the bubble is dismissed, and the touchstart - // body event is unbound, reallowing gestures - body.on('touchstart', function (evt) { - evt.preventDefault(); - hideBubble(); - body.unbind('touchstart'); - }); - } - - // Checks if you are on a mobile device, if the device is - // mobile (agentService.isMobile() = true), then - // the a click on something (info button) brings up - // the bubble - if (agentService.isMobile()) { - element.on('click', showBubble); - } - - return { - /** - * Detach any event handlers associated with this gesture. - * @memberof InfoGesture - * @method - */ - destroy: function () { - // Dismiss any active bubble... - hideBubble(); - // ...and detach listeners - element.off('click', showBubble); - } - }; - } - - return InfoGestureButton; - - } - -); diff --git a/platform/commonUI/inspect/src/gestures/InfoGesture.js b/platform/commonUI/inspect/src/gestures/InfoGesture.js deleted file mode 100644 index 424d3557f8..0000000000 --- a/platform/commonUI/inspect/src/gestures/InfoGesture.js +++ /dev/null @@ -1,149 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * The `info` gesture displays domain object metadata in a - * bubble on hover. - * - * @memberof platform/commonUI/inspect - * @constructor - * @implements {Gesture} - * @param $timeout Angular's `$timeout` - * @param {InfoService} infoService a service which shows info bubbles - * @param {number} delay delay, in milliseconds, before bubble appears - * @param element jqLite-wrapped DOM element - * @param {DomainObject} domainObject the domain object for which to - * show information - */ - function InfoGesture($timeout, agentService, infoService, delay, element, domainObject) { - var self = this; - - // Callback functions to preserve the "this" pointer (in the - // absence of Function.prototype.bind) - this.showBubbleCallback = function (event) { - self.showBubble(event); - }; - - this.hideBubbleCallback = function (event) { - self.hideBubble(event); - }; - - this.trackPositionCallback = function (event) { - self.trackPosition(event); - }; - - this.element = element; - this.$timeout = $timeout; - this.infoService = infoService; - this.delay = delay; - this.domainObject = domainObject; - - // Checks if you are on a mobile device, if the device is - // not mobile (agentService.isMobile() = false), then - // the pendingBubble and therefore hovering is allowed - if (!agentService.isMobile()) { - // Show bubble (on a timeout) on mouse over - element.on('mouseenter', this.showBubbleCallback); - } - } - - InfoGesture.prototype.trackPosition = function (event) { - // Record mouse position, so bubble can be shown at latest - // mouse position (not just where the mouse entered) - this.mousePosition = [event.clientX, event.clientY]; - }; - - InfoGesture.prototype.hideBubble = function () { - // If a bubble is showing, dismiss it - if (this.dismissBubble) { - this.dismissBubble(); - this.element.off('mouseleave', this.hideBubbleCallback); - this.dismissBubble = undefined; - } - - // If a bubble will be shown on a timeout, cancel that - if (this.pendingBubble) { - this.$timeout.cancel(this.pendingBubble); - this.element.off('mousemove', this.trackPositionCallback); - this.element.off('mouseleave', this.hideBubbleCallback); - this.pendingBubble = undefined; - } - - // Also clear mouse position so we don't have a ton of tiny - // arrays allocated while user mouses over things - this.mousePosition = undefined; - }; - - InfoGesture.prototype.showBubble = function (event) { - var self = this; - - function displayBubble() { - self.dismissBubble = self.infoService.display( - "info-table", - self.domainObject.getModel().name, - self.domainObject.useCapability('metadata'), - self.mousePosition - ); - self.element.off('mousemove', self.trackPositionCallback); - self.pendingBubble = undefined; - } - - this.trackPosition(event); - - // Do nothing if we're already scheduled to show a bubble. - // This may happen due to redundant event firings caused - // by https://github.com/angular/angular.js/issues/12795 - if (this.pendingBubble) { - return; - } - - // Also need to track position during hover - this.element.on('mousemove', this.trackPositionCallback); - - // Show the bubble, after a suitable delay (if mouse has - // left before this time is up, this will be canceled.) - this.pendingBubble = this.$timeout(displayBubble, this.delay); - - this.element.on('mouseleave', this.hideBubbleCallback); - }; - - /** - * Detach any event handlers associated with this gesture. - * @method - */ - InfoGesture.prototype.destroy = function () { - // Dismiss any active bubble... - this.hideBubble(); - // ...and detach listeners - this.element.off('mouseenter', this.showBubbleCallback); - }; - - return InfoGesture; - - } - -); - diff --git a/platform/commonUI/inspect/src/services/InfoService.js b/platform/commonUI/inspect/src/services/InfoService.js deleted file mode 100644 index 36b789230e..0000000000 --- a/platform/commonUI/inspect/src/services/InfoService.js +++ /dev/null @@ -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. - *****************************************************************************/ - -define( - ['../InfoConstants'], - function (InfoConstants) { - - var BUBBLE_TEMPLATE = InfoConstants.BUBBLE_TEMPLATE, - MOBILE_POSITION = InfoConstants.BUBBLE_MOBILE_POSITION, - OPTIONS = InfoConstants.BUBBLE_OPTIONS; - - /** - * Displays informative content ("info bubbles") for the user. - * @memberof platform/commonUI/inspect - * @constructor - */ - function InfoService($compile, $rootScope, popupService, agentService) { - this.$compile = $compile; - this.$rootScope = $rootScope; - this.popupService = popupService; - this.agentService = agentService; - } - - /** - * Display an info bubble at the specified location. - * @param {string} templateKey template to place in bubble - * @param {string} title title for the bubble - * @param {*} content content to pass to the template, via - * `ng-model` - * @param {number[]} x,y position of the info bubble, in - * pixel coordinates. - * @returns {Function} a function that may be invoked to - * dismiss the info bubble - */ - InfoService.prototype.display = function (templateKey, title, content, position) { - var $compile = this.$compile, - $rootScope = this.$rootScope, - scope = $rootScope.$new(), - span = $compile('')(scope), - bubbleSpaceLR = InfoConstants.BUBBLE_MARGIN_LR - + InfoConstants.BUBBLE_MAX_WIDTH, - options, - popup, - bubble; - - options = Object.create(OPTIONS); - options.marginX = -bubbleSpaceLR; - - // prevent bubble from appearing right under pointer, - // which causes hover callback to be called multiple times - options.offsetX = 1; - - // On a phone, bubble takes up more screen real estate, - // so position it differently (toward the bottom) - if (this.agentService.isPhone()) { - position = MOBILE_POSITION; - options = {}; - } - - popup = this.popupService.display(span, position, options); - - // Pass model & container parameters into the scope - scope.bubbleModel = content; - scope.bubbleTemplate = templateKey; - scope.bubbleTitle = title; - // Style the bubble according to how it was positioned - scope.bubbleLayout = [ - popup.goesUp() ? 'arw-btm' : 'arw-top', - popup.goesLeft() ? 'arw-right' : 'arw-left' - ].join(' '); - - // Create the info bubble, now that we know how to - // point the arrow... - bubble = $compile(BUBBLE_TEMPLATE)(scope); - span.append(bubble); - - // Return a function to dismiss the info bubble - return function dismiss() { - popup.dismiss(); - scope.$destroy(); - }; - }; - - return InfoService; - } -); - diff --git a/platform/commonUI/inspect/test/gestures/InfoButtonGestureSpec.js b/platform/commonUI/inspect/test/gestures/InfoButtonGestureSpec.js deleted file mode 100644 index 2f81e4b327..0000000000 --- a/platform/commonUI/inspect/test/gestures/InfoButtonGestureSpec.js +++ /dev/null @@ -1,148 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['../../src/gestures/InfoButtonGesture'], - function (InfoButtonGesture) { - - describe("The info button gesture", function () { - var mockDocument, - mockBody, - mockAgentService, - mockInfoService, - mockElement, - mockDomainObject, - mockEvent, - mockScope, - mockOff, - testMetadata, - mockHide, - gesture, - fireGesture, - fireDismissGesture; - - beforeEach(function () { - mockDocument = jasmine.createSpyObj('$document', ['find']); - mockBody = jasmine.createSpyObj('body', ['on', 'off', 'scope', 'css', 'unbind']); - mockDocument.find.and.returnValue(mockBody); - mockAgentService = jasmine.createSpyObj('agentService', ['isMobile', 'isPhone']); - mockInfoService = jasmine.createSpyObj( - 'infoService', - ['display'] - ); - mockElement = jasmine.createSpyObj( - 'element', - ['on', 'off', 'scope', 'css'] - ); - mockDomainObject = jasmine.createSpyObj( - 'domainObject', - ['getId', 'getCapability', 'useCapability', 'getModel'] - ); - - mockEvent = jasmine.createSpyObj("event", ["preventDefault", "stopPropagation"]); - mockEvent.pageX = 0; - mockEvent.pageY = 0; - mockScope = jasmine.createSpyObj('$scope', ['$on']); - mockOff = jasmine.createSpy('$off'); - testMetadata = [{ - name: "Test name", - value: "Test value" - }]; - mockHide = jasmine.createSpy('hide'); - - mockDomainObject.getModel.and.returnValue({ name: "Test Object" }); - mockDomainObject.useCapability.and.callFake(function (c) { - return (c === 'metadata') ? testMetadata : undefined; - }); - mockElement.scope.and.returnValue(mockScope); - mockScope.$on.and.returnValue(mockOff); - mockInfoService.display.and.returnValue(mockHide); - mockAgentService.isMobile.and.returnValue(true); - gesture = new InfoButtonGesture( - mockDocument, - mockAgentService, - mockInfoService, - mockElement, - mockDomainObject - ); - fireGesture = mockElement.on.calls.mostRecent().args[1]; - }); - - it("expect click on the representation", function () { - // Fires a click call on element and then - // expects the click to have happened - fireGesture(mockEvent); - expect(mockElement.on).toHaveBeenCalledWith( - "click", - jasmine.any(Function) - ); - }); - - it("expect click then dismiss on the representation", function () { - // Fire the click and then expect the click - fireGesture(mockEvent); - expect(mockElement.on).toHaveBeenCalledWith( - "click", - jasmine.any(Function) - ); - - // Get the touch start on the body - // and fire the dismiss gesture - fireDismissGesture = mockBody.on.calls.mostRecent().args[1]; - fireDismissGesture(mockEvent); - // Expect Body to have been touched, event.preventDefault() - // to be called, then the mockBody listener to be detached - // lastly unbind the touchstart used to dismiss so other - // events can be called - expect(mockBody.on).toHaveBeenCalledWith( - "touchstart", - jasmine.any(Function) - ); - expect(mockEvent.preventDefault).toHaveBeenCalled(); - expect(mockBody.off).toHaveBeenCalledWith( - "touchstart", - jasmine.any(Function) - ); - expect(mockBody.unbind).toHaveBeenCalledWith( - 'touchstart' - ); - }); - - it("detaches a callback for info bubble events when destroyed", function () { - expect(mockElement.off).not.toHaveBeenCalled(); - - gesture.destroy(); - - expect(mockElement.off).toHaveBeenCalledWith( - "click", - jasmine.any(Function) - ); - }); - - // https://github.com/nasa/openmct/issues/948 - it("does not try to access scope", function () { - expect(mockElement.scope).not.toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/commonUI/inspect/test/gestures/InfoGestureSpec.js b/platform/commonUI/inspect/test/gestures/InfoGestureSpec.js deleted file mode 100644 index 6f66d82473..0000000000 --- a/platform/commonUI/inspect/test/gestures/InfoGestureSpec.js +++ /dev/null @@ -1,188 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['../../src/gestures/InfoGesture'], - function (InfoGesture) { - - describe("The info gesture", function () { - var mockTimeout, - mockAgentService, - mockInfoService, - testDelay = 12321, - mockElement, - mockDomainObject, - mockScope, - mockOff, - testMetadata, - mockPromise, - mockHide, - gesture; - - function fireEvent(evt, value) { - mockElement.on.calls.all().forEach(function (call) { - if (call.args[0] === evt) { - call.args[1](value); - } - }); - } - - beforeEach(function () { - mockTimeout = jasmine.createSpy('$timeout'); - mockTimeout.cancel = jasmine.createSpy('cancel'); - mockAgentService = jasmine.createSpyObj('agentService', ['isMobile']); - mockInfoService = jasmine.createSpyObj( - 'infoService', - ['display'] - ); - mockElement = jasmine.createSpyObj( - 'element', - ['on', 'off', 'scope', 'css'] - ); - mockDomainObject = jasmine.createSpyObj( - 'domainObject', - ['getId', 'getCapability', 'useCapability', 'getModel'] - ); - mockScope = jasmine.createSpyObj('$scope', ['$on']); - mockOff = jasmine.createSpy('$off'); - testMetadata = [{ - name: "Test name", - value: "Test value" - }]; - mockPromise = jasmine.createSpyObj('promise', ['then']); - mockHide = jasmine.createSpy('hide'); - - mockDomainObject.getModel.and.returnValue({ name: "Test Object" }); - mockDomainObject.useCapability.and.callFake(function (c) { - return (c === 'metadata') ? testMetadata : undefined; - }); - mockElement.scope.and.returnValue(mockScope); - mockScope.$on.and.returnValue(mockOff); - mockTimeout.and.returnValue(mockPromise); - mockInfoService.display.and.returnValue(mockHide); - - gesture = new InfoGesture( - mockTimeout, - mockAgentService, - mockInfoService, - testDelay, - mockElement, - mockDomainObject - ); - }); - - it("listens for mouseenter on the representation", function () { - expect(mockElement.on) - .toHaveBeenCalledWith('mouseenter', jasmine.any(Function)); - }); - - it("displays an info bubble on a delay after mouseenter", function () { - fireEvent("mouseenter", { - clientX: 1977, - clientY: 42 - }); - expect(mockTimeout) - .toHaveBeenCalledWith(jasmine.any(Function), testDelay); - mockTimeout.calls.mostRecent().args[0](); - expect(mockInfoService.display).toHaveBeenCalledWith( - jasmine.any(String), - "Test Object", - testMetadata, - [1977, 42] - ); - }); - - it("does not display info bubble if mouse leaves too soon", function () { - fireEvent("mouseenter", { - clientX: 1977, - clientY: 42 - }); - fireEvent("mouseleave", { - clientX: 1977, - clientY: 42 - }); - expect(mockTimeout.cancel).toHaveBeenCalledWith(mockPromise); - expect(mockInfoService.display).not.toHaveBeenCalled(); - }); - - it("hides a shown bubble when mouse leaves", function () { - fireEvent("mouseenter", { - clientX: 1977, - clientY: 42 - }); - mockTimeout.calls.mostRecent().args[0](); - expect(mockHide).not.toHaveBeenCalled(); // verify precondition - fireEvent("mouseleave", {}); - expect(mockHide).toHaveBeenCalled(); - }); - - it("tracks mouse position", function () { - fireEvent("mouseenter", { - clientX: 1977, - clientY: 42 - }); - fireEvent("mousemove", { - clientX: 1999, - clientY: 11 - }); - fireEvent("mousemove", { - clientX: 1984, - clientY: 11 - }); - mockTimeout.calls.mostRecent().args[0](); - // Should have displayed at the latest observed mouse position - expect(mockInfoService.display).toHaveBeenCalledWith( - jasmine.any(String), - "Test Object", - testMetadata, - [1984, 11] - ); - }); - - it("hides shown bubbles when destroyed", function () { - fireEvent("mouseenter", { - clientX: 1977, - clientY: 42 - }); - mockTimeout.calls.mostRecent().args[0](); - expect(mockHide).not.toHaveBeenCalled(); // verify precondition - gesture.destroy(); - expect(mockHide).toHaveBeenCalled(); - }); - - it("detaches listeners when destroyed", function () { - fireEvent("mouseenter", { - clientX: 1977, - clientY: 42 - }); - gesture.destroy(); - mockElement.on.calls.all().forEach(function (call) { - expect(mockElement.off).toHaveBeenCalledWith( - call.args[0], - call.args[1] - ); - }); - }); - - }); - } -); diff --git a/platform/commonUI/inspect/test/services/InfoServiceSpec.js b/platform/commonUI/inspect/test/services/InfoServiceSpec.js deleted file mode 100644 index 9641bf94d3..0000000000 --- a/platform/commonUI/inspect/test/services/InfoServiceSpec.js +++ /dev/null @@ -1,142 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['../../src/services/InfoService'], - function (InfoService) { - - describe("The info service", function () { - var mockCompile, - mockRootScope, - mockPopupService, - mockAgentService, - mockScope, - mockElements, - mockPopup, - service; - - beforeEach(function () { - mockCompile = jasmine.createSpy('$compile'); - mockRootScope = jasmine.createSpyObj('$rootScope', ['$new']); - mockAgentService = jasmine.createSpyObj('agentService', ['isMobile', 'isPhone']); - mockPopupService = jasmine.createSpyObj( - 'popupService', - ['display'] - ); - mockPopup = jasmine.createSpyObj('popup', [ - 'dismiss', - 'goesLeft', - 'goesRight', - 'goesUp', - 'goesDown' - ]); - - mockScope = jasmine.createSpyObj("scope", ["$destroy"]); - mockElements = []; - - mockPopupService.display.and.returnValue(mockPopup); - mockCompile.and.callFake(function () { - var mockCompiledTemplate = jasmine.createSpy('template'), - mockElement = jasmine.createSpyObj('element', [ - 'css', - 'remove', - 'append' - ]); - mockCompiledTemplate.and.returnValue(mockElement); - mockElements.push(mockElement); - - return mockCompiledTemplate; - }); - mockRootScope.$new.and.returnValue(mockScope); - - service = new InfoService( - mockCompile, - mockRootScope, - mockPopupService, - mockAgentService - ); - }); - - it("creates elements and displays them as popups", function () { - service.display('', '', {}, [123, 456]); - expect(mockPopupService.display).toHaveBeenCalledWith( - mockElements[0], - [123, 456], - jasmine.any(Object) - ); - }); - - it("provides a function to remove displayed info bubbles", function () { - var fn = service.display('', '', {}, [0, 0]); - expect(mockPopup.dismiss).not.toHaveBeenCalled(); - fn(); - expect(mockPopup.dismiss).toHaveBeenCalled(); - }); - - it("when on phone device, positions at bottom", function () { - mockAgentService.isPhone.and.returnValue(true); - service = new InfoService( - mockCompile, - mockRootScope, - mockPopupService, - mockAgentService - ); - service.display('', '', {}, [123, 456]); - expect(mockPopupService.display).toHaveBeenCalledWith( - mockElements[0], - [0, -25], - jasmine.any(Object) - ); - }); - - [false, true].forEach(function (goesLeft) { - [false, true].forEach(function (goesUp) { - var vertical = goesUp ? "up" : "down", - horizontal = goesLeft ? "left" : "right", - location = [vertical, horizontal].join('-'); - describe("when bubble goes " + location, function () { - var expectedLocation = [ - goesUp ? "bottom" : "top", - goesLeft ? "right" : "left" - ].join('-'); - - beforeEach(function () { - mockPopup.goesUp.and.returnValue(goesUp); - mockPopup.goesDown.and.returnValue(!goesUp); - mockPopup.goesLeft.and.returnValue(goesLeft); - mockPopup.goesRight.and.returnValue(!goesLeft); - service.display('', '', {}, [10, 10]); - }); - - it("positions the arrow in the " + expectedLocation, function () { - expect(mockScope.bubbleLayout).toEqual([ - goesUp ? "arw-btm" : "arw-top", - goesLeft ? "arw-right" : "arw-left" - ].join(' ')); - }); - }); - }); - }); - - }); - } -); diff --git a/platform/commonUI/mobile/bundle.js b/platform/commonUI/mobile/bundle.js deleted file mode 100644 index 0cacd0f21f..0000000000 --- a/platform/commonUI/mobile/bundle.js +++ /dev/null @@ -1,44 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/AgentService" -], function ( - AgentService -) { - return { - name: "platform/commonUI/mobile", - definition: { - "extensions": { - "services": [ - { - "key": "agentService", - "implementation": AgentService, - "depends": [ - "$window" - ] - } - ] - } - } - }; -}); diff --git a/platform/commonUI/mobile/src/AgentServiceSpec.js b/platform/commonUI/mobile/src/AgentServiceSpec.js deleted file mode 100644 index d024c59953..0000000000 --- a/platform/commonUI/mobile/src/AgentServiceSpec.js +++ /dev/null @@ -1,96 +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 AgentService from "./AgentService"; - -const TEST_USER_AGENTS = { - DESKTOP: - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36", - IPAD: - "Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53", - IPHONE: - "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53" -}; - -describe("The AgentService", function () { - let testWindow; - let agentService; - - beforeEach(function () { - testWindow = { - innerWidth: 640, - innerHeight: 480, - navigator: { - userAgent: TEST_USER_AGENTS.DESKTOP - } - }; - }); - - it("recognizes desktop devices as non-mobile", function () { - testWindow.navigator.userAgent = TEST_USER_AGENTS.DESKTOP; - agentService = new AgentService(testWindow); - expect(agentService.isMobile()).toBeFalsy(); - expect(agentService.isPhone()).toBeFalsy(); - expect(agentService.isTablet()).toBeFalsy(); - }); - - it("detects iPhones", function () { - testWindow.navigator.userAgent = TEST_USER_AGENTS.IPHONE; - agentService = new AgentService(testWindow); - expect(agentService.isMobile()).toBeTruthy(); - expect(agentService.isPhone()).toBeTruthy(); - expect(agentService.isTablet()).toBeFalsy(); - }); - - it("detects iPads", function () { - testWindow.navigator.userAgent = TEST_USER_AGENTS.IPAD; - agentService = new AgentService(testWindow); - expect(agentService.isMobile()).toBeTruthy(); - expect(agentService.isPhone()).toBeFalsy(); - expect(agentService.isTablet()).toBeTruthy(); - }); - - it("detects display orientation", function () { - agentService = new AgentService(testWindow); - testWindow.innerWidth = 1024; - testWindow.innerHeight = 400; - expect(agentService.isPortrait()).toBeFalsy(); - expect(agentService.isLandscape()).toBeTruthy(); - testWindow.innerWidth = 400; - testWindow.innerHeight = 1024; - expect(agentService.isPortrait()).toBeTruthy(); - expect(agentService.isLandscape()).toBeFalsy(); - }); - - it("detects touch support", function () { - testWindow.ontouchstart = null; - expect(new AgentService(testWindow).isTouch()).toBe(true); - delete testWindow.ontouchstart; - expect(new AgentService(testWindow).isTouch()).toBe(false); - }); - - it("allows for checking browser type", function () { - testWindow.navigator.userAgent = "Chromezilla Safarifox"; - agentService = new AgentService(testWindow); - expect(agentService.isBrowser("Chrome")).toBe(true); - expect(agentService.isBrowser("Firefox")).toBe(false); - }); -}); diff --git a/platform/commonUI/notification/bundle.js b/platform/commonUI/notification/bundle.js deleted file mode 100644 index 0bb7477fa2..0000000000 --- a/platform/commonUI/notification/bundle.js +++ /dev/null @@ -1,47 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/NotificationService" -], function ( - NotificationService -) { - - return { - name: "platform/commonUI/notification", - definition: { - "extensions": { - "services": [ - { - "key": "notificationService", - "implementation": function (openmct) { - return new NotificationService.default(openmct); - }, - "depends": [ - "openmct" - ] - } - ] - } - } - }; -}); diff --git a/platform/commonUI/notification/src/NotificationService.js b/platform/commonUI/notification/src/NotificationService.js deleted file mode 100644 index 181c097c14..0000000000 --- a/platform/commonUI/notification/src/NotificationService.js +++ /dev/null @@ -1,64 +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 NotificationService { - constructor(openmct) { - this.openmct = openmct; - } - info(message) { - if (typeof message === 'string') { - return this.openmct.notifications.info(message); - } else { - if (Object.prototype.hasOwnProperty.call(message, 'progress')) { - return this.openmct.notifications.progress(message.title, message.progress, message.progressText); - } else { - return this.openmct.notifications.info(message.title); - } - } - } - alert(message) { - if (typeof message === 'string') { - return this.openmct.notifications.alert(message); - } else { - return this.openmct.notifications.alert(message.title); - } - } - error(message) { - if (typeof message === 'string') { - return this.openmct.notifications.error(message); - } else { - return this.openmct.notifications.error(message.title); - } - } - notify(options) { - switch (options.severity) { - case 'info': - return this.info(options); - case 'alert': - return this.alert(options); - case 'error': - return this.error(options); - } - } - getAllNotifications() { - return this.openmct.notifications.notifications; - } -} diff --git a/platform/commonUI/regions/bundle.js b/platform/commonUI/regions/bundle.js deleted file mode 100644 index b6ecc8ec6f..0000000000 --- a/platform/commonUI/regions/bundle.js +++ /dev/null @@ -1,55 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - './src/InspectorController', - './src/EditableRegionPolicy' -], function ( - InspectorController, - EditableRegionPolicy -) { - - return { - name: "platform/commonUI/regions", - definition: { - "extensions": { - "controllers": [ - { - "key": "InspectorController", - "implementation": InspectorController, - "depends": [ - "$scope", - "openmct", - "$document" - ] - } - ], - "policies": [ - { - "category": "region", - "implementation": EditableRegionPolicy - } - ] - } - } - }; -}); diff --git a/platform/commonUI/regions/src/EditableRegionPolicy.js b/platform/commonUI/regions/src/EditableRegionPolicy.js deleted file mode 100644 index 03215bcade..0000000000 --- a/platform/commonUI/regions/src/EditableRegionPolicy.js +++ /dev/null @@ -1,56 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * A policy for determining whether a region part should be visible or - * not, based on its editability and the current state of the domain - * object . - * @constructor - * @implements {Policy} - * @memberof platform/commonUI/regions - */ - function EditableRegionPolicy() { - } - - EditableRegionPolicy.prototype.allow = function (regionPart, domainObject) { - if (!regionPart.modes) { - return true; - } - - if (domainObject.hasCapability('editor') && domainObject.getCapability('editor').inEditContext()) { - //If the domain object is in edit mode, only include a part - // if it is marked editable - return regionPart.modes.indexOf('edit') !== -1; - } else { - //If the domain object is not in edit mode, return any parts - // that are not explicitly marked editable - return regionPart.modes.indexOf('browse') !== -1; - } - }; - - return EditableRegionPolicy; - } -); diff --git a/platform/commonUI/regions/src/InspectorController.js b/platform/commonUI/regions/src/InspectorController.js deleted file mode 100644 index e82556b682..0000000000 --- a/platform/commonUI/regions/src/InspectorController.js +++ /dev/null @@ -1,93 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * The InspectorController listens for the selection changes and adds the selection - * object to the scope. - * - * @constructor - */ - function InspectorController($scope, openmct, $document) { - var self = this; - self.$scope = $scope; - - /** - * Callback handler for the selection change event. - * Adds the selection object to the scope. If the selected item has an inspector view, - * it puts the key in the scope. If provider view exists, it shows the view. - */ - function setSelection(selection) { - if (selection[0]) { - var view = openmct.inspectorViews.get(selection); - var container = $document[0].querySelectorAll('.inspector-provider-view')[0]; - container.innerHTML = ""; - - if (view) { - self.providerView = true; - view.show(container); - } else { - self.providerView = false; - var selectedItem = selection[0].context.oldItem; - - if (selectedItem) { - $scope.inspectorKey = selectedItem.getCapability("type").typeDef.inspector; - } - } - } - - self.$scope.selection = selection; - } - - openmct.selection.on("change", setSelection); - - setSelection(openmct.selection.get()); - - $scope.$on("$destroy", function () { - openmct.selection.off("change", setSelection); - }); - } - - /** - * Gets the selected item. - * - * @returns a domain object - */ - InspectorController.prototype.selectedItem = function () { - return this.$scope.selection[0] && this.$scope.selection[0].context.oldItem; - }; - - /** - * Checks if a provider view exists. - * - * @returns 'true' if provider view exists, 'false' otherwise - */ - InspectorController.prototype.hasProviderView = function () { - return this.providerView; - }; - - return InspectorController; - } -); diff --git a/platform/commonUI/regions/src/Region.js b/platform/commonUI/regions/src/Region.js deleted file mode 100644 index 90c5be3b06..0000000000 --- a/platform/commonUI/regions/src/Region.js +++ /dev/null @@ -1,99 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * @typeDef {object} PartContents - * @property {string} key If the part is defined as a - * representation, the key corresponding to the representation. - * @memberOf platform/commonUI/regions - */ - - /** - * @typeDef {object} RegionConfiguration - * @property {string} name A unique name for this region part - * @property {PartContents} [content] the details of the region being - * defined - * @property {Array} [modes] the modes that this region - * should be included in. Options are 'edit' and 'browse'. By - * default, will be included in both. Inclusion of regions is - * determined by policies of category 'region'. By default, the - * {EditableRegionPolicy} will be applied. - * @memberOf platform/commonUI/regions - */ - - /** - * Defines the interface for a screen region. A screen region is a - * section of the browse an edit screens for an object. Regions are - * declared in object type definitions. - * @memberOf platform/commonUI/regions - * @abstract - * @constructor - */ - function Region(configuration) { - configuration = configuration || {}; - this.name = configuration.name; - this.content = configuration.content; - this.modes = configuration.modes; - - this.regions = []; - } - - /** - * Adds a sub-region to this region. - * @param {Region} region the part to add - * @param {number} [index] the position to insert the region. By default - * will add to the end - */ - Region.prototype.addRegion = function (region, index) { - if (index) { - this.regions.splice(index, 0, region); - } else { - this.regions.push(region); - } - }; - - /** - * Removes a sub-region from this region. - * @param {Region | number | strnig} region The region to - * remove. If a number, will remove the region at that index. If a - * string, will remove the region with the matching name. If an - * object, will attempt to remove that object from the Region - */ - Region.prototype.removeRegion = function (region) { - if (typeof region === 'number') { - this.regions.splice(region, 1); - } else if (typeof region === 'string') { - this.regions = this.regions.filter(function (thisRegion) { - return thisRegion.name !== region; - }); - } else { - this.regions.splice(this.regions.indexOf(region), 1); - } - }; - - return Region; - } -); diff --git a/platform/commonUI/regions/test/EditableRegionPolicySpec.js b/platform/commonUI/regions/test/EditableRegionPolicySpec.js deleted file mode 100644 index 3aac194653..0000000000 --- a/platform/commonUI/regions/test/EditableRegionPolicySpec.js +++ /dev/null @@ -1,74 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['../src/EditableRegionPolicy'], - function (EditableRegionPolicy) { - - describe("The editable region policy ", function () { - - var editableRegionPolicy, - mockDomainObject, - mockEditorCapability, - mockBrowseRegionPart = { - modes: 'browse' - }, - mockEditRegionPart = { - modes: 'edit' - }, - mockAllModesRegionPart = {}; - - beforeEach(function () { - editableRegionPolicy = new EditableRegionPolicy(); - - mockEditorCapability = jasmine.createSpyObj("editorCapability", [ - "inEditContext" - ]); - mockDomainObject = jasmine.createSpyObj("domainObject", [ - "hasCapability", "getCapability" - ]); - mockDomainObject.hasCapability.and.returnValue(true); - mockDomainObject.getCapability.and.returnValue(mockEditorCapability); - }); - - it("includes only browse region parts for object not in edit mode", function () { - mockEditorCapability.inEditContext.and.returnValue(false); - expect(editableRegionPolicy.allow(mockBrowseRegionPart, mockDomainObject)).toBe(true); - expect(editableRegionPolicy.allow(mockEditRegionPart, mockDomainObject)).toBe(false); - }); - - it("includes only edit region parts for object in edit mode", function () { - mockEditorCapability.inEditContext.and.returnValue(true); - expect(editableRegionPolicy.allow(mockBrowseRegionPart, mockDomainObject)).toBe(false); - expect(editableRegionPolicy.allow(mockEditRegionPart, mockDomainObject)).toBe(true); - }); - - it("includes region parts with no mode specification", function () { - mockEditorCapability.inEditContext.and.returnValue(false); - expect(editableRegionPolicy.allow(mockAllModesRegionPart, mockDomainObject)).toBe(true); - mockEditorCapability.inEditContext.and.returnValue(true); - expect(editableRegionPolicy.allow(mockAllModesRegionPart, mockDomainObject)).toBe(true); - }); - - }); - } -); diff --git a/platform/commonUI/regions/test/InspectorControllerSpec.js b/platform/commonUI/regions/test/InspectorControllerSpec.js deleted file mode 100644 index 7a69870972..0000000000 --- a/platform/commonUI/regions/test/InspectorControllerSpec.js +++ /dev/null @@ -1,120 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['../src/InspectorController'], - function (InspectorController) { - - describe("The inspector controller ", function () { - var mockScope, - mockDomainObject, - mockOpenMCT, - mockSelection, - mockInspectorViews, - mockTypeDef, - controller, - container, - $document = [], - selectable = []; - - beforeEach(function () { - mockTypeDef = { - typeDef: { - inspector: "some-key" - } - }; - - mockDomainObject = jasmine.createSpyObj('domainObject', [ - 'getCapability' - ]); - mockDomainObject.getCapability.and.returnValue(mockTypeDef); - - mockScope = jasmine.createSpyObj('$scope', - ['$on', 'selection'] - ); - - selectable[0] = { - context: { - oldItem: mockDomainObject - } - }; - - mockSelection = jasmine.createSpyObj("selection", [ - 'on', - 'off', - 'get' - ]); - mockSelection.get.and.returnValue(selectable); - - mockInspectorViews = jasmine.createSpyObj('inspectorViews', ['get']); - mockOpenMCT = { - selection: mockSelection, - inspectorViews: mockInspectorViews - }; - - container = jasmine.createSpy('container', ['innerHTML']); - $document[0] = jasmine.createSpyObj("$document", ['querySelectorAll']); - $document[0].querySelectorAll.and.returnValue([container]); - - controller = new InspectorController(mockScope, mockOpenMCT, $document); - }); - - it("listens for selection change event", function () { - expect(mockOpenMCT.selection.on).toHaveBeenCalledWith( - 'change', - jasmine.any(Function) - ); - - expect(controller.selectedItem()).toEqual(mockDomainObject); - - var mockItem = jasmine.createSpyObj('domainObject', [ - 'getCapability' - ]); - mockItem.getCapability.and.returnValue(mockTypeDef); - selectable[0].context.oldItem = mockItem; - - mockOpenMCT.selection.on.calls.mostRecent().args[1](selectable); - - expect(controller.selectedItem()).toEqual(mockItem); - }); - - it("cleans up on scope destroy", function () { - expect(mockScope.$on).toHaveBeenCalledWith( - '$destroy', - jasmine.any(Function) - ); - - mockScope.$on.calls.all()[0].args[1](); - - expect(mockOpenMCT.selection.off).toHaveBeenCalledWith( - 'change', - jasmine.any(Function) - ); - }); - - it("adds selection object to scope", function () { - expect(mockScope.selection).toEqual(selectable); - expect(controller.selectedItem()).toEqual(mockDomainObject); - }); - }); - } -); diff --git a/platform/commonUI/regions/test/RegionSpec.js b/platform/commonUI/regions/test/RegionSpec.js deleted file mode 100644 index 5b3151e8fb..0000000000 --- a/platform/commonUI/regions/test/RegionSpec.js +++ /dev/null @@ -1,103 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['../src/Region'], - function (Region) { - - describe("The region class ", function () { - - var region, - part2 = new Region({'name': 'part2'}); - - beforeEach(function () { - region = new Region(); - region.regions = [ - new Region({name: 'part1'}), - new Region({name: 'part3'}), - new Region({name: 'part4'}) - ]; - }); - - it("adding a region at a specified index adds it in that" - + " position", function () { - - region.addRegion(part2, 1); - - expect(region.regions.length).toBe(4); - expect(region.regions[1]).toBe(part2); - }); - - it("adding a region without an index adds it at the end", function () { - var partN = new Region({'name': 'partN'}); - - region.addRegion(partN); - - expect(region.regions.length).toBe(4); - expect(region.regions[region.regions.length - 1]).toBe(partN); - }); - - describe("removing a region", function () { - var partName = "part2"; - - beforeEach(function () { - region.regions = [ - new Region({name: 'part1'}), - part2, - new Region({name: 'part3'}), - new Region({name: 'part4'}) - ]; - }); - - it("with a string matches on region name", function () { - expect(region.regions.length).toBe(4); - expect(region.regions.indexOf(part2)).toBe(1); - - region.removeRegion(partName); - - expect(region.regions.length).toBe(3); - expect(region.regions.indexOf(part2)).toBe(-1); - }); - - it("with a number removes by index", function () { - expect(region.regions.length).toBe(4); - expect(region.regions.indexOf(part2)).toBe(1); - - region.removeRegion(1); - - expect(region.regions.length).toBe(3); - expect(region.regions.indexOf(part2)).toBe(-1); - }); - - it("with object matches that object", function () { - expect(region.regions.length).toBe(4); - expect(region.regions.indexOf(part2)).toBe(1); - - region.removeRegion(part2); - - expect(region.regions.length).toBe(3); - expect(region.regions.indexOf(part2)).toBe(-1); - }); - }); - }); - } -); diff --git a/platform/containment/README.md b/platform/containment/README.md deleted file mode 100644 index b72c3674fa..0000000000 --- a/platform/containment/README.md +++ /dev/null @@ -1,2 +0,0 @@ -Implements support for rules which determine which objects are allowed -to contain other objects, typically by type. diff --git a/platform/containment/bundle.js b/platform/containment/bundle.js deleted file mode 100644 index 6dfc7be34a..0000000000 --- a/platform/containment/bundle.js +++ /dev/null @@ -1,76 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/CompositionPolicy", - "./src/CompositionMutabilityPolicy", - "./src/CompositionModelPolicy", - "./src/ComposeActionPolicy", - "./src/PersistableCompositionPolicy" -], function ( - CompositionPolicy, - CompositionMutabilityPolicy, - CompositionModelPolicy, - ComposeActionPolicy, - PersistableCompositionPolicy -) { - - return { - name: "platform/containment", - definition: { - "extensions": { - "policies": [ - { - "category": "composition", - "implementation": CompositionPolicy, - "message": "Objects of this type cannot contain objects of that type." - }, - { - "category": "composition", - "implementation": CompositionMutabilityPolicy, - "message": "Objects of this type cannot be modified." - }, - { - "category": "composition", - "implementation": CompositionModelPolicy, - "message": "Objects of this type cannot contain other objects." - }, - { - "category": "action", - "implementation": ComposeActionPolicy, - "depends": [ - "$injector", - "openmct" - ], - "message": "Objects of this type cannot contain objects of that type." - }, - { - "category": "composition", - "implementation": PersistableCompositionPolicy, - "depends": ["openmct"], - "message": "Change cannot be made to composition of non-persistable object" - } - ] - } - } - }; -}); diff --git a/platform/containment/src/ComposeActionPolicy.js b/platform/containment/src/ComposeActionPolicy.js deleted file mode 100644 index b47b339824..0000000000 --- a/platform/containment/src/ComposeActionPolicy.js +++ /dev/null @@ -1,78 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * Restrict `compose` actions to cases where composition - * is explicitly allowed. - * - * Note that this is a policy that needs the `policyService`, - * since it's delegated to a different policy category. - * To avoid a circular dependency, the service is obtained via - * Angular's `$injector`. - * @constructor - * @memberof platform/containment - * @implements {Policy.} - */ - function ComposeActionPolicy($injector, openmct) { - this.getPolicyService = function () { - return $injector.get('policyService'); - }; - - this.openmct = openmct; - } - - ComposeActionPolicy.prototype.allowComposition = function (containerObject, selectedObject) { - - // Get a reference to the policy service if needed... - this.policyService = this.policyService || this.getPolicyService(); - - // ...and delegate to the composition policy - return containerObject.getId() !== selectedObject.getId() - && this.openmct.composition.checkPolicy(containerObject.useCapability('adapter'), - selectedObject.useCapability('adapter')); - }; - - /** - * Check whether or not a compose action should be allowed - * in this context. - * @returns {boolean} true if it may be allowed - * @memberof platform/containment.ComposeActionPolicy# - */ - ComposeActionPolicy.prototype.allow = function (candidate, context) { - if (candidate.getMetadata().key === 'compose') { - return this.allowComposition( - (context || {}).domainObject, - (context || {}).selectedObject - ); - } - - return true; - }; - - return ComposeActionPolicy; - - } -); diff --git a/platform/containment/src/CompositionModelPolicy.js b/platform/containment/src/CompositionModelPolicy.js deleted file mode 100644 index 11f27449e9..0000000000 --- a/platform/containment/src/CompositionModelPolicy.js +++ /dev/null @@ -1,26 +0,0 @@ - -define( - [], - function () { - - /** - * Policy allowing composition only for domain object types which - * have a composition property. - * @constructor - * @memberof platform/containment - * @implements {Policy.} - */ - function CompositionModelPolicy() { - } - - CompositionModelPolicy.prototype.allow = function (candidate) { - var candidateType = candidate.getCapability('type'); - - return Array.isArray( - (candidateType.getInitialModel() || {}).composition - ); - }; - - return CompositionModelPolicy; - } -); diff --git a/platform/containment/src/CompositionMutabilityPolicy.js b/platform/containment/src/CompositionMutabilityPolicy.js deleted file mode 100644 index cb0eec7207..0000000000 --- a/platform/containment/src/CompositionMutabilityPolicy.js +++ /dev/null @@ -1,45 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * Disallow composition changes to objects which are not mutable. - * @memberof platform/containment - * @constructor - * @implements {Policy.} - */ - function CompositionMutabilityPolicy() { - } - - CompositionMutabilityPolicy.prototype.allow = function (candidate) { - // Equate creatability with mutability; that is, users - // can only modify objects of types they can create, and - // vice versa. - return candidate.getCapability('type').hasFeature('creation'); - }; - - return CompositionMutabilityPolicy; - } -); diff --git a/platform/containment/src/CompositionPolicy.js b/platform/containment/src/CompositionPolicy.js deleted file mode 100644 index 53fa48933d..0000000000 --- a/platform/containment/src/CompositionPolicy.js +++ /dev/null @@ -1,71 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * This bundle implements "containment" rules, which determine which objects - * can be contained within which other objects. - * @namespace platform/containment - */ -define( - [], - function () { - - /** - * Determines whether a given object can contain a candidate child object. - * @constructor - * @memberof platform/containment - * @implements {Policy.} - */ - function CompositionPolicy() { - } - - CompositionPolicy.prototype.allow = function (parent, child) { - var parentDef = parent.getCapability('type').getDefinition(); - - // A parent without containment rules can contain anything. - if (!parentDef.contains) { - return true; - } - - // If any containment rule matches context type, the candidate - // can contain this type. - return parentDef.contains.some(function (c) { - // Simple containment rules are supported typeKeys. - if (typeof c === 'string') { - return c === child.getCapability('type').getKey(); - } - - // More complicated rules require context to have all specified - // capabilities. - if (!Array.isArray(c.has)) { - c.has = [c.has]; - } - - return c.has.every(function (capability) { - return child.hasCapability(capability); - }); - }); - }; - - return CompositionPolicy; - } -); diff --git a/platform/containment/src/PersistableCompositionPolicy.js b/platform/containment/src/PersistableCompositionPolicy.js deleted file mode 100644 index 389fcb8f9f..0000000000 --- a/platform/containment/src/PersistableCompositionPolicy.js +++ /dev/null @@ -1,61 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-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. - *****************************************************************************/ - -/** - * This bundle implements "containment" rules, which determine which objects - * can be contained within which other objects. - * @namespace platform/containment - */ -define( - ['objectUtils'], - function (objectUtils) { - - function PersistableCompositionPolicy(openmct) { - this.openmct = openmct; - } - - /** - * Only allow changes to composition if the changes can be saved. This in - * effect prevents selection of objects from the locator that do not - * support persistence. - * @param parent - * @param child - * @returns {boolean} - */ - PersistableCompositionPolicy.prototype.allow = function (parent) { - // If object is in edit mode, allow composition because it is - // part of object creation, and the object may be saved to another - // namespace that does support persistence. The EditPersistableObjectsPolicy - // prevents editing of objects that cannot be persisted, so we can assume that this - // is a new object. - if (!(parent.hasCapability('editor') && parent.getCapability('editor').isEditContextRoot())) { - let identifier = this.openmct.objects.parseKeyString(parent.getId()); - - return this.openmct.objects.isPersistable(identifier); - } - - return true; - }; - - return PersistableCompositionPolicy; - } -); diff --git a/platform/containment/test/ComposeActionPolicySpec.js b/platform/containment/test/ComposeActionPolicySpec.js deleted file mode 100644 index a80ee4f37f..0000000000 --- a/platform/containment/test/ComposeActionPolicySpec.js +++ /dev/null @@ -1,95 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/ComposeActionPolicy"], - function (ComposeActionPolicy) { - xdescribe("The compose action policy", function () { - var mockInjector, - mockPolicyService, - mockTypes, - mockDomainObjects, - mockAction, - testContext, - policy; - - beforeEach(function () { - mockInjector = jasmine.createSpyObj('$injector', ['get']); - mockPolicyService = jasmine.createSpyObj( - 'policyService', - ['allow'] - ); - mockTypes = ['a', 'b'].map(function (type) { - var mockType = jasmine.createSpyObj('type-' + type, ['getKey']); - mockType.getKey.and.returnValue(type); - - return mockType; - }); - mockDomainObjects = ['a', 'b'].map(function (id, index) { - var mockDomainObject = jasmine.createSpyObj( - 'domainObject-' + id, - ['getId', 'getCapability'] - ); - mockDomainObject.getId.and.returnValue(id); - mockDomainObject.getCapability.and.callFake(function (c) { - return c === 'type' && mockTypes[index]; - }); - - return mockDomainObject; - }); - mockAction = jasmine.createSpyObj('action', ['getMetadata']); - - testContext = { - key: 'compose', - domainObject: mockDomainObjects[0], - selectedObject: mockDomainObjects[1] - }; - - mockAction.getMetadata.and.returnValue(testContext); - mockInjector.get.and.callFake(function (service) { - return service === 'policyService' && mockPolicyService; - }); - - policy = new ComposeActionPolicy(mockInjector); - }); - - it("defers to composition policy", function () { - mockPolicyService.allow.and.returnValue(false); - expect(policy.allow(mockAction, testContext)).toBeFalsy(); - mockPolicyService.allow.and.returnValue(true); - expect(policy.allow(mockAction, testContext)).toBeTruthy(); - - expect(mockPolicyService.allow).toHaveBeenCalledWith( - 'composition', - mockDomainObjects[0], - mockDomainObjects[1] - ); - }); - - it("allows actions other than compose", function () { - testContext.key = 'somethingElse'; - mockPolicyService.allow.and.returnValue(false); - expect(policy.allow(mockAction, testContext)).toBeTruthy(); - }); - }); - } -); diff --git a/platform/containment/test/CompositionModelPolicySpec.js b/platform/containment/test/CompositionModelPolicySpec.js deleted file mode 100644 index c2ebb9e45d..0000000000 --- a/platform/containment/test/CompositionModelPolicySpec.js +++ /dev/null @@ -1,30 +0,0 @@ - -define( - ["../src/CompositionModelPolicy"], - function (CompositionModelPolicy) { - - describe("The composition model policy", function () { - var mockObject, - mockType, - policy; - - beforeEach(function () { - mockType = jasmine.createSpyObj('type', ['getInitialModel']); - mockObject = { - getCapability: function () { - return mockType; - } - }; - policy = new CompositionModelPolicy(); - }); - - it("only allows composition for types which will have a composition property", function () { - mockType.getInitialModel.and.returnValue({}); - expect(policy.allow(mockObject)).toBeFalsy(); - mockType.getInitialModel.and.returnValue({ composition: [] }); - expect(policy.allow(mockObject)).toBeTruthy(); - }); - }); - - } -); diff --git a/platform/containment/test/CompositionMutabilityPolicySpec.js b/platform/containment/test/CompositionMutabilityPolicySpec.js deleted file mode 100644 index 900a821d54..0000000000 --- a/platform/containment/test/CompositionMutabilityPolicySpec.js +++ /dev/null @@ -1,51 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/CompositionMutabilityPolicy"], - function (CompositionMutabilityPolicy) { - - describe("The composition mutability policy", function () { - var mockObject, - mockType, - policy; - - beforeEach(function () { - mockType = jasmine.createSpyObj('type', ['hasFeature']); - mockObject = { - getCapability: function () { - return mockType; - } - }; - policy = new CompositionMutabilityPolicy(); - }); - - it("only allows composition for types which can be created/modified", function () { - expect(policy.allow(mockObject)).toBeFalsy(); - mockType.hasFeature.and.returnValue(true); - expect(policy.allow(mockObject)).toBeTruthy(); - expect(mockType.hasFeature).toHaveBeenCalledWith('creation'); - }); - }); - - } -); diff --git a/platform/containment/test/CompositionPolicySpec.js b/platform/containment/test/CompositionPolicySpec.js deleted file mode 100644 index ad29b572a9..0000000000 --- a/platform/containment/test/CompositionPolicySpec.js +++ /dev/null @@ -1,131 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/CompositionPolicy"], - function (CompositionPolicy) { - describe("Composition policy", function () { - var mockParentObject, - typeA, - typeB, - typeC, - mockChildObject, - policy; - - beforeEach(function () { - mockParentObject = jasmine.createSpyObj('domainObject', [ - 'getCapability' - ]); - - typeA = jasmine.createSpyObj( - 'type A-- the particular kind', - ['getKey', 'getDefinition'] - ); - typeA.getKey.and.returnValue('a'); - typeA.getDefinition.and.returnValue({ - contains: ['a'] - }); - - typeB = jasmine.createSpyObj( - 'type B-- anything goes', - ['getKey', 'getDefinition'] - ); - typeB.getKey.and.returnValue('b'); - typeB.getDefinition.and.returnValue({ - contains: ['a', 'b'] - }); - - typeC = jasmine.createSpyObj( - 'type C-- distinguishing and interested in telemetry', - ['getKey', 'getDefinition'] - ); - typeC.getKey.and.returnValue('c'); - typeC.getDefinition.and.returnValue({ - contains: [{has: 'telemetry'}] - }); - - mockChildObject = jasmine.createSpyObj( - 'childObject', - ['getCapability', 'hasCapability'] - ); - - policy = new CompositionPolicy(); - }); - - describe('enforces simple containment rules', function () { - - it('allows when type matches', function () { - mockParentObject.getCapability.and.returnValue(typeA); - - mockChildObject.getCapability.and.returnValue(typeA); - expect(policy.allow(mockParentObject, mockChildObject)) - .toBeTruthy(); - - mockParentObject.getCapability.and.returnValue(typeB); - expect(policy.allow(mockParentObject, mockChildObject)) - .toBeTruthy(); - - mockChildObject.getCapability.and.returnValue(typeB); - expect(policy.allow(mockParentObject, mockChildObject)) - .toBeTruthy(); - }); - - it('disallows when type doesn\'t match', function () { - - mockParentObject.getCapability.and.returnValue(typeA); - mockChildObject.getCapability.and.returnValue(typeB); - expect(policy.allow(mockParentObject, mockChildObject)) - .toBeFalsy(); - - mockChildObject.getCapability.and.returnValue(typeC); - expect(policy.allow(mockParentObject, mockChildObject)) - .toBeFalsy(); - }); - - }); - - describe('enforces capability-based containment rules', function () { - it('allows when object has capability', function () { - mockParentObject.getCapability.and.returnValue(typeC); - - mockChildObject.hasCapability.and.returnValue(true); - expect(policy.allow(mockParentObject, mockChildObject)) - .toBeTruthy(); - expect(mockChildObject.hasCapability) - .toHaveBeenCalledWith('telemetry'); - }); - - it('skips when object doesn\'t have capability', function () { - mockChildObject.hasCapability.and.returnValue(false); - - mockParentObject.getCapability.and.returnValue(typeC); - - expect(policy.allow(mockParentObject, mockChildObject)) - .toBeFalsy(); - expect(mockChildObject.hasCapability) - .toHaveBeenCalledWith('telemetry'); - }); - }); - - }); - } -); diff --git a/platform/containment/test/PersistableCompositionPolicySpec.js b/platform/containment/test/PersistableCompositionPolicySpec.js deleted file mode 100644 index 6f9aa74bd6..0000000000 --- a/platform/containment/test/PersistableCompositionPolicySpec.js +++ /dev/null @@ -1,85 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-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/PersistableCompositionPolicy"], - function (PersistableCompositionPolicy) { - describe("Persistable Composition policy", function () { - var objectAPI; - var mockOpenMCT; - var persistableCompositionPolicy; - var mockParent; - var mockChild; - var mockEditorCapability; - - beforeEach(function () { - objectAPI = jasmine.createSpyObj('objectsAPI', [ - 'isPersistable', - 'parseKeyString' - ]); - - mockOpenMCT = { - objects: objectAPI - }; - mockParent = jasmine.createSpyObj('domainObject', [ - 'hasCapability', - 'getCapability', - 'getId' - ]); - mockParent.hasCapability.and.returnValue(true); - mockParent.getId.and.returnValue('someNamespace:someId'); - mockChild = {}; - mockEditorCapability = jasmine.createSpyObj('domainObject', [ - 'isEditContextRoot' - ]); - mockParent.getCapability.and.returnValue(mockEditorCapability); - persistableCompositionPolicy = new PersistableCompositionPolicy(mockOpenMCT); - }); - - //Parent - // - getCapability ('editor') - // - isEditContextRoot - // - openMct.objects.getProvider - - it("Does not allow composition for objects that are not persistable", function () { - mockEditorCapability.isEditContextRoot.and.returnValue(false); - objectAPI.isPersistable.and.returnValue(true); - expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true); - objectAPI.isPersistable.and.returnValue(false); - expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(false); - }); - - it("Always allows composition of objects in edit mode to support object creation", function () { - mockEditorCapability.isEditContextRoot.and.returnValue(true); - objectAPI.isPersistable.and.returnValue(true); - expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true); - expect(objectAPI.isPersistable).not.toHaveBeenCalled(); - - mockEditorCapability.isEditContextRoot.and.returnValue(false); - objectAPI.isPersistable.and.returnValue(true); - expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true); - expect(objectAPI.isPersistable).toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/core/README.md b/platform/core/README.md deleted file mode 100644 index 422ab4ce63..0000000000 --- a/platform/core/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This bundle contains core software components of Open MCT. These -components describe MCT's information model, and are responsible for -introducing the API and infrastructure which supports domain objects. diff --git a/platform/core/bundle.js b/platform/core/bundle.js deleted file mode 100644 index 97013e8358..0000000000 --- a/platform/core/bundle.js +++ /dev/null @@ -1,357 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/objects/DomainObjectProvider", - "./src/capabilities/CoreCapabilityProvider", - "./src/models/StaticModelProvider", - "./src/models/ModelAggregator", - "./src/models/ModelCacheService", - "./src/models/PersistedModelProvider", - "./src/models/CachingModelDecorator", - "./src/types/TypeProvider", - "./src/actions/ActionProvider", - "./src/actions/ActionAggregator", - "./src/actions/LoggingActionDecorator", - "./src/views/ViewProvider", - "./src/identifiers/IdentifierProvider", - "./src/capabilities/CompositionCapability", - "./src/capabilities/RelationshipCapability", - "./src/types/TypeCapability", - "./src/actions/ActionCapability", - "./src/views/ViewCapability", - "./src/capabilities/PersistenceCapability", - "./src/capabilities/MetadataCapability", - "./src/capabilities/MutationCapability", - "./src/capabilities/DelegationCapability", - "./src/capabilities/InstantiationCapability", - "./src/services/Now", - "./src/services/Throttle", - "./src/services/Topic", - "./src/services/Instantiate" -], function ( - DomainObjectProvider, - CoreCapabilityProvider, - StaticModelProvider, - ModelAggregator, - ModelCacheService, - PersistedModelProvider, - CachingModelDecorator, - TypeProvider, - ActionProvider, - ActionAggregator, - LoggingActionDecorator, - ViewProvider, - IdentifierProvider, - CompositionCapability, - RelationshipCapability, - TypeCapability, - ActionCapability, - ViewCapability, - PersistenceCapability, - MetadataCapability, - MutationCapability, - DelegationCapability, - InstantiationCapability, - Now, - Throttle, - Topic, - Instantiate -) { - - return { - name: "platform/core", - definition: { - "name": "Open MCT Core", - "description": "Defines core concepts of Open MCT.", - "sources": "src", - "configuration": { - "paths": { - "uuid": "uuid" - } - }, - "extensions": { - "components": [ - { - "provides": "objectService", - "type": "provider", - "implementation": DomainObjectProvider, - "depends": [ - "modelService", - "instantiate" - ] - }, - { - "provides": "capabilityService", - "type": "provider", - "implementation": CoreCapabilityProvider, - "depends": [ - "capabilities[]", - "$log" - ] - }, - { - "provides": "modelService", - "type": "provider", - "implementation": StaticModelProvider, - "depends": [ - "models[]", - "$q", - "$log" - ] - }, - { - "provides": "modelService", - "type": "aggregator", - "implementation": ModelAggregator, - "depends": [ - "$q" - ] - }, - { - "provides": "modelService", - "type": "provider", - "implementation": PersistedModelProvider, - "depends": [ - "persistenceService", - "$q", - "now", - "PERSISTENCE_SPACE" - ] - }, - { - "provides": "modelService", - "type": "decorator", - "implementation": CachingModelDecorator, - "depends": [ - "cacheService" - ] - }, - { - "provides": "typeService", - "type": "provider", - "implementation": TypeProvider, - "depends": [ - "types[]" - ] - }, - { - "provides": "actionService", - "type": "provider", - "implementation": ActionProvider, - "depends": [ - "actions[]", - "$log" - ] - }, - { - "provides": "actionService", - "type": "aggregator", - "implementation": ActionAggregator - }, - { - "provides": "actionService", - "type": "decorator", - "implementation": LoggingActionDecorator, - "depends": [ - "$log" - ] - }, - { - "provides": "viewService", - "type": "provider", - "implementation": ViewProvider, - "depends": [ - "views[]", - "$log" - ] - }, - { - "provides": "identifierService", - "type": "provider", - "implementation": IdentifierProvider, - "depends": [ - "PERSISTENCE_SPACE" - ] - } - ], - "types": [ - { - "properties": [ - { - "control": "textfield", - "name": "Title", - "key": "name", - "property": "name", - "pattern": "\\S+", - "required": true, - "cssClass": "l-input-lg" - }, - { - "name": "Notes", - "key": "notes", - "property": "notes", - "control": "textarea", - "required": false, - "cssClass": "l-textarea-sm" - } - ] - }, - { - "key": "root", - "name": "Root", - "cssClass": "icon-folder" - } - ], - "capabilities": [ - { - "key": "composition", - "implementation": CompositionCapability, - "depends": [ - "$injector" - ] - }, - { - "key": "relationship", - "implementation": RelationshipCapability, - "depends": [ - "$injector" - ] - }, - { - "key": "type", - "implementation": TypeCapability, - "depends": [ - "typeService" - ] - }, - { - "key": "action", - "implementation": ActionCapability, - "depends": [ - "$q", - "actionService" - ] - }, - { - "key": "view", - "implementation": ViewCapability, - "depends": [ - "viewService" - ] - }, - { - "key": "persistence", - "implementation": PersistenceCapability, - "depends": [ - "cacheService", - "persistenceService", - "identifierService", - "notificationService", - "$q", - "openmct" - ] - }, - { - "key": "metadata", - "implementation": MetadataCapability - }, - { - "key": "mutation", - "implementation": MutationCapability, - "depends": [ - "topic", - "now" - ] - }, - { - "key": "delegation", - "implementation": DelegationCapability, - "depends": [ - "$q" - ] - }, - { - "key": "instantiation", - "implementation": InstantiationCapability, - "depends": [ - "$injector", - "identifierService", - "now" - ] - } - ], - "services": [ - { - "key": "cacheService", - "implementation": ModelCacheService - }, - { - "key": "now", - "implementation": Now - }, - { - "key": "throttle", - "implementation": Throttle, - "depends": [ - "$timeout" - ] - }, - { - "key": "topic", - "implementation": Topic, - "depends": [ - "$log" - ] - }, - { - "key": "instantiate", - "implementation": Instantiate, - "depends": [ - "capabilityService", - "identifierService", - "cacheService" - ] - } - ], - "constants": [ - { - "key": "PERSISTENCE_SPACE", - "value": "mct" - } - ], - "licenses": [ - { - "name": "Math.uuid.js", - "version": "1.4.7", - "description": "Unique identifier generation (code adapted.)", - "author": "Robert Kieffer", - "website": "https://github.com/broofa/node-uuid", - "copyright": "Copyright (c) 2010-2012 Robert Kieffer", - "license": "license-mit", - "link": "http://opensource.org/licenses/MIT" - } - ] - } - } - }; -}); diff --git a/platform/core/src/actions/ActionAggregator.js b/platform/core/src/actions/ActionAggregator.js deleted file mode 100644 index 7d664f009a..0000000000 --- a/platform/core/src/actions/ActionAggregator.js +++ /dev/null @@ -1,124 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - function () { - - /** - * Actions are reusable processes/behaviors performed by users within - * the system, typically upon domain objects. Actions are commonly - * exposed to users as menu items or buttons. - * - * Actions are usually registered via the `actions` extension - * category, or (in advanced cases) via an `actionService` - * implementation. - * - * @interface Action - */ - - /** - * Perform the behavior associated with this action. The return type - * may vary depending on which action has been performed; in general, - * no return value should be expected. - * - * @method Action#perform - */ - - /** - * Get metadata associated with this action. - * - * @method Action#getMetadata - * @returns {ActionMetadata} - */ - - /** - * Metadata associated with an Action. Actions of specific types may - * extend this with additional properties. - * - * @typedef {Object} ActionMetadata - * @property {string} key machine-readable identifier for this action - * @property {string} name human-readable name for this action - * @property {string} description human-readable description - * @property {string} cssClass CSS class for icon - * @property {ActionContext} context the context in which the action - * will be performed. - */ - - /** - * Provides actions that can be performed within specific contexts. - * - * @interface ActionService - */ - - /** - * Get actions which can be performed within a certain context. - * - * @method ActionService#getActions - * @param {ActionContext} context the context in which the action will - * be performed - * @return {Action[]} relevant actions - */ - - /** - * A description of the context in which an action may occur. - * - * @typedef ActionContext - * @property {DomainObject} [domainObject] the domain object being - * acted upon. - * @property {DomainObject} [selectedObject] the selection at the - * time of action (e.g. the dragged object in a - * drag-and-drop operation.) - * @property {string} [key] the machine-readable identifier of - * the relevant action - * @property {string} [category] a string identifying the category - * of action being performed - */ - - /** - * The ActionAggregator makes several actionService - * instances act as those they were one. When requesting - * actions for a given context, results from all - * services will be assembled and concatenated. - * - * @memberof platform/core - * @constructor - * @implements {ActionService} - * @param {ActionService[]} actionProviders an array - * of action services - */ - function ActionAggregator(actionProviders) { - this.actionProviders = actionProviders; - } - - ActionAggregator.prototype.getActions = function (context) { - // Get all actions from all providers, reduce down - // to one array by concatenation - return this.actionProviders.map(function (provider) { - return provider.getActions(context); - }).reduce(function (a, b) { - return a.concat(b); - }, []); - }; - - return ActionAggregator; - } -); diff --git a/platform/core/src/actions/ActionCapability.js b/platform/core/src/actions/ActionCapability.js deleted file mode 100644 index 09a51ee030..0000000000 --- a/platform/core/src/actions/ActionCapability.js +++ /dev/null @@ -1,119 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * Module defining ActionCapability. Created by vwoeltje on 11/10/14. - */ -define( - ['lodash'], - function (_) { - - /** - * The ActionCapability allows applicable Actions to be retrieved and - * performed for specific domain objects, e.g.: - * - * `domainObject.getCapability("action").perform("navigate");` - * - * ...will initiate a navigate action upon the domain object, - * if an action with key "navigate" is defined. - * - * @param {*} $q Angular's $q service, for promises - * @param {ActionService} actionService the service from - * which to retrieve actions. - * @param {DomainObject} domainObject the object upon - * which the action will be performed (also, the - * action which exposes the capability.) - * - * @memberof platform/core - * @constructor - */ - function ActionCapability($q, actionService, domainObject) { - this.$q = $q; - this.actionService = actionService; - this.domainObject = domainObject; - } - - /** - * Perform an action. This will find and perform the - * first matching action available for the specified - * context or key. - * - * @param {ActionContext|string} context the context in which - * to perform the action; this is passed along to - * the action service to match against available - * actions. The "domainObject" field will automatically - * be populated with the domain object that exposed - * this capability. If given as a string, this will - * be taken as the "key" field to match against - * specific actions. - * @returns {Promise} the result of the action that was - * performed, or undefined if no matching action - * was found. - * @memberof platform/core.ActionCapability# - */ - ActionCapability.prototype.getActions = function (context) { - // Get all actions which are valid in this context; - // this simply redirects to the action service, - // but additionally adds a domainObject field. - var baseContext; - if (typeof context === 'string') { - baseContext = { key: context }; - } else { - baseContext = context || {}; - } - - var actionContext = Object.assign({}, baseContext); - actionContext.domainObject = this.domainObject; - - return this.actionService.getActions(actionContext); - }; - - /** - * Get actions which are available for this domain object, - * in this context. - * - * @param {ActionContext|string} context the context in which - * to perform the action; this is passed along to - * the action service to match against available - * actions. The "domainObject" field will automatically - * be populated with the domain object that exposed - * this capability. If given as a string, this will - * be taken as the "key" field to match against - * specific actions. - * @returns {Action[]} an array of matching actions - * @memberof platform/core.ActionCapability# - */ - ActionCapability.prototype.perform = function (context, flag) { - // Alias to getActions(context)[0].perform, with a - // check for empty arrays. - var actions = this.getActions(context); - - return this.$q.when( - (actions && actions.length > 0) - ? actions[0].perform(flag) - : undefined - ); - }; - - return ActionCapability; - } -); diff --git a/platform/core/src/actions/ActionProvider.js b/platform/core/src/actions/ActionProvider.js deleted file mode 100644 index 6aa7a06f0b..0000000000 --- a/platform/core/src/actions/ActionProvider.js +++ /dev/null @@ -1,157 +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. - *****************************************************************************/ - -/** - * Module defining ActionProvider. Created by vwoeltje on 11/7/14. - */ -define( - [], - function () { - - /** - * An ActionProvider (implementing ActionService) provides actions - * that are applicable in specific contexts, chosen from a set - * of actions exposed via extension (specifically, the "actions" - * category of extension.) - * - * @memberof platform/core - * @imeplements {ActionService} - * @constructor - */ - function ActionProvider(actions, $log) { - var self = this; - - this.$log = $log; - - // Build up look-up tables - this.actions = actions; - this.actionsByKey = {}; - this.actionsByCategory = {}; - actions.forEach(function (Action) { - // Get an action's category or categories - var categories = Action.category || []; - - // Convert to an array if necessary - categories = Array.isArray(categories) - ? categories : [categories]; - - // Store action under all relevant categories - categories.forEach(function (category) { - self.actionsByCategory[category] = - self.actionsByCategory[category] || []; - self.actionsByCategory[category].push(Action); - }); - - // Store action by ekey as well - if (Action.key) { - self.actionsByKey[Action.key] = - self.actionsByKey[Action.key] || []; - self.actionsByKey[Action.key].push(Action); - } - }); - } - - ActionProvider.prototype.getActions = function (actionContext) { - var context = (actionContext || {}), - category = context.category, - key = context.key, - $log = this.$log, - candidates; - - // Instantiate an action; invokes the constructor and - // additionally fills in the action's getMetadata method - // with the extension definition (if no getMetadata - // method was supplied.) - function instantiateAction(Action, ctxt) { - var action = new Action(ctxt), - metadata; - - // Provide a getMetadata method that echos - // declarative bindings, as well as context, - // unless the action has defined its own. - if (!action.getMetadata) { - metadata = Object.create(Action.definition || {}); - metadata.context = ctxt; - action.getMetadata = function () { - return metadata; - }; - } - - return action; - } - - // Filter the array of actions down to those which are - // applicable in a given context, according to the static - // appliesTo method of given actions (if defined), and - // instantiate those applicable actions. - function createIfApplicable(actions, ctxt) { - function isApplicable(Action) { - return Action.appliesTo ? Action.appliesTo(ctxt) : true; - } - - function instantiate(Action) { - try { - return instantiateAction(Action, ctxt); - } catch (e) { - $log.error([ - "Could not instantiate action", - Action.key, - "due to:", - e.message - ].join(" ")); - - return undefined; - } - } - - function isDefined(action) { - return action !== undefined; - } - - return (actions || []).filter(isApplicable) - .map(instantiate) - .filter(isDefined); - } - - // Match actions to the provided context by comparing "key" - // and/or "category" parameters, if specified. - candidates = this.actions; - if (key) { - candidates = this.actionsByKey[key]; - if (category) { - candidates = candidates.filter(function (Action) { - return Action.category === category; - }); - } - } else if (category) { - candidates = this.actionsByCategory[category]; - } - - // Instantiate those remaining actions, with additional - // filtering per any appliesTo methods defined on those - // actions. - return createIfApplicable(candidates, context); - }; - - return ActionProvider; - } -); diff --git a/platform/core/src/actions/LoggingActionDecorator.js b/platform/core/src/actions/LoggingActionDecorator.js deleted file mode 100644 index 5d4d1bd4ad..0000000000 --- a/platform/core/src/actions/LoggingActionDecorator.js +++ /dev/null @@ -1,80 +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. - *****************************************************************************/ - -/** - * Module defining LoggingActionDecorator. Created by vwoeltje on 11/17/14. - */ -define( - [], - function () { - - /** - * The LoggingActionDecorator decorates an ActionService such that - * the actions it exposes always emit a log message when they are - * performed. - * - * @memberof platform/core - * @constructor - * @implements {ActionService} - * @param $log Angular's logging service - * @param {ActionService} actionService the decorated action service - */ - function LoggingActionDecorator($log, actionService) { - this.$log = $log; - this.actionService = actionService; - } - - LoggingActionDecorator.prototype.getActions = function () { - var actionService = this.actionService, - $log = this.$log; - - // Decorate the perform method of the specified action, such that - // it emits a log message whenever performed. - function addLogging(action) { - var logAction = Object.create(action), - metadata = action.getMetadata() || {}, - context = metadata.context || {}, - domainObject = context.domainObject; - - logAction.perform = function () { - $log.info([ - "Performing action ", - metadata.key, - " upon ", - domainObject && domainObject.getId() - ].join("")); - - return action.perform.apply(action, arguments); - }; - - return logAction; - } - - return actionService.getActions.apply( - actionService, - arguments - ).map(addLogging); - }; - - return LoggingActionDecorator; - } -); diff --git a/platform/core/src/capabilities/CompositionCapability.js b/platform/core/src/capabilities/CompositionCapability.js deleted file mode 100644 index 9b432a6f2c..0000000000 --- a/platform/core/src/capabilities/CompositionCapability.js +++ /dev/null @@ -1,162 +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. - *****************************************************************************/ - -/** - * Module defining CompositionCapability. Created by vwoeltje on 11/7/14. - */ -define( - ['./ContextualDomainObject'], - function (ContextualDomainObject) { - - /** - * Composition capability. A domain object's composition is the set of - * domain objects it contains. This is available as an array of - * identifiers in the model; the composition capability makes this - * available as an array of domain object instances, which may - * require consulting the object service (e.g. to trigger a database - * query to retrieve the nested object models.) - * - * @memberof platform/core - * @constructor - * @implements {Capability} - */ - function CompositionCapability($injector, domainObject) { - // Get a reference to the object service from $injector - this.injectObjectService = function () { - this.objectService = $injector.get("objectService"); - }; - - this.domainObject = domainObject; - } - - /** - * Add a domain object to the composition of this domain object. - * - * If no index is given, this is added to the end of the composition. - * - * @param {DomainObject|string} domainObject the domain object to add, - * or simply its identifier - * @param {number} [index] the index at which to add the object - * @returns {Promise.} a promise for the added object - * in its new context - */ - CompositionCapability.prototype.add = function (domainObject, index) { - var self = this, - id = typeof domainObject === 'string' - ? domainObject : domainObject.getId(), - model = self.domainObject.getModel(), - composition = model.composition, - oldIndex = composition.indexOf(id); - - // Find the object with the above id, used to contextualize - function findObject(objects) { - var i; - for (i = 0; i < objects.length; i += 1) { - if (objects[i].getId() === id) { - return objects[i]; - } - } - } - - function contextualize(mutationResult) { - return mutationResult && self.invoke().then(findObject); - } - - function addIdToModel(objModel) { - // Pick a specific index if needed. - index = isNaN(index) ? composition.length : index; - // Also, don't put past the end of the array - index = Math.min(composition.length, index); - - // Remove the existing instance of the id - if (oldIndex !== -1) { - objModel.composition.splice(oldIndex, 1); - } - - // ...and add it back at the appropriate index. - objModel.composition.splice(index, 0, id); - } - - // If no index has been specified already and the id is already - // present, nothing to do. If the id is already at that index, - // also nothing to do, so cancel mutation. - if ((isNaN(index) && oldIndex !== -1) || (index === oldIndex)) { - return contextualize(true); - } - - return this.domainObject.useCapability('mutation', addIdToModel) - .then(contextualize); - }; - - /** - * Request the composition of this object. - * @returns {Promise.} a list of all domain - * objects which compose this domain object. - */ - CompositionCapability.prototype.invoke = function () { - var domainObject = this.domainObject, - model = domainObject.getModel(), - ids; - - // Then filter out non-existent objects, - // and wrap others (such that they expose a - // "context" capability) - function contextualizeObjects(objects) { - return ids.filter(function (id) { - return objects[id]; - }).map(function (id) { - return new ContextualDomainObject(objects[id], domainObject); - }); - } - - // Lazily acquire object service (avoids cyclical dependency) - if (!this.objectService) { - this.injectObjectService(); - } - - // Make a new request if we haven't made one, or if the - // object has been modified. - if (!this.lastPromise || this.lastModified !== model.modified) { - ids = model.composition || []; - this.lastModified = model.modified; - // Load from the underlying object service - this.lastPromise = this.objectService.getObjects(ids) - .then(contextualizeObjects); - } - - return this.lastPromise; - }; - - /** - * Test to determine whether or not this capability should be exposed - * by a domain object based on its model. Checks for the presence of - * a composition field, that must be an array. - * @param model the domain object model - * @returns {boolean} true if this object has a composition - */ - CompositionCapability.appliesTo = function (model) { - return Array.isArray((model || {}).composition); - }; - - return CompositionCapability; - } -); diff --git a/platform/core/src/capabilities/ContextCapability.js b/platform/core/src/capabilities/ContextCapability.js deleted file mode 100644 index 16857e5f5c..0000000000 --- a/platform/core/src/capabilities/ContextCapability.js +++ /dev/null @@ -1,111 +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. - *****************************************************************************/ - -/** - * Module defining ContextCapability. Created by vwoeltje on 11/17/14. - */ -define( - [], - function () { - - /** - * The `context` capability of a domain object (retrievable with - * `domainObject.getCapability("context")`) allows an object's - * hierarchical parents and ancestors to be retrieved (specifically, - * those whose `composition` capability was used to access this - * object.) - * - * @memberof platform/core - * @constructor - * @implements {Capability} - */ - function ContextCapability(parentObject, domainObject) { - this.parentObject = parentObject; - this.domainObject = domainObject; - } - - /** - * Get the immediate parent of a domain object. - * - * A domain object may be contained in multiple places; its - * parent (as exposed by this capability) is the domain - * object from which this object was accessed, usually - * by way of a `composition` capability. - * - * @returns {DomainObject} the immediate parent of this - * domain object. - */ - ContextCapability.prototype.getParent = function () { - return this.parentObject; - }; - - /** - * Get an array containing the complete direct ancestry - * of this domain object, including the domain object - * itself. - * - * A domain object may be contained in multiple places; its - * parent and all ancestors (as exposed by this capability) - * serve as a record of how this specific domain object - * instance was reached. - * - * The first element in the returned array is the deepest - * ancestor; subsequent elements are progressively more - * recent ancestors, with the domain object which exposed - * the capability occupying the last element of the array. - * - * @returns {DomainObject[]} the full composition ancestry - * of the domain object which exposed this - * capability. - */ - ContextCapability.prototype.getPath = function () { - var parentObject = this.parentObject, - parentContext = - parentObject && parentObject.getCapability('context'), - parentPath = parentContext - ? parentContext.getPath() : [this.parentObject]; - - return parentPath.concat([this.domainObject]); - }; - - /** - * Get the deepest ancestor available for this domain object; - * equivalent to `getPath()[0]`. - * - * See notes on `getPath()` for how ancestry is defined in - * the context of this capability. - * - * @returns {DomainObject} the deepest ancestor of the domain - * object which exposed this capability. - */ - ContextCapability.prototype.getRoot = function () { - var parentContext = this.parentObject - && this.parentObject.getCapability('context'); - - return parentContext - ? parentContext.getRoot() - : (this.parentObject || this.domainObject); - }; - - return ContextCapability; - } -); diff --git a/platform/core/src/capabilities/ContextualDomainObject.js b/platform/core/src/capabilities/ContextualDomainObject.js deleted file mode 100644 index 7e5eb50ac3..0000000000 --- a/platform/core/src/capabilities/ContextualDomainObject.js +++ /dev/null @@ -1,66 +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. - *****************************************************************************/ - -/** - * Module defining ContextualDomainObject. Created by vwoeltje on 11/18/14. - */ -define( - ["./ContextCapability"], - function (ContextCapability) { - - /** - * Wraps a domain object, such that it exposes a `context` capability. - * A domain object may be contained by multiple other domain objects; - * the `context` capability allows two instances of the same domain - * object to be distinguished from one another based on which - * specific instance of a containing object exposed them (by way of a - * `composition` capability.) - * - * @param {DomainObject} domainObject the domain object for which - * context should be exposed - * @param {DomainObject} parentObject the domain object from which - * the wrapped object was retrieved - * - * @memberof platform/core - * @constructor - * @implements {DomainObject} - */ - function ContextualDomainObject(domainObject, parentObject) { - // Prototypally inherit from the domain object, and - // instantiate its context capability ahead of time. - var contextualObject = Object.create(domainObject), - contextCapability = - new ContextCapability(parentObject, domainObject); - - // Intercept requests for a context capability. - contextualObject.getCapability = function (name) { - return name === "context" - ? contextCapability - : domainObject.getCapability.apply(this, arguments); - }; - - return contextualObject; - } - - return ContextualDomainObject; - } -); diff --git a/platform/core/src/capabilities/CoreCapabilityProvider.js b/platform/core/src/capabilities/CoreCapabilityProvider.js deleted file mode 100644 index 5c9385561a..0000000000 --- a/platform/core/src/capabilities/CoreCapabilityProvider.js +++ /dev/null @@ -1,110 +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. - *****************************************************************************/ - -/** - * Module defining CoreCapabilityProvider. Created by vwoeltje on 11/7/14. - */ -define( - [], - function () { - - /** - * A capability provides an interface with dealing with some - * dynamic behavior associated with a domain object. - * @interface Capability - */ - - /** - * Optional; if present, will be used by `DomainObject#useCapability` - * to simplify interaction with a specific capability. Parameters - * and return values vary depending on capability type. - * @method Capability#invoke - */ - - /** - * Provides capabilities based on extension definitions, - * matched to domain object models. - * - * @param {Array.} an array - * of constructor functions for capabilities, as - * exposed by extensions defined at the bundle level. - * - * @memberof platform/core - * @constructor - */ - function CoreCapabilityProvider(capabilities, $log) { - // Filter by invoking the capability's appliesTo method - function filterCapabilities(model, id) { - return capabilities.filter(function (capability) { - return capability.appliesTo - ? capability.appliesTo(model, id) - : true; - }); - } - - // Package capabilities as key-value pairs - function packageCapabilities(caps) { - var result = {}; - caps.forEach(function (capability) { - if (capability.key) { - result[capability.key] = - result[capability.key] || capability; - } else { - $log.warn("No key defined for capability; skipping."); - } - }); - - return result; - } - - function getCapabilities(model, id) { - return packageCapabilities(filterCapabilities(model, id)); - } - - return { - /** - * Get all capabilities associated with a given domain - * object. - * - * This returns a promise for an object containing key-value - * pairs, where keys are capability names and values are - * either: - * - * * Capability instances - * * Capability constructors (which take a domain object - * as their argument.) - * - * - * @param {*} model the object model - * @returns {Object.} all - * capabilities known to be valid for this model, as - * key-value pairs - * @memberof platform/core.CoreCapabilityProvider# - */ - getCapabilities: getCapabilities - }; - } - - return CoreCapabilityProvider; - } -); - diff --git a/platform/core/src/capabilities/DelegationCapability.js b/platform/core/src/capabilities/DelegationCapability.js deleted file mode 100644 index 61f652f6a3..0000000000 --- a/platform/core/src/capabilities/DelegationCapability.js +++ /dev/null @@ -1,129 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * Module defining DelegationCapability. Created by vwoeltje on 11/18/14. - */ -define( - [], - function () { - - /** - * The `delegation` capability allows a domain object to indicate - * that it wishes to delegate responsibility for some other - * capability to some other domain objects. - * - * This is specifically useful in the case of telemetry panels, - * which delegate responsibility for the `telemetry` capability - * to their contained objects. - * - * A type of domain object may indicate that it wishes to delegate - * responsibility for one or more capabilities to the members of - * its composition; this is done by included a `delegates` field - * in the type's definition, which contains an array of names of - * capabilities to be delegated. - * - * @param $q Angular's $q, for promises - * @param {DomainObject} domainObject the delegating domain object - * @memberof platform/core - * @constructor - * @implements {Capability} - */ - function DelegationCapability($q, domainObject) { - var type = domainObject.getCapability("type"), - self = this; - - this.$q = $q; - this.delegateCapabilities = {}; - this.domainObject = domainObject; - - // Generate set for easy lookup of capability delegation - if (type && type.getDefinition) { - (type.getDefinition().delegates || []).forEach(function (key) { - self.delegateCapabilities[key] = true; - }); - } - } - - /** - * Get the domain objects which are intended to be delegated - * responsibility for some specific capability. - * - * @param {string} key the name of the delegated capability - * @returns {DomainObject[]} the domain objects to which - * responsibility for this capability is delegated. - * @memberof platform/core.DelegationCapability# - */ - DelegationCapability.prototype.getDelegates = function (key) { - var domainObject = this.domainObject; - - function filterObjectsWithCapability(capability) { - return function (objects) { - return objects.filter(function (obj) { - return obj.hasCapability(capability); - }); - }; - } - - function promiseChildren() { - return domainObject.useCapability('composition'); - } - - return this.doesDelegateCapability(key) - ? promiseChildren().then( - filterObjectsWithCapability(key) - ) - : this.$q.when([]); - }; - - /** - * Check if the domain object which exposed this capability - * wishes to delegate another capability. - * - * @param {string} key the capability to check for - * @returns {boolean} true if the capability is delegated - */ - DelegationCapability.prototype.doesDelegateCapability = function (key) { - return Boolean(this.delegateCapabilities[key]); - }; - - /** - * Invoke this capability; alias of `getDelegates`, used to - * simplify usage, e.g.: - * - * `domainObject.useCapability("delegation", "telemetry")` - * - * ...will retrieve all members of a domain object's - * composition which have a "telemetry" capability. - * - * @param {string} the name of the delegated capability - * @returns {DomainObject[]} the domain objects to which - * responsibility for this capability is delegated. - * @memberof platform/core.DelegationCapability# - */ - DelegationCapability.prototype.invoke = - DelegationCapability.prototype.getDelegates; - - return DelegationCapability; - - } -); diff --git a/platform/core/src/capabilities/InstantiationCapability.js b/platform/core/src/capabilities/InstantiationCapability.js deleted file mode 100644 index f8592f6133..0000000000 --- a/platform/core/src/capabilities/InstantiationCapability.js +++ /dev/null @@ -1,85 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['./ContextualDomainObject'], - function (ContextualDomainObject) { - - /** - * Implements the `instantiation` capability. This allows new domain - * objects to be instantiated. - * - * @constructor - * @memberof platform/core - * @param $injector Angular's `$injector` - * @implements {Capability} - */ - function InstantiationCapability( - $injector, - identifierService, - now, - domainObject - ) { - this.$injector = $injector; - this.identifierService = identifierService; - this.domainObject = domainObject; - this.now = now; - } - - /** - * Instantiate a new domain object with the provided model. - * - * This domain object will have been simply instantiated; it will not - * have been persisted, nor will it have been added to the - * composition of the object which exposed this capability. - * - * @param {object} the model for the new domain object - * @returns {DomainObject} the new domain object - */ - InstantiationCapability.prototype.instantiate = function (model) { - var parsedId = - this.identifierService.parse(this.domainObject.getId()), - space = parsedId.getDefinedSpace(), - id = this.identifierService.generate(space); - - model.modified = this.now(); - - // Lazily initialize; instantiate depends on capabilityService, - // which depends on all capabilities, including this one. - this.instantiateFn = this.instantiateFn - || this.$injector.get("instantiate"); - - var newObject = this.instantiateFn(model, id); - - return new ContextualDomainObject(newObject, this.domainObject); - }; - - /** - * Alias of `instantiate`. - * @see {platform/core.CreationCapability#instantiate} - */ - InstantiationCapability.prototype.invoke = - InstantiationCapability.prototype.instantiate; - - return InstantiationCapability; - } -); diff --git a/platform/core/src/capabilities/MetadataCapability.js b/platform/core/src/capabilities/MetadataCapability.js deleted file mode 100644 index fa8f93516b..0000000000 --- a/platform/core/src/capabilities/MetadataCapability.js +++ /dev/null @@ -1,92 +0,0 @@ - -define( - ['moment'], - function (moment) { - - /** - * A piece of information about a domain object. - * @typedef {Object} MetadataProperty - * @property {string} name the human-readable name of this property - * @property {string} value the human-readable value of this property, - * for this specific domain object - */ - - var TIME_FORMAT = "YYYY-MM-DD HH:mm:ss"; - - /** - * Implements the `metadata` capability of a domain object, providing - * properties of that object for display. - * - * Usage: `domainObject.useCapability("metadata")` - * - * ...which will return an array of objects containing `name` and - * `value` properties describing that domain object (suitable for - * display.) - * - * @param {DomainObject} domainObject the domain object whose - * metadata is to be exposed - * @implements {Capability} - * @constructor - * @memberof platform/core - */ - function MetadataCapability(domainObject) { - this.domainObject = domainObject; - } - - /** - * Get metadata about this object. - * @returns {MetadataProperty[]} metadata about this object - */ - MetadataCapability.prototype.invoke = function () { - var domainObject = this.domainObject, - model = domainObject.getModel(); - - function hasDisplayableValue(metadataProperty) { - var t = typeof metadataProperty.value; - - return (t === 'string' || t === 'number'); - } - - function formatTimestamp(timestamp) { - return typeof timestamp === 'number' - ? (moment.utc(timestamp).format(TIME_FORMAT) + " UTC") - : undefined; - } - - function getProperties() { - var type = domainObject.getCapability('type'); - - function lookupProperty(typeProperty) { - return { - name: typeProperty.getDefinition().name, - value: typeProperty.getValue(model) - }; - } - - return (type ? type.getProperties() : []).map(lookupProperty); - } - - function getCommonMetadata() { - var type = domainObject.getCapability('type'); - - // Note that invalid values will be filtered out later - return [ - { - name: "Updated", - value: formatTimestamp(model.modified) - }, - { - name: "Type", - value: type && type.getName() - } - ]; - } - - return getProperties().concat(getCommonMetadata()) - .filter(hasDisplayableValue); - }; - - return MetadataCapability; - } -); - diff --git a/platform/core/src/capabilities/MutationCapability.js b/platform/core/src/capabilities/MutationCapability.js deleted file mode 100644 index 62e82c27f0..0000000000 --- a/platform/core/src/capabilities/MutationCapability.js +++ /dev/null @@ -1,189 +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. - *****************************************************************************/ - -/** - * Module defining MutationCapability. Created by vwoeltje on 11/12/14. - */ -define( - [], - function () { - - var GENERAL_TOPIC = "mutation", - TOPIC_PREFIX = "mutation:"; - - // Utility function to overwrite a destination object - // with the contents of a source object. - function copyValues(destination, source) { - // First, remove all previously-existing keys - Object.keys(destination).forEach(function (k) { - delete destination[k]; - }); - // Second, write all new keys - Object.keys(source).forEach(function (k) { - destination[k] = source[k]; - }); - } - - // Utility function to cast to a promise, without waiting - // for nextTick if a value is non-promise-like. - function fastPromise(value) { - return (value || {}).then ? value : { - then: function (callback) { - return fastPromise(callback(value)); - } - }; - } - - /** - * The `mutation` capability allows a domain object's model to be - * modified. Wrapping such modifications in calls made through - * this capability allows these changes to be tracked (e.g. to - * ensure that a domain object's `modified` timestamp is kept - * up-to-date.) - * - * Usage: - * - * ``` - * domainObject.useCapability("mutation", function (model) { - * // make changes to model here... - * }); - * ``` - * - * @param {Function} topic a service for creating listeners - * @param {Function} now a service to get the current time - * @param {DomainObject} domainObject the domain object - * which will expose this capability - * @memberof platform/core - * @constructor - * @implements {Capability} - */ - function MutationCapability(topic, now, domainObject) { - this.generalMutationTopic = - topic(GENERAL_TOPIC); - this.specificMutationTopic = - topic(TOPIC_PREFIX + domainObject.getId()); - - this.now = now; - this.domainObject = domainObject; - } - - /** - * Modify the domain object's model, using a provided - * function. This function will receive a copy of the - * domain object's model as an argument; behavior - * varies depending on that function's return value: - * - * * If no value (or undefined) is returned by the mutator, - * the state of the model object delivered as the mutator's - * argument will become the domain object's new model. - * This is useful for writing code that modifies the model - * directly. - * * If a plain object is returned, that object will be used - * as the domain object's new model. - * * If boolean `false` is returned, the mutation will be - * cancelled. - * * If a promise is returned, its resolved value will be - * handled as one of the above. - * - * - * @param {Function} mutator the function which will make - * changes to the domain object's model. - * @param {number} [timestamp] timestamp to record for - * this mutation (otherwise, system time will be - * used) - * @returns {Promise.} a promise for the result - * of the mutation; true if changes were made. - */ - MutationCapability.prototype.mutate = function (mutator, timestamp) { - // Get the object's model and clone it, so the - // mutator function has a temporary copy to work with. - var domainObject = this.domainObject, - now = this.now, - generalTopic = this.generalMutationTopic, - specificTopic = this.specificMutationTopic, - model = domainObject.getModel(), - clone = JSON.parse(JSON.stringify(model)), - useTimestamp = arguments.length > 1; - - function notifyListeners(newModel) { - generalTopic.notify(domainObject); - specificTopic.notify(newModel); - } - - // Function to handle copying values to the actual - function handleMutation(mutationResult) { - // If mutation result was undefined, just use - // the clone; this allows the mutator to omit return - // values and just change the model directly. - var result = mutationResult || clone; - - // Allow mutators to change their mind by - // returning false. - if (mutationResult !== false) { - // Copy values if result was a different object - // (either our clone or some other new thing) - let modelHasChanged = _.isEqual(model, result) === false; - if (modelHasChanged) { - copyValues(model, result); - } - - if (modelHasChanged - || (useTimestamp !== undefined) - || (model.modified === undefined)) { - model.modified = useTimestamp ? timestamp : now(); - } - - notifyListeners(model); - } - - // Report the result of the mutation - return mutationResult !== false; - } - - // Invoke the provided mutator, then make changes to - // the underlying model (if applicable.) - return fastPromise(mutator(clone)).then(handleMutation); - }; - - /** - * Listen for mutations of this domain object's model. - * The provided listener will be invoked with the domain - * object's new model after any changes. To stop listening, - * invoke the function returned by this method. - * @param {Function} listener function to call on mutation - * @returns {Function} a function to stop listening - * @memberof platform/core.MutationCapability# - */ - MutationCapability.prototype.listen = function (listener) { - return this.specificMutationTopic.listen(listener); - }; - - /** - * Alias of `mutate`, used to support useCapability. - */ - MutationCapability.prototype.invoke = - MutationCapability.prototype.mutate; - - return MutationCapability; - } -); - diff --git a/platform/core/src/capabilities/PersistenceCapability.js b/platform/core/src/capabilities/PersistenceCapability.js deleted file mode 100644 index 64842a5958..0000000000 --- a/platform/core/src/capabilities/PersistenceCapability.js +++ /dev/null @@ -1,206 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define(["objectUtils"], - function (objectUtils) { - - /** - * Defines the `persistence` capability, used to trigger the - * writing of changes to a domain object to an underlying - * persistence store. - * - * @param {PersistenceService} persistenceService the underlying - * provider of persistence capabilities. - * @param {string} space the name of the persistence space to - * use (this is an arbitrary string, useful in principle - * for distinguishing different persistence stores from - * one another.) - * @param {DomainObject} the domain object which shall expose - * this capability - * - * @memberof platform/core - * @constructor - * @implements {Capability} - */ - function PersistenceCapability( - cacheService, - persistenceService, - identifierService, - notificationService, - $q, - openmct, - domainObject - ) { - // Cache modified timestamp - this.modified = domainObject.getModel().modified; - - this.domainObject = domainObject; - this.cacheService = cacheService; - this.identifierService = identifierService; - this.persistenceService = persistenceService; - this.notificationService = notificationService; - this.$q = $q; - this.openmct = openmct; - } - - /** - * Checks if the value returned is falsey, and if so returns a - * rejected promise - */ - function rejectIfFalsey(value, $q) { - if (!value) { - return Promise.reject("Error persisting object"); - } else { - return value; - } - } - - function formatError(error) { - if (error && error.message) { - return error.message; - } else if (error && typeof error === "string") { - return error; - } else { - return "unknown error"; - } - } - - /** - * Display a notification message if an error has occurred during - * persistence. - */ - function notifyOnError(error, domainObject, notificationService, $q) { - var errorMessage = "Unable to persist " + domainObject.getModel().name; - if (error) { - errorMessage += ": " + formatError(error); - } - - notificationService.error({ - title: "Error persisting " + domainObject.getModel().name, - hint: errorMessage, - dismissable: true - }); - - return Promise.reject(error); - } - - /** - * Persist any changes which have been made to this - * domain object's model. - * @returns {Promise} a promise which will be resolved - * if persistence is successful, and rejected - * if not. - */ - PersistenceCapability.prototype.persist = function () { - var self = this, - domainObject = this.domainObject; - - const identifier = { - namespace: this.getSpace(), - key: this.getKey() - }; - - let newStyleObject = objectUtils.toNewFormat(domainObject.getModel(), identifier); - - return this.openmct.objects - .save(newStyleObject) - .then(function (result) { - return rejectIfFalsey(result, self.$q); - }).catch(function (error) { - return notifyOnError(error, domainObject, self.notificationService, self.$q); - }); - }; - - /** - * Update this domain object to match the latest from - * persistence. - * @returns {Promise} a promise which will be resolved - * when the update is complete - */ - PersistenceCapability.prototype.refresh = function () { - var domainObject = this.domainObject; - var $q = this.$q; - - // Update a domain object's model upon refresh - function updateModel(model) { - if (model === undefined) { - //Get failed, reject promise - return $q.reject('Got empty object model'); - } else { - var modified = model.modified; - - return domainObject.useCapability("mutation", function () { - return model; - }, modified); - - } - } - - if (domainObject.getModel().persisted === undefined) { - return this.$q.when(true); - } - - return this.persistenceService.readObject( - this.getSpace(), - this.getKey() - ).then(updateModel); - }; - - /** - * Get the space in which this domain object is persisted; - * this is useful when, for example, decided which space a - * newly-created domain object should be persisted to (by - * default, this should be the space of its containing - * object.) - * - * @returns {string} the name of the space which should - * be used to persist this object - */ - PersistenceCapability.prototype.getSpace = function () { - var id = this.domainObject.getId(); - - return this.identifierService.parse(id).getSpace(); - }; - - /** - * Check if this domain object has been persisted at some - * point. - * @returns {boolean} true if the object has been persisted - */ - PersistenceCapability.prototype.persisted = function () { - return this.domainObject.getModel().persisted !== undefined; - }; - - /** - * Get the key for this domain object in the given space. - * - * @returns {string} the key of the object in it's space. - */ - PersistenceCapability.prototype.getKey = function () { - var id = this.domainObject.getId(); - - return this.identifierService.parse(id).getKey(); - }; - - return PersistenceCapability; - } -); diff --git a/platform/core/src/capabilities/RelationshipCapability.js b/platform/core/src/capabilities/RelationshipCapability.js deleted file mode 100644 index 4ab11e4c83..0000000000 --- a/platform/core/src/capabilities/RelationshipCapability.js +++ /dev/null @@ -1,128 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * Relationship capability. Describes a domain objects relationship - * to other domain objects within the system, and provides a way to - * access related objects. - * - * For most cases, this is not the capability to use; the - * `composition` capability describes the more general relationship - * between objects typically seen (e.g. in the tree.) This capability - * is instead intended for the more unusual case of relationships - * which are not intended to appear in the tree, but are instead - * intended only for special, limited usage. - * - * @memberof platform/core - * @constructor - * @implements {Capability} - */ - function RelationshipCapability($injector, domainObject) { - // Get a reference to the object service from $injector - this.injectObjectService = function () { - this.objectService = $injector.get("objectService"); - }; - - this.lastPromise = {}; - this.domainObject = domainObject; - } - - /** - * List all types of relationships exposed by this - * object. - * @returns {string[]} a list of all relationship types - */ - RelationshipCapability.prototype.listRelationships = function listRelationships() { - var relationships = - (this.domainObject.getModel() || {}).relationships || {}; - - // Check if this key really does expose an array of ids - // (to filter out malformed relationships) - function isArray(key) { - return Array.isArray(relationships[key]); - } - - return Object.keys(relationships).filter(isArray).sort(); - }; - - /** - * Request related objects, with a given relationship type. - * This will typically require asynchronous lookup, so this - * returns a promise. - * @param {string} key the type of relationship - * @returns {Promise.} a promise for related - * domain objects - */ - RelationshipCapability.prototype.getRelatedObjects = function (key) { - var model = this.domainObject.getModel(), - ids; - - // Package objects as an array - function packageObject(objects) { - return ids.map(function (id) { - return objects[id]; - }).filter(function (obj) { - return obj; - }); - } - - // Clear cached promises if modification has occurred - if (this.lastModified !== model.modified) { - this.lastPromise = {}; - this.lastModified = model.modified; - } - - // Make a new request if needed - if (!this.lastPromise[key]) { - ids = (model.relationships || {})[key] || []; - this.lastModified = model.modified; - // Lazily initialize object service now that we need it - if (!this.objectService) { - this.injectObjectService(); - } - - // Load from the underlying object service - this.lastPromise[key] = this.objectService.getObjects(ids) - .then(packageObject); - } - - return this.lastPromise[key]; - }; - - /** - * Test to determine whether or not this capability should be exposed - * by a domain object based on its model. Checks for the presence of - * a `relationships` field, that must be an object. - * @param model the domain object model - * @returns {boolean} true if this object has relationships - */ - RelationshipCapability.appliesTo = function (model) { - return Boolean((model || {}).relationships); - }; - - return RelationshipCapability; - } -); diff --git a/platform/core/src/identifiers/Identifier.js b/platform/core/src/identifiers/Identifier.js deleted file mode 100644 index 7f8065c3f3..0000000000 --- a/platform/core/src/identifiers/Identifier.js +++ /dev/null @@ -1,84 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - var SEPARATOR = ":"; - - /** - * Provides an interface for interpreting domain object identifiers; - * in particular, parses out persistence space/key pairs associated - * with the domain object. - * - * @memberof platform/core - * @constructor - * @param {string} id the domain object identifier - * @param {string} defaultSpace the persistence space to use if - * one is not encoded in the identifier - */ - function Identifier(id, defaultSpace) { - var separatorIndex = id.indexOf(SEPARATOR); - - if (separatorIndex > -1) { - this.key = id.substring(separatorIndex + 1); - this.space = id.substring(0, separatorIndex); - this.definedSpace = this.space; - } else { - this.key = id; - this.space = defaultSpace; - this.definedSpace = undefined; - } - } - - /** - * Get the key under which the identified domain object's model - * should be persisted, within its persistence space. - * @returns {string} the key within its persistence space - */ - Identifier.prototype.getKey = function () { - return this.key; - }; - - /** - * Get the space in which the identified domain object's model should - * be persisted. - * @returns {string} the persistence space - */ - Identifier.prototype.getSpace = function () { - return this.space; - }; - - /** - * Get the persistence space, if any, which has been explicitly - * encoded in this domain object's identifier. Returns undefined - * if no such space has been specified. - * @returns {string} the persistence space, or undefined - */ - Identifier.prototype.getDefinedSpace = function () { - return this.definedSpace; - }; - - return Identifier; - } -); diff --git a/platform/core/src/identifiers/IdentifierProvider.js b/platform/core/src/identifiers/IdentifierProvider.js deleted file mode 100644 index 82c20b2194..0000000000 --- a/platform/core/src/identifiers/IdentifierProvider.js +++ /dev/null @@ -1,65 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["uuid", "./Identifier"], - function (uuid, Identifier) { - - /** - * Parses and generates domain object identifiers. - * @param {string} defaultSpace the default persistence space - * @constructor - * @memberof {platform/core} - */ - function IdentifierProvider(defaultSpace) { - this.defaultSpace = defaultSpace; - } - - /** - * Generate a new domain object identifier. A persistence space - * may optionally be included; if not specified, no space will - * be encoded into the identifier. - * @param {string} [space] the persistence space to encode - * in this identifier - * @returns {string} a new domain object identifier - */ - IdentifierProvider.prototype.generate = function (space) { - var id = uuid(); - if (space !== undefined) { - id = space + ":" + id; - } - - return id; - }; - - /** - * Parse a domain object identifier to examine its component - * parts (e.g. its persistence space.) - * @returns {platform/core.Identifier} the parsed identifier - */ - IdentifierProvider.prototype.parse = function (id) { - return new Identifier(id, this.defaultSpace); - }; - - return IdentifierProvider; - } -); diff --git a/platform/core/src/models/CachingModelDecorator.js b/platform/core/src/models/CachingModelDecorator.js deleted file mode 100644 index 59f9435470..0000000000 --- a/platform/core/src/models/CachingModelDecorator.js +++ /dev/null @@ -1,65 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * The caching model decorator maintains a cache of loaded domain - * object models, and ensures that duplicate models for the same - * object are not provided. - * @memberof platform/core - * @constructor - * @param {ModelService} modelService this service to decorate - * @implements {ModelService} - */ - function CachingModelDecorator(cacheService, modelService) { - this.cacheService = cacheService; - this.modelService = modelService; - } - - CachingModelDecorator.prototype.getModels = function (ids) { - var loadFromCache = ids.filter(function cached(id) { - return this.cacheService.has(id); - }, this), - loadFromService = ids.filter(function notCached(id) { - return !this.cacheService.has(id); - }, this); - - if (!loadFromCache.length) { - return this.modelService.getModels(loadFromService); - } - - return this.modelService.getModels(loadFromService) - .then(function (modelResults) { - loadFromCache.forEach(function (id) { - modelResults[id] = this.cacheService.get(id); - }, this); - - return modelResults; - }.bind(this)); - }; - - return CachingModelDecorator; - } -); diff --git a/platform/core/src/models/ModelAggregator.js b/platform/core/src/models/ModelAggregator.js deleted file mode 100644 index aa1ce242d9..0000000000 --- a/platform/core/src/models/ModelAggregator.js +++ /dev/null @@ -1,99 +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. - *****************************************************************************/ - -/** - * Module defining ModelAggregator. Created by vwoeltje on 11/7/14. - */ -define( - [], - function () { - - /** - * Allow domain object models to be looked up by their identifiers. - * - * @interface ModelService - */ - - /** - * Get domain object models. - * - * This may provide either a superset or a subset of the models - * requested. Absence of a model means it does not exist within - * this service instance. - * - * @method ModelService#getModels - * @param {string[]} ids identifiers for models desired. - * @returns {Promise.} a promise for an object mapping - * string identifiers to domain object models. - */ - - /** - * Allows multiple services which provide models for domain objects - * to be treated as one. - * - * @memberof platform/core - * @constructor - * @implements {ModelService} - * @param $q Angular's $q, for promises - * @param {ModelService[]} providers the model providers to be - * aggregated - */ - function ModelAggregator($q, providers) { - this.providers = providers; - this.$q = $q; - } - - // Pick a domain object model to use, favoring the one - // with the most recent timestamp - function pick(a, b) { - var aModified = (a || {}).modified || Number.NEGATIVE_INFINITY, - bModified = (b || {}).modified || Number.NEGATIVE_INFINITY; - - return (aModified > bModified) ? a : (b || a); - } - - // Merge results from multiple providers into one - // large result object. - function mergeModels(provided, ids) { - var result = {}; - ids.forEach(function (id) { - provided.forEach(function (models) { - if (models[id]) { - result[id] = pick(result[id], models[id]); - } - }); - }); - - return result; - } - - ModelAggregator.prototype.getModels = function (ids) { - return this.$q.all(this.providers.map(function (provider) { - return provider.getModels(ids); - })).then(function (provided) { - return mergeModels(provided, ids); - }); - }; - - return ModelAggregator; - } -); diff --git a/platform/core/src/models/ModelCacheService.js b/platform/core/src/models/ModelCacheService.js deleted file mode 100644 index e2ca5e4e42..0000000000 --- a/platform/core/src/models/ModelCacheService.js +++ /dev/null @@ -1,85 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([], function () { - - /** - * Provides a cache for domain object models which exist in memory, - * but may or may not exist in backing persistence stores. - * @constructor - * @memberof platform/core - */ - function ModelCacheService() { - this.cache = {}; - } - - /** - * Put a domain object model in the cache. - * @param {string} id the domain object's identifier - * @param {object} model the domain object's model - */ - ModelCacheService.prototype.put = function (id, model) { - this.cache[id] = model; - }; - - /** - * Retrieve a domain object model from the cache. - * @param {string} id the domain object's identifier - * @returns {object} the domain object's model - */ - ModelCacheService.prototype.get = function (id) { - return this.cache[id]; - }; - - /** - * Check if a domain object model is in the cache. - * @param {string} id the domain object's identifier - * @returns {boolean} true if present; false if not - */ - ModelCacheService.prototype.has = function (id) { - return Object.prototype.hasOwnProperty.call(this.cache, id); - }; - - /** - * Remove a domain object model from the cache. - * @param {string} id the domain object's identifier - */ - ModelCacheService.prototype.remove = function (id) { - delete this.cache[id]; - }; - - /** - * Retrieve all cached domain object models. These are given - * as an object containing key-value pairs, where keys are - * domain object identifiers and values are domain object models. - * @returns {object} all domain object models - */ - ModelCacheService.prototype.all = function () { - return this.cache; - }; - - ModelCacheService.prototype.flush = function () { - this.cache = {}; - }; - - return ModelCacheService; -}); diff --git a/platform/core/src/models/PersistedModelProvider.js b/platform/core/src/models/PersistedModelProvider.js deleted file mode 100644 index 23f05371ac..0000000000 --- a/platform/core/src/models/PersistedModelProvider.js +++ /dev/null @@ -1,136 +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. - *****************************************************************************/ - -/** - * Module defining PersistedModelProvider. Created by vwoeltje on 11/12/14. - */ -define( - [], - function () { - - /** - * A model service which reads domain object models from an external - * persistence service. - * - * Identifiers will be interpreted as follows: - * * If no colon is present, the model will be read from the default - * persistence space. - * * If a colon is present, everything before the first colon will be - * taken to refer to the persistence space, and everything after - * will be taken to be that model's key within this space. (If - * no such space exists within the `persistenceService`, that - * identifier will simply be ignored.) - * - * @memberof platform/core - * @constructor - * @implements {ModelService} - * @param {PersistenceService} persistenceService the service in which - * domain object models are persisted. - * @param $q Angular's $q service, for working with promises - * @param {function} now a function which provides the current time - * @param {string} space the name of the persistence space(s) - * from which models should be retrieved by default - */ - function PersistedModelProvider(persistenceService, $q, now, space) { - this.persistenceService = persistenceService; - this.$q = $q; - this.now = now; - this.defaultSpace = space; - } - - PersistedModelProvider.prototype.getModels = function (ids) { - var persistenceService = this.persistenceService, - $q = this.$q, - now = this.now, - defaultSpace = this.defaultSpace, - parsedIds; - - // Load a single object model from any persistence spaces - function loadModel(parsedId) { - return persistenceService - .readObject(parsedId.space, parsedId.key); - } - - // Ensure that models read from persistence have some - // sensible timestamp indicating they've been persisted. - function addPersistedTimestamp(model) { - if (model && (model.persisted === undefined)) { - model.persisted = model.modified !== undefined - ? model.modified : now(); - } - - return model; - } - - // Package the result as id->model - function packageResult(parsedIdsToPackage, models) { - var result = {}; - parsedIdsToPackage.forEach(function (parsedId, index) { - var id = parsedId.id; - if (models[index]) { - result[id] = models[index]; - } - }); - - return result; - } - - function loadModels(parsedIdsToLoad) { - return $q.all(parsedIdsToLoad.map(loadModel)) - .then(function (models) { - return packageResult( - parsedIdsToLoad, - models.map(addPersistedTimestamp) - ); - }); - } - - function restrictToSpaces(spaces) { - return parsedIds.filter(function (parsedId) { - return spaces.indexOf(parsedId.space) !== -1; - }); - } - - parsedIds = ids.map(function (id) { - var parts = id.split(":"); - - return (parts.length > 1) - ? { - id: id, - space: parts[0], - key: parts.slice(1).join(":") - } - : { - id: id, - space: defaultSpace, - key: id - }; - }); - - return persistenceService.listSpaces() - .then(restrictToSpaces) - .then(loadModels); - }; - - return PersistedModelProvider; - } -); diff --git a/platform/core/src/models/StaticModelProvider.js b/platform/core/src/models/StaticModelProvider.js deleted file mode 100644 index 9da70ec52c..0000000000 --- a/platform/core/src/models/StaticModelProvider.js +++ /dev/null @@ -1,71 +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. - *****************************************************************************/ - -/** - * Module defining StaticModelProvider. Created by vwoeltje on 11/7/14. - */ -define( - [], - function () { - - /** - * Loads static models, provided as declared extensions of bundles. - * @memberof platform/core - * @constructor - */ - function StaticModelProvider(models, $q, $log) { - var modelMap = {}; - - function addModelToMap(model) { - // Skip models which don't look right - if (typeof model !== 'object' - || typeof model.id !== 'string' - || typeof model.model !== 'object') { - $log.warn([ - "Skipping malformed domain object model exposed by ", - ((model || {}).bundle || {}).path - ].join("")); - } else { - modelMap[model.id] = model.model; - } - } - - // Prepopulate maps with models to make subsequent lookup faster. - models.forEach(addModelToMap); - - this.modelMap = modelMap; - this.$q = $q; - } - - StaticModelProvider.prototype.getModels = function (ids) { - var modelMap = this.modelMap, - result = {}; - ids.forEach(function (id) { - result[id] = modelMap[id]; - }); - - return this.$q.when(result); - }; - - return StaticModelProvider; - } -); diff --git a/platform/core/src/objects/DomainObjectImpl.js b/platform/core/src/objects/DomainObjectImpl.js deleted file mode 100644 index 0b9fd0b79b..0000000000 --- a/platform/core/src/objects/DomainObjectImpl.js +++ /dev/null @@ -1,142 +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. - *****************************************************************************/ - -/** - * Module defining DomainObject. Created by vwoeltje on 11/7/14. - */ -define( - [], - function () { - - /** - * A domain object is an entity of interest to the user. - * - * @interface DomainObject - */ - - /** - * Get the unique identifier for this domain object. - * - * @method DomainObject#getId - * @return {string} the domain object's unique identifier - */ - - /** - * Get the domain object's model. This is useful to - * directly look up known properties of an object, but - * direct modification of a returned model is generally - * discouraged and may result in errors. Instead, an - * object's `mutation` capability should be used. - * - * @method DomainObject#getModel - * @return {object} the domain object's persistent state - */ - - /** - * Get a capability associated with this object. - * Capabilities are looked up by string identifiers; - * prior knowledge of a capability's interface is - * necessary. - * - * @method DomainObject#getCapability - * @param {string} key the identifier for the capability - * @return {Capability} the named capability, or undefined - * if not present. - */ - - /** - * Check if this domain object supports a capability - * with the provided name. - * - * @method DomainObject#hasCapability - * @param {string} key the identifier for the capability - * @return {boolean} true if this domain object has this capability - */ - - /** - * Use a capability of an object; the behavior of this method - * depends on the interface of the capability, and whether - * or not it is present. - * - * * If the capability is not present for this object, - * no operation occurs. - * * If the capability is present and has an `invoke` method, - * that method is called with any additional arguments - * provided, and its return value is returned. - * * If the capability is present but has no `invoke` method, - * this capability itself is returned. - * - * @method DomainObject#useCapability - * @param {string} name the name of the capability to invoke - * @param {...*} [arguments] to pass to the invocation - * @returns {*|Capability} the result of invocation (see description) - */ - - /** - * Construct a new domain object with the specified - * identifier, model, and capabilities. - * - * @param {string} id the object's unique identifier - * @param {object} model the "JSONifiable" state of the object - * @param {Object.|function} capabilities all - * capabilities to be exposed by this object - * @memberof platform/core - * @constructor - */ - function DomainObjectImpl(id, model, capabilities) { - this.id = id; - this.model = model; - this.capabilities = capabilities; - } - - DomainObjectImpl.prototype.getId = function () { - return this.id; - }; - - DomainObjectImpl.prototype.getModel = function () { - return this.model; - }; - - DomainObjectImpl.prototype.getCapability = function (name) { - var capability = this.capabilities[name]; - - return typeof capability === 'function' - ? capability(this) : capability; - }; - - DomainObjectImpl.prototype.hasCapability = function (name) { - return this.getCapability(name) !== undefined; - }; - - DomainObjectImpl.prototype.useCapability = function (name) { - // Get tail of args to pass to invoke - var args = Array.prototype.slice.apply(arguments, [1]), - capability = this.getCapability(name); - - return (capability && capability.invoke) - ? capability.invoke.apply(capability, args) - : capability; - }; - - return DomainObjectImpl; - } -); diff --git a/platform/core/src/objects/DomainObjectProvider.js b/platform/core/src/objects/DomainObjectProvider.js deleted file mode 100644 index 40d865a44e..0000000000 --- a/platform/core/src/objects/DomainObjectProvider.js +++ /dev/null @@ -1,93 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * This bundle implements core components of Open MCT's service - * infrastructure and information model. - * @namespace platform/core - */ -define( - [], - function () { - - /** - * Provides instances of domain objects, as retrieved by their - * identifiers. - * - * @interface ObjectService - */ - - /** - * Get a set of objects associated with a list of identifiers. - * The provided result may contain a subset or a superset of - * the total number of objects. - * - * @method ObjectService#getObjects - * @param {string[]} ids the identifiers for domain objects - * of interest. - * @return {Promise>} a promise - * for an object containing key-value pairs, where keys - * are string identifiers for domain objects, and - * values are the corresponding domain objects themselves. - */ - - /** - * Construct a new provider for domain objects. - * - * @param {ModelService} modelService the service which shall - * provide models (persistent state) for domain objects - * @param {Function} instantiate a service to instantiate new - * domain object instances - * @param $q Angular's $q, for promise consolidation - * @memberof platform/core - * @constructor - */ - function DomainObjectProvider(modelService, instantiate) { - this.modelService = modelService; - this.instantiate = instantiate; - } - - DomainObjectProvider.prototype.getObjects = function getObjects(ids) { - var modelService = this.modelService, - instantiate = this.instantiate; - - // Assemble the results from the model service and the - // capability service into one value, suitable to return - // from this service. - function assembleResult(models) { - var result = {}; - ids.forEach(function (id) { - if (models[id]) { - // Create the domain object - result[id] = instantiate(models[id], id); - } - }); - - return result; - } - - return modelService.getModels(ids).then(assembleResult); - }; - - return DomainObjectProvider; - } -); diff --git a/platform/core/src/services/Instantiate.js b/platform/core/src/services/Instantiate.js deleted file mode 100644 index c32f74e89f..0000000000 --- a/platform/core/src/services/Instantiate.js +++ /dev/null @@ -1,61 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['../objects/DomainObjectImpl'], - function (DomainObjectImpl) { - - /** - * The `instantiate` service allows new domain object instances to be - * created. These objects are not persisted to any back-end or - * placed anywhere in the object hierarchy by default. - * - * Usage: `instantiate(model, [id])` - * - * ...returns a new instance of a domain object with the specified - * model. An identifier may be provided; if omitted, one will be - * generated instead. - * - * @constructor - * @memberof platform/core - * @param {CapabilityService} capabilityService the service which will - * provide instantiated domain objects with their capabilities - * @param {IdentifierService} identifierService service to generate - * new identifiers - */ - function Instantiate( - capabilityService, - identifierService, - cacheService - ) { - return function (model, id) { - var capabilities = capabilityService.getCapabilities(model); - id = id || identifierService.generate(); - cacheService.put(id, model); - - return new DomainObjectImpl(id, model, capabilities); - }; - } - - return Instantiate; - } -); diff --git a/platform/core/src/services/Now.js b/platform/core/src/services/Now.js deleted file mode 100644 index d3cd6e87a6..0000000000 --- a/platform/core/src/services/Now.js +++ /dev/null @@ -1,48 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * Defines the `now` service, which is a simple wrapper upon - * `Date.now()` which can be injected to support testability. - * - * @returns {Function} a function which returns current system time - * @memberof platform/core - */ - function Now() { - /** - * Get the current time. - * @returns {number} current time, in milliseconds since - * 1970-01-01 00:00:00Z - * @memberof platform/core.Now# - */ - return function () { - return Date.now(); - }; - } - - return Now; - } -); diff --git a/platform/core/src/services/Throttle.js b/platform/core/src/services/Throttle.js deleted file mode 100644 index 22d28b785b..0000000000 --- a/platform/core/src/services/Throttle.js +++ /dev/null @@ -1,93 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * Throttler for function executions, registered as the `throttle` - * service. - * - * Usage: - * - * throttle(fn, delay, [apply]) - * - * Returns a function that, when invoked, will invoke `fn` after - * `delay` milliseconds, only if no other invocations are pending. - * The optional argument `apply` determines whether or not a - * digest cycle should be triggered. - * - * The returned function will itself return a `Promise` which will - * resolve to the returned value of `fn` whenever that is invoked. - * - * In cases where arguments are provided, only the most recent - * set of arguments will be passed on to the throttled function - * at the time it is executed. - * - * @returns {Function} - * @memberof platform/core - */ - function Throttle($timeout) { - /** - * Throttle this function. - * @param {Function} fn the function to throttle - * @param {number} [delay] the delay, in milliseconds, before - * executing this function; defaults to 0. - * @param {boolean} apply true if a `$apply` call should be - * invoked after this function executes; defaults to - * `false`. - * @memberof platform/core.Throttle# - */ - return function (fn, delay, apply) { - var promise, - args = []; - - function invoke() { - // Clear the active timeout so a new one starts next time. - promise = undefined; - - // Invoke the function with the latest supplied arguments. - return fn.apply(null, args); - } - - // Defaults - delay = delay || 0; - apply = apply || false; - - return function () { - // Store arguments from this invocation - args = Array.prototype.slice.apply(arguments, [0]); - // Start a timeout if needed - promise = promise || $timeout(invoke, delay, apply); - - // Return whichever timeout is active (to get - // a promise for the results of fn) - return promise; - }; - }; - } - - return Throttle; - } -); - diff --git a/platform/core/src/services/Topic.js b/platform/core/src/services/Topic.js deleted file mode 100644 index 9201073452..0000000000 --- a/platform/core/src/services/Topic.js +++ /dev/null @@ -1,97 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - var ERROR_PREFIX = "Error when notifying listener: "; - - /** - * The `topic` service provides a way to create both named, - * shared listeners and anonymous, private listeners. - * - * Usage: - * - * ``` - * var t = topic('foo'); // Use/create a named topic - * t.listen(function () { ... }); - * t.notify({ some: "message" }); - * ``` - * - * Named topics are shared; multiple calls to `topic` - * with the same argument will return a single object instance. - * Anonymous topics (where `topic` has been called with no - * arguments) are private; each call returns a new instance. - * - * @returns {Function} - * @memberof platform/core - */ - function Topic($log) { - var topics = {}; - - function createTopic() { - var listeners = []; - - return { - listen: function (listener) { - listeners.push(listener); - - return function unlisten() { - listeners = listeners.filter(function (l) { - return l !== listener; - }); - }; - }, - notify: function (message) { - listeners.forEach(function (listener) { - try { - listener(message); - } catch (e) { - $log.error(ERROR_PREFIX + e.message); - $log.error(e); - } - }); - } - }; - } - - /** - * Use and (if necessary) create a new topic. - * @param {string} [key] name of the topic to use - * @memberof platform/core.Topic# - */ - return function (key) { - if (arguments.length < 1) { - return createTopic(); - } else { - topics[key] = topics[key] || createTopic(); - - return topics[key]; - } - }; - } - - return Topic; - } -); - diff --git a/platform/core/src/types/MergeModels.js b/platform/core/src/types/MergeModels.js deleted file mode 100644 index 4df2009e85..0000000000 --- a/platform/core/src/types/MergeModels.js +++ /dev/null @@ -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. - *****************************************************************************/ - -/** - * Defines MergedModel, which allows a deep merge of domain object - * models, or JSONifiable JavaScript objects generally. - * - */ -define( - function () { - - /** - * Utility function for merging domain object models (or any - * JavaScript object which follows the same conventions.) - * Performs a "deep merge", resolving conflicts (occurrences - * of the same property in both objects) such that: - * - * * Non-conflicting properties are both contained in the - * result object. - * * Conflicting properties which are both arrays are - * concatenated. - * * Conflicting properties which are both objects are - * merged recursively. - * * Conflicting properties which do not fall into any of the - * preceding categories are taken from the second argument, - * shadowing any values from the first. - * - * An optional third argument, the "merger", may be provided. - * This may be either a function, or an object containing - * key-value pairs where keys are strings (corresponding to - * the names of properties) and values are other mergers - * (either functions or objects.) - * - * * If the merger is a function, it will be used upon the - * two input objects in lieu of the behavior described - * above. - * * If the merger is an object, then its values will be - * used as mergers when resolving properties with - * corresponding keys in the recursive step. - * - * - * @param modelA the first object to be merged - * @param modelB the second object to be merged - * @param merger the merger, as described above - * @returns {*} the result of merging `modelA` and `modelB` - * @constructor - * @memberof platform/core - */ - function mergeModels(modelA, modelB, merger) { - var mergeFunction; - - function mergeArrays(a, b) { - return a.concat(b); - } - - function mergeObjects(a, b) { - var result = {}; - Object.keys(a).forEach(function (k) { - result[k] = Object.prototype.hasOwnProperty.call(b, k) - ? mergeModels(a[k], b[k], (merger || {})[k]) - : a[k]; - }); - Object.keys(b).forEach(function (k) { - // Copy any properties not already merged - if (!Object.prototype.hasOwnProperty.call(a, k)) { - result[k] = b[k]; - } - }); - - return result; - } - - function mergeOther(a, b) { - return b; - } - - mergeFunction = (merger && Function.isFunction(merger)) ? merger - : (Array.isArray(modelA) && Array.isArray(modelB)) ? mergeArrays - : (modelA instanceof Object && modelB instanceof Object) ? mergeObjects - : mergeOther; - - return mergeFunction(modelA, modelB); - } - - return mergeModels; - } -); diff --git a/platform/core/src/types/TypeCapability.js b/platform/core/src/types/TypeCapability.js deleted file mode 100644 index 99ded1a89f..0000000000 --- a/platform/core/src/types/TypeCapability.js +++ /dev/null @@ -1,55 +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. - *****************************************************************************/ - -/** - * Module defining TypeCapability. Created by vwoeltje on 11/10/14. - */ -define( - [], - function () { - - /** - * The `type` capability makes information about a domain object's - * type directly available when working with that object, by way - * of a `domainObject.getCapability('type')` invocation. - * - * @memberof platform/core - * @constructor - * @augments {Type} - * @implements {Capability} - * @param {TypeService} typeService the service which - * provides type information - * @param {DomainObject} domainObject the domain object - * which exposes the type capability - */ - function TypeCapability(typeService, domainObject) { - var typeKey = domainObject.getModel().type, - type = typeService.getType(typeKey); - - // Simply return the type, but wrap with Object.create - // to avoid exposing the type object directly. - return Object.create(type); - } - - return TypeCapability; - } -); diff --git a/platform/core/src/types/TypeImpl.js b/platform/core/src/types/TypeImpl.js deleted file mode 100644 index 4c2d9ed3d8..0000000000 --- a/platform/core/src/types/TypeImpl.js +++ /dev/null @@ -1,194 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['./TypeProperty'], - function (TypeProperty) { - - /** - * Describes a type of domain object. - * - * @interface Type - */ - - /** - * Get the string key which identifies this type. - * This is the type's machine-readable name/identifier, - * and will correspond to the "type" field of the models - * of domain objects of this type. - * - * @returns {string} the key which identifies this type - * @method Type#getKey - */ - /** - * Get the human-readable name for this type, as should - * be displayed in the user interface when referencing - * this type. - * - * @returns {string} the human-readable name of this type - * @method Type#getName - */ - /** - * Get the human-readable description for this type, as should - * be displayed in the user interface when describing - * this type. - * - * @returns {string} the human-readable description of this type - * @method Type#getDescription - */ - /** - * Get the cssClass associated with this type. cssClass is a - * string which will appear as an icon (when - * displayed in an appropriate font) which visually - * distinguish types from one another. - * - * @returns {string} the cssClass for this type - * @method Type#getCssClass - */ - /** - * Get an array of properties associated with objects of - * this type, as might be shown in a Create wizard or - * an Edit Properties view. - * - * @return {TypeProperty[]} properties associated with - * objects of this type - * @method Type#getPropertiees - */ - /** - * Get the initial state of a model for domain objects of - * this type. - * - * @return {object} initial domain object model - * @method Type#getInitialModel - */ - /** - * Get the raw type definition for this type. This is an - * object containing key-value pairs of type metadata; - * this allows the retrieval and use of custom type - * properties which are not recognized within this interface. - * - * @returns {object} the raw definition for this type - * @method Type#getDefinition - */ - /** - * Check if this type is or inherits from some other type. - * - * @param {string|Type} key either - * a string key for a type, or an instance of a type - * object, which this - * @returns {boolean} true - * @method Type#instanceOf - */ - /** - * Check if a type should support a given feature. This simply - * checks for the presence or absence of the feature key in - * the type definition's "feature" field. - * @param {string} feature a string identifying the feature - * @returns {boolean} true if the feature is supported - * @method Type#hasFeature - */ - - /** - * Construct a new type. Types describe categories of - * domain objects. - * - * @implements {Type} - * @param {TypeDefinition} typeDef an object containing - * key-value pairs describing a type and its - * relationship to other types. - * @constructor - * @memberof platform/core - */ - function TypeImpl(typeDef) { - var inheritList = typeDef.inherits || [], - featureSet = {}; - - (typeDef.features || []).forEach(function (feature) { - featureSet[feature] = true; - }); - - this.typeDef = typeDef; - this.featureSet = featureSet; - this.inheritList = inheritList; - } - - TypeImpl.prototype.getKey = function () { - return this.typeDef.key; - }; - - TypeImpl.prototype.getName = function () { - return this.typeDef.name; - }; - - TypeImpl.prototype.getDescription = function () { - return this.typeDef.description; - }; - - TypeImpl.prototype.getCssClass = function () { - return this.typeDef.cssClass; - }; - - TypeImpl.prototype.getProperties = function () { - return (this.typeDef.properties || []).map(function (propertyDef) { - return new TypeProperty(propertyDef); - }); - }; - - /** - * Returns the default model for an object of this type. Note that - * this method returns a clone of the original model, so if using this - * method heavily, consider caching the result to optimize performance. - * - * @return {object} The default model for an object of this type. - */ - TypeImpl.prototype.getInitialModel = function () { - return JSON.parse(JSON.stringify(this.typeDef.model || {})); - }; - - TypeImpl.prototype.getDefinition = function () { - return this.typeDef; - }; - - TypeImpl.prototype.instanceOf = function instanceOf(key) { - var typeDef = this.typeDef, - inheritList = this.inheritList; - - if (key === typeDef.key) { - return true; - } else if (inheritList.indexOf(key) > -1) { - return true; - } else if (!key) { - return true; - } else if (key !== null && typeof key === 'object') { - return key.getKey ? this.instanceOf(key.getKey()) : false; - } else { - return false; - } - }; - - TypeImpl.prototype.hasFeature = function (feature) { - return this.featureSet[feature] || false; - }; - - return TypeImpl; - } -); diff --git a/platform/core/src/types/TypeProperty.js b/platform/core/src/types/TypeProperty.js deleted file mode 100644 index 1abc82acfb..0000000000 --- a/platform/core/src/types/TypeProperty.js +++ /dev/null @@ -1,163 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['./TypePropertyConversion'], - function (TypePropertyConversion) { - - /** - * Instantiate a property associated with domain objects of a - * given type. This provides an interface by which - * - * @memberof platform/core - * @constructor - */ - function TypeProperty(propertyDefinition) { - // Load an appropriate conversion - this.conversion = new TypePropertyConversion( - propertyDefinition.conversion || "identity" - ); - this.propertyDefinition = propertyDefinition; - } - - // Check if a value is defined; used to check if initial array - // values have been populated. - function isUnpopulatedArray(value) { - var i; - - if (!Array.isArray(value) || value.length === 0) { - return false; - } - - for (i = 0; i < value.length; i += 1) { - if (value[i] !== undefined) { - return false; - } - } - - return true; - } - - // Specify a field deeply within an object - function specifyValue(object, propertyPath, value) { - // If path is not an array, just set the property - if (!Array.isArray(propertyPath)) { - object[propertyPath] = value; - } else if (propertyPath.length > 1) { - // Otherwise, look up in defined sequence - object[propertyPath[0]] = object[propertyPath[0]] || {}; - specifyValue( - object[propertyPath[0]], - propertyPath.slice(1), - value - ); - } else if (propertyPath.length === 1) { - object[propertyPath[0]] = value; - } - } - - // Perform a lookup for a value from an object, - // which may recursively look at contained objects - // based on the path provided. - function lookupValue(object, propertyPath) { - var value; - - // Can't look up from a non-object - if (!object) { - return undefined; - } - - // If path is not an array, just look up the property - if (!Array.isArray(propertyPath)) { - return object[propertyPath]; - } - - // Otherwise, look up in the sequence defined in the array - if (propertyPath.length > 0) { - value = object[propertyPath[0]]; - - return propertyPath.length > 1 - ? lookupValue(value, propertyPath.slice(1)) - : value; - } - - // Fallback; property path was empty - return undefined; - } - - /** - * Retrieve the value associated with this property - * from a given model. - * @param {object} model a domain object model to read from - * @returns {*} the value for this property, as read from the model - */ - TypeProperty.prototype.getValue = function (model) { - var property = this.propertyDefinition.property - || this.propertyDefinition.key, - initialValue = - property && lookupValue(model, property); - - // Provide an empty array if this is a multi-item - // property. - if (Array.isArray(this.propertyDefinition.items)) { - initialValue = initialValue - || new Array(this.propertyDefinition.items.length); - } - - return this.conversion.toFormValue(initialValue); - }; - - /** - * Set a value associated with this property in - * an object's model. - * @param {object} model a domain object model to update - * @param {*} value the new value to set for this property - */ - TypeProperty.prototype.setValue = function (model, value) { - var property = this.propertyDefinition.property - || this.propertyDefinition.key; - - // If an array contains all undefined values, treat it - // as undefined, to filter back out arrays for input - // that never got entered. - value = isUnpopulatedArray(value) ? undefined : value; - - // Convert to a value suitable for storage in the - // domain object's model - value = this.conversion.toModelValue(value); - - return property - ? specifyValue(model, property, value) - : undefined; - }; - - /** - * Get the raw definition for this property. - * @returns {TypePropertyDefinition} - */ - TypeProperty.prototype.getDefinition = function () { - return this.propertyDefinition; - }; - - return TypeProperty; - } -); diff --git a/platform/core/src/types/TypePropertyConversion.js b/platform/core/src/types/TypePropertyConversion.js deleted file mode 100644 index 27e74e4b71..0000000000 --- a/platform/core/src/types/TypePropertyConversion.js +++ /dev/null @@ -1,99 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - function () { - - var conversions = { - number: { - toModelValue: parseFloat, - toFormValue: function (modelValue) { - return (typeof modelValue === 'number') - ? modelValue.toString(10) : undefined; - } - }, - identity: { - toModelValue: function (v) { - return v; - }, - toFormValue: function (v) { - return v; - } - } - }, - ARRAY_SUFFIX = '[]'; - - // Utility function to handle arrays of conversions - function ArrayConversion(conversion) { - return { - toModelValue: function (formValue) { - return formValue && formValue.map(conversion.toModelValue); - }, - toFormValue: function (modelValue) { - return modelValue && modelValue.map(conversion.toFormValue); - } - }; - } - - /** - * Look up an appropriate conversion between form values and model - * values, e.g. to numeric values. - * @constructor - * @memberof platform/core - */ - function TypePropertyConversion(name) { - if (name - && name.length > ARRAY_SUFFIX.length - && name.indexOf(ARRAY_SUFFIX, name.length - ARRAY_SUFFIX.length) !== -1) { - return new ArrayConversion( - new TypePropertyConversion( - name.substring(0, name.length - ARRAY_SUFFIX.length) - ) - ); - } else { - if (!conversions[name]) { - throw new Error("Unknown conversion type: " + name); - } - - return conversions[name]; - } - } - - /** - * Convert a value from its format as read from a form, to a - * format appropriate to store in a model. - * @method platform/core.TypePropertyConversion#toModelValue - * @param {*} formValue value as read from a form - * @returns {*} value to store in a model - */ - - /** - * Convert a value from its format as stored in a model, to a - * format appropriate to display in a form. - * @method platform/core.TypePropertyConversion#toFormValue - * @param {*} modelValue value as stored in a model - * @returns {*} value to display within a form - */ - - return TypePropertyConversion; - } -); diff --git a/platform/core/src/types/TypeProvider.js b/platform/core/src/types/TypeProvider.js deleted file mode 100644 index ecf4f550a1..0000000000 --- a/platform/core/src/types/TypeProvider.js +++ /dev/null @@ -1,197 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['./TypeImpl', './MergeModels'], - function (TypeImpl, mergeModels) { - - /** - * Provides domain object types that are available/recognized within - * the system. - * - * @interface TypeService - */ - /** - * Get a specific type by name. - * - * @method TypeService#getType - * @param {string} key the key (machine-readable identifier) - * for the type of interest - * @returns {Type} the type identified by this key - */ - /** - * List all known types. - * - * @method TypeService#listTypes - * @returns {Type[]} all known types - */ - - var TO_CONCAT = ['inherits', 'capabilities', 'properties', 'features'], - TO_MERGE = ['model']; - - function copyKeys(a, b) { - Object.keys(b).forEach(function (k) { - a[k] = b[k]; - }); - } - - function removeDuplicates(array) { - var set = {}; - - return array ? array.filter(function (element) { - // Don't filter objects (e.g. property definitions) - if (element instanceof Object && !(element instanceof String)) { - return true; - } - - return set[element] - ? false - : (set[element] = true); - }) : array; - } - - // Reduce an array of type definitions to a single type definition, - // which has merged all properties in order. - function collapse(typeDefs) { - var collapsed = typeDefs.reduce(function (a, b) { - var result = {}; - copyKeys(result, a); - copyKeys(result, b); - - // Special case: Do a merge, e.g. on "model" - TO_MERGE.forEach(function (k) { - if (a[k] && b[k]) { - result[k] = mergeModels(a[k], b[k]); - } - }); - - // Special case: Concatenate certain arrays - TO_CONCAT.forEach(function (k) { - if (a[k] || b[k]) { - result[k] = (a[k] || []).concat(b[k] || []); - } - }); - - return result; - }, {}); - - // Remove any duplicates from the collapsed array - TO_CONCAT.forEach(function (k) { - if (collapsed[k]) { - collapsed[k] = removeDuplicates(collapsed[k]); - } - }); - - return collapsed; - } - - /** - * A type provider provides information about types of domain objects - * within the running Open MCT instance. - * - * @param {Array} types the raw type - * definitions for this type. - * @memberof platform/core - * @constructor - */ - function TypeProvider(types) { - var rawTypeDefinitions = types, - typeDefinitions = (function (typeDefArray) { - var result = {}; - typeDefArray.forEach(function (typeDef) { - var k = typeDef.key; - if (k) { - result[k] = (result[k] || []).concat(typeDef); - } - }); - - return result; - }(rawTypeDefinitions)); - - this.typeMap = {}; - this.typeDefinitions = typeDefinitions; - this.rawTypeDefinitions = types; - } - - TypeProvider.prototype.listTypes = function () { - var self = this; - - return removeDuplicates( - this.rawTypeDefinitions.filter(function (def) { - return def.key; - }).map(function (def) { - return def.key; - }).map(function (key) { - return self.getType(key); - }) - ); - }; - - TypeProvider.prototype.getType = function (key) { - var typeDefinitions = this.typeDefinitions, - self = this; - - function getUndefinedType() { - return (self.undefinedType = self.undefinedType || collapse( - self.rawTypeDefinitions.filter(function (typeDef) { - return !typeDef.key; - }) - )); - } - - function asArray(value) { - return Array.isArray(value) ? value : [value]; - } - - function lookupTypeDef(typeKey) { - function buildTypeDef(typeKeyToBuild) { - var typeDefs = typeDefinitions[typeKeyToBuild] || [], - inherits = typeDefs.map(function (typeDef) { - return asArray(typeDef.inherits || []); - }).reduce(function (a, b) { - return a.concat(b); - }, []), - def = collapse( - [getUndefinedType()].concat( - inherits.map(lookupTypeDef) - ).concat(typeDefs) - ); - - // Always provide a default name - def.model = def.model || {}; - def.model.name = def.model.name - || ("Unnamed " + (def.name || "Object")); - - return def; - } - - return (self.typeMap[typeKey] = - self.typeMap[typeKey] || buildTypeDef(typeKey)); - } - - return new TypeImpl(lookupTypeDef(key)); - }; - - return TypeProvider; - } - -); diff --git a/platform/core/src/views/ViewCapability.js b/platform/core/src/views/ViewCapability.js deleted file mode 100644 index b80f5cbed3..0000000000 --- a/platform/core/src/views/ViewCapability.js +++ /dev/null @@ -1,58 +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. - *****************************************************************************/ - -/** - * Module defining ViewCapability. Created by vwoeltje on 11/10/14. - */ -define( - [], - function () { - - /** - * A `view` capability can be used to retrieve an array of - * all views (or, more specifically, the declarative metadata - * thereabout) which are applicable to a specific domain - * object. - * - * @memberof platform/core - * @implements {Capability} - * @constructor - */ - function ViewCapability(viewService, domainObject) { - this.viewService = viewService; - this.domainObject = domainObject; - } - - /** - * Get all view definitions which are applicable to - * this object. - * @returns {View[]} an array of view definitions - * which are applicable to this object. - * @memberof platform/core.ViewCapability# - */ - ViewCapability.prototype.invoke = function () { - return this.viewService.getViews(this.domainObject); - }; - - return ViewCapability; - } -); diff --git a/platform/core/src/views/ViewProvider.js b/platform/core/src/views/ViewProvider.js deleted file mode 100644 index c8c5cf5568..0000000000 --- a/platform/core/src/views/ViewProvider.js +++ /dev/null @@ -1,161 +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. - *****************************************************************************/ - -/** - * Module defining ViewProvider. Created by vwoeltje on 11/10/14. - */ -define( - [], - function () { - - /** - * Provides definitions for views that are available for specific - * domain objects. - * - * @interface ViewService - */ - - /** - * Get all views which are applicable to this domain object. - * - * @method ViewService#getViews - * @param {DomainObject} domainObject the domain object to view - * @returns {View[]} all views which can be used to visualize - * this domain object. - */ - - /** - * A view provider allows view definitions (defined as extensions) - * to be read, and takes responsibility for filtering these down - * to a set that is applicable to specific domain objects. This - * filtering is parameterized by the extension definitions - * themselves; specifically: - * - * * Definitions with a `needs` property containing an array of - * strings are only applicable to domain objects which have - * all those capabilities. - * * If the view definition has a `delegation` property that - * is truthy, then domain objects which delegate capabilities - * from the `needs` property will be treated as having those - * capabilities for purposes of determining view applicability. - * * Definitions with a `type` property are only applicable to - * domain object's whose `type` capability matches or inherits - * from that type. - * - * Views themselves are primarily metadata, such as name, icon and - * description (to be shown in the UI); they do not contain any - * information directly applicable to rendering to the DOM, although - * they do contain sufficient information (such as a `templateUrl`, - * used in the representation bundle) to retrieve those details. - * The role of a view provider and of a view capability is to - * describe what views are available, not how to instantiate them. - * - * @memberof platform/core - * @constructor - * @param {View[]} an array of view definitions - * @param $log Angular's logging service - * @implements {ViewService} - */ - function ViewProvider(views, $log) { - - // Views without defined keys cannot be used in the user - // interface, and can result in unexpected behavior. These - // are filtered out using this function. - function validate(view) { - var key = view.key; - - // Leave a log message to support detection of this issue. - if (!key) { - $log.warn([ - "No key specified for view in ", - (view.bundle || {}).path, - "; omitting this view." - ].join("")); - } - - return key; - } - - // Filter out any key-less views - this.views = views.filter(validate); - } - - ViewProvider.prototype.getViews = function (domainObject) { - var type = domainObject.useCapability("type"); - - // Check if an object has all capabilities designated as `needs` - // for a view. Exposing a capability via delegation is taken to - // satisfy this filter if `allowDelegation` is true. - function capabilitiesMatch(domainObj, capabilities, allowDelegation) { - var delegation = domainObj.getCapability("delegation"); - - allowDelegation = allowDelegation && (delegation !== undefined); - - // Check if an object has (or delegates, if allowed) a - // capability. - function hasCapability(c) { - return domainObj.hasCapability(c) - || (allowDelegation && delegation.doesDelegateCapability(c)); - } - - // For the reduce step below. - function and(a, b) { - return a && b; - } - - // Do a bulk `and` operation over all needed capabilities. - return capabilities.map(hasCapability).reduce(and, true); - } - - // Check if a view and domain object type can be paired; - // both can restrict the others they accept. - function viewMatchesType(view, objType) { - var views = objType && (objType.getDefinition() || {}).views, - matches = true; - - // View is restricted to a certain type - if (view.type) { - matches = matches && objType && objType.instanceOf(view.type); - } - - // Type wishes to restrict its specific views - if (Array.isArray(views)) { - matches = matches && (views.indexOf(view.key) > -1); - } - - return matches; - } - - // First, filter views by type (matched to domain object type.) - // Second, filter by matching capabilities. - return this.views.filter(function (view) { - return viewMatchesType(view, type) && capabilitiesMatch( - domainObject, - view.needs || [], - view.delegation || false - ); - }); - }; - - return ViewProvider; - } -); diff --git a/platform/core/test/actions/ActionAggregatorSpec.js b/platform/core/test/actions/ActionAggregatorSpec.js deleted file mode 100644 index 3719686024..0000000000 --- a/platform/core/test/actions/ActionAggregatorSpec.js +++ /dev/null @@ -1,73 +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. - *****************************************************************************/ - -/** - * ActionAggregatorSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/actions/ActionAggregator"], - function (ActionAggregator) { - - describe("Action aggregator", function () { - var mockAggregators, - aggregator; - - function createMockActionProvider(actions, i) { - var spy = jasmine.createSpyObj("agg" + i, ["getActions"]); - spy.getActions.and.returnValue(actions); - - return spy; - } - - beforeEach(function () { - mockAggregators = [ - ["a", "b"], - ["c"], - ["d", "e", "f"] - ].map(createMockActionProvider); - aggregator = new ActionAggregator(mockAggregators); - }); - - it("consolidates results from aggregated services", function () { - expect(aggregator.getActions()).toEqual( - ["a", "b", "c", "d", "e", "f"] - ); - }); - - it("passes context along to all aggregated services", function () { - var context = { domainObject: "something" }; - - // Verify precondition - mockAggregators.forEach(function (mockAgg) { - expect(mockAgg.getActions).not.toHaveBeenCalled(); - }); - - aggregator.getActions(context); - - // All services should have been called with this context - mockAggregators.forEach(function (mockAgg) { - expect(mockAgg.getActions).toHaveBeenCalledWith(context); - }); - }); - }); - } -); diff --git a/platform/core/test/actions/ActionCapabilitySpec.js b/platform/core/test/actions/ActionCapabilitySpec.js deleted file mode 100644 index 2d7b16e991..0000000000 --- a/platform/core/test/actions/ActionCapabilitySpec.js +++ /dev/null @@ -1,96 +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. - *****************************************************************************/ - -/** - * ActionCapabilitySpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/actions/ActionCapability"], - function (ActionCapability) { - - describe("The action capability", function () { - var mockQ, - mockAction, - mockActionService, - mockDomainObject, - capability; - - beforeEach(function () { - mockAction = jasmine.createSpyObj( - "action", - ["perform", "getMetadata"] - ); - mockActionService = jasmine.createSpyObj( - "actionService", - ["getActions"] - ); - mockQ = jasmine.createSpyObj( - "$q", - ["when"] - ); - mockDomainObject = jasmine.createSpyObj( - "domainObject", - ["getId", "getModel", "getCapability", "hasCapability", "useCapability"] - ); - - mockActionService.getActions.and.returnValue([mockAction, {}]); - - capability = new ActionCapability( - mockQ, - mockActionService, - mockDomainObject - ); - }); - - it("retrieves action for domain objects from the action service", function () { - // Verify precondition - expect(mockActionService.getActions).not.toHaveBeenCalled(); - - // Call getActions - expect(capability.getActions("some key")).toEqual([mockAction, {}]); - - // Verify interaction - expect(mockActionService.getActions).toHaveBeenCalledWith({ - key: "some key", - domainObject: mockDomainObject - }); - }); - - it("promises the result of performed actions", function () { - var mockPromise = jasmine.createSpyObj("promise", ["then"]); - mockQ.when.and.returnValue(mockPromise); - mockAction.perform.and.returnValue("the action's result"); - - // Verify precondition - expect(mockAction.perform).not.toHaveBeenCalled(); - - // Perform via capability - expect(capability.perform()).toEqual(mockPromise); - - // Verify that the action's result is what was wrapped - expect(mockQ.when).toHaveBeenCalledWith("the action's result"); - - }); - - }); - } -); diff --git a/platform/core/test/actions/ActionProviderSpec.js b/platform/core/test/actions/ActionProviderSpec.js deleted file mode 100644 index bbade4d9fa..0000000000 --- a/platform/core/test/actions/ActionProviderSpec.js +++ /dev/null @@ -1,204 +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. - *****************************************************************************/ - -/** - * ActionProviderSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/actions/ActionProvider"], - function (ActionProvider) { - - describe("The action provider", function () { - var mockLog, - actions, - actionProvider; - - function SimpleAction() { - return { - perform: function () { - return "simple"; - } - }; - } - - function CategorizedAction() { - return { - perform: function () { - return "categorized"; - } - }; - } - - CategorizedAction.category = "someCategory"; - - function KeyedAction() { - return { - perform: function () { - return "keyed"; - } - }; - } - - KeyedAction.key = "someKey"; - - function CategorizedKeyedAction() { - return { - perform: function () { - return "both"; - } - }; - } - - CategorizedKeyedAction.key = "someKey"; - CategorizedKeyedAction.category = "someCategory"; - - function MetadataAction() { - return { - perform: function () { - return "metadata"; - }, - getMetadata: function () { - return "custom metadata"; - } - }; - } - - MetadataAction.key = "metadata"; - - beforeEach(function () { - mockLog = jasmine.createSpyObj( - '$log', - ['error', 'warn', 'info', 'debug'] - ); - actions = [ - SimpleAction, - CategorizedAction, - KeyedAction, - CategorizedKeyedAction, - MetadataAction - ]; - actionProvider = new ActionProvider(actions); - }); - - it("exposes provided action extensions", function () { - var provided = actionProvider.getActions(); - - // Should have gotten all actions - expect(provided.length).toEqual(actions.length); - - // Verify that this was the action we expected - expect(provided[0].perform()).toEqual("simple"); - }); - - it("matches provided actions by key", function () { - var provided = actionProvider.getActions({ key: "someKey" }); - - // Only two should have matched - expect(provided.length).toEqual(2); - - // Verify that this was the action we expected - expect(provided[0].perform()).toEqual("keyed"); - }); - - it("matches provided actions by category", function () { - var provided = actionProvider.getActions({ category: "someCategory" }); - - // Only two should have matched - expect(provided.length).toEqual(2); - - // Verify that this was the action we expected - expect(provided[0].perform()).toEqual("categorized"); - }); - - it("matches provided actions by both category and key", function () { - var provided = actionProvider.getActions({ - category: "someCategory", - key: "someKey" - }); - - // Only two should have matched - expect(provided.length).toEqual(1); - - // Verify that this was the action we expected - expect(provided[0].perform()).toEqual("both"); - }); - - it("adds a getMetadata method when none is defined", function () { - var provided = actionProvider.getActions({ - category: "someCategory", - key: "someKey" - }); - - // Should be defined, even though the action didn't define this - expect(provided[0].getMetadata).toBeDefined(); - - // Should have static fields, plus context - expect(provided[0].getMetadata().context).toEqual({ - key: "someKey", - category: "someCategory" - }); - - }); - - it("does not override defined getMetadata methods", function () { - var provided = actionProvider.getActions({ key: "metadata" }); - expect(provided[0].getMetadata()).toEqual("custom metadata"); - }); - - describe("when actions throw errors during instantiation", function () { - var errorText, - provided; - - beforeEach(function () { - errorText = "some error text"; - - function BadAction() { - throw new Error(errorText); - } - - provided = new ActionProvider( - [SimpleAction, BadAction], - mockLog - ).getActions(); - }); - - it("logs an error", function () { - expect(mockLog.error) - .toHaveBeenCalledWith(jasmine.any(String)); - }); - - it("reports the error's message", function () { - expect( - mockLog.error.calls.mostRecent().args[0].indexOf(errorText) - ).not.toEqual(-1); - }); - - it("still provides valid actions", function () { - expect(provided.length).toEqual(1); - expect(provided[0].perform()).toEqual("simple"); - }); - - }); - - }); - } -); diff --git a/platform/core/test/actions/LoggingActionDecoratorSpec.js b/platform/core/test/actions/LoggingActionDecoratorSpec.js deleted file mode 100644 index 332b337cbb..0000000000 --- a/platform/core/test/actions/LoggingActionDecoratorSpec.js +++ /dev/null @@ -1,71 +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. - *****************************************************************************/ - -/** - * LoggingActionDecoratorSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/actions/LoggingActionDecorator"], - function (LoggingActionDecorator) { - - describe("The logging action decorator", function () { - var mockLog, - mockAction, - mockActionService, - decorator; - - beforeEach(function () { - mockAction = jasmine.createSpyObj( - "action", - ["perform", "getMetadata"] - ); - mockActionService = jasmine.createSpyObj( - "actionService", - ["getActions"] - ); - mockLog = jasmine.createSpyObj( - "$log", - ["error", "warn", "info", "debug"] - ); - - mockActionService.getActions.and.returnValue([mockAction]); - - decorator = new LoggingActionDecorator( - mockLog, - mockActionService - ); - }); - - it("logs when actions are performed", function () { - // Verify precondition - expect(mockLog.info).not.toHaveBeenCalled(); - - // Perform an action, retrieved through the decorator - decorator.getActions()[0].perform(); - - // That should have been logged. - expect(mockLog.info).toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/core/test/capabilities/CompositionCapabilitySpec.js b/platform/core/test/capabilities/CompositionCapabilitySpec.js deleted file mode 100644 index 48a9ac684f..0000000000 --- a/platform/core/test/capabilities/CompositionCapabilitySpec.js +++ /dev/null @@ -1,214 +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. - *****************************************************************************/ - -/** - * CompositionCapabilitySpec. Created by vwoeltje on 11/6/14. - */ -define( - [ - "../../src/capabilities/CompositionCapability", - "../../src/capabilities/ContextualDomainObject" - ], - function (CompositionCapability, ContextualDomainObject) { - - var DOMAIN_OBJECT_METHODS = [ - "getId", - "getModel", - "getCapability", - "hasCapability", - "useCapability" - ]; - - describe("The composition capability", function () { - var mockDomainObject, - mockInjector, - mockObjectService, - composition; - - // Composition Capability makes use of promise chaining, - // so support that, but don't introduce complication of - // native promises. - function mockPromise(value) { - return (value || {}).then ? value : { - then: function (callback) { - return mockPromise(callback(value)); - } - }; - } - - beforeEach(function () { - mockDomainObject = jasmine.createSpyObj( - "domainObject", - DOMAIN_OBJECT_METHODS - ); - - mockObjectService = jasmine.createSpyObj( - "objectService", - ["getObjects"] - ); - - mockInjector = { - get: function (name) { - return (name === "objectService") && mockObjectService; - } - }; - - mockObjectService.getObjects.and.returnValue(mockPromise([])); - - composition = new CompositionCapability( - mockInjector, - mockDomainObject - ); - }); - - it("applies only to models with a composition field", function () { - expect(CompositionCapability.appliesTo({ composition: [] })) - .toBeTruthy(); - expect(CompositionCapability.appliesTo({})) - .toBeFalsy(); - }); - - it("requests ids found in model's composition from the object service", function () { - var ids = ["a", "b", "c", "xyz"]; - - mockDomainObject.getModel.and.returnValue({ composition: ids }); - - composition.invoke(); - - expect(mockObjectService.getObjects).toHaveBeenCalledWith(ids); - }); - - it("adds a context capability to returned domain objects", function () { - var result, - mockChild = jasmine.createSpyObj("child", DOMAIN_OBJECT_METHODS); - - mockDomainObject.getModel.and.returnValue({ composition: ["x"] }); - mockObjectService.getObjects.and.returnValue(mockPromise({x: mockChild})); - mockChild.getCapability.and.returnValue(undefined); - - composition.invoke().then(function (c) { - result = c; - }); - - // Should have been added by a wrapper - expect(result[0].getCapability('context')).toBeDefined(); - - }); - - it("allows domain objects to be added", function () { - var result, - testModel = { composition: [] }, - mockChild = jasmine.createSpyObj("child", DOMAIN_OBJECT_METHODS); - - mockDomainObject.getModel.and.returnValue(testModel); - mockObjectService.getObjects.and.returnValue(mockPromise({a: mockChild})); - mockChild.getCapability.and.returnValue(undefined); - mockChild.getId.and.returnValue('a'); - - mockDomainObject.useCapability.and.callFake(function (key, mutator) { - if (key === 'mutation') { - mutator(testModel); - - return mockPromise(true); - } - }); - - composition.add(mockChild).then(function (domainObject) { - result = domainObject; - }); - - expect(testModel.composition).toEqual(['a']); - - // Should have returned the added object in its new context - expect(result.getId()).toEqual('a'); - expect(result.getCapability('context')).toBeDefined(); - expect(result.getCapability('context').getParent()) - .toEqual(mockDomainObject); - }); - - it("does not re-add IDs which are already present", function () { - var result, - testModel = { composition: ['a'] }, - mockChild = jasmine.createSpyObj("child", DOMAIN_OBJECT_METHODS); - - mockDomainObject.getModel.and.returnValue(testModel); - mockObjectService.getObjects.and.returnValue(mockPromise({a: mockChild})); - mockChild.getCapability.and.returnValue(undefined); - mockChild.getId.and.returnValue('a'); - - mockDomainObject.useCapability.and.callFake(function (key, mutator) { - if (key === 'mutation') { - mutator(testModel); - - return mockPromise(true); - } - }); - - composition.add(mockChild).then(function (domainObject) { - result = domainObject; - }); - - // Still just 'a' - expect(testModel.composition).toEqual(['a']); - - // Should have returned the added object in its new context - expect(result.getId()).toEqual('a'); - expect(result.getCapability('context')).toBeDefined(); - expect(result.getCapability('context').getParent()) - .toEqual(mockDomainObject); - }); - - it("can add objects at a specified index", function () { - var result, - testModel = { composition: ['a', 'b', 'c'] }, - mockChild = jasmine.createSpyObj("child", DOMAIN_OBJECT_METHODS); - - mockDomainObject.getModel.and.returnValue(testModel); - mockObjectService.getObjects.and.returnValue(mockPromise({a: mockChild})); - mockChild.getCapability.and.returnValue(undefined); - mockChild.getId.and.returnValue('a'); - - mockDomainObject.useCapability.and.callFake(function (key, mutator) { - if (key === 'mutation') { - mutator(testModel); - - return mockPromise(true); - } - }); - - composition.add(mockChild, 1).then(function (domainObject) { - result = domainObject; - }); - - // Still just 'a' - expect(testModel.composition).toEqual(['b', 'a', 'c']); - - // Should have returned the added object in its new context - expect(result.getId()).toEqual('a'); - expect(result.getCapability('context')).toBeDefined(); - expect(result.getCapability('context').getParent()) - .toEqual(mockDomainObject); - }); - - }); - } -); diff --git a/platform/core/test/capabilities/ContextCapabilitySpec.js b/platform/core/test/capabilities/ContextCapabilitySpec.js deleted file mode 100644 index 8afcbd7e10..0000000000 --- a/platform/core/test/capabilities/ContextCapabilitySpec.js +++ /dev/null @@ -1,79 +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. - *****************************************************************************/ - -/** - * ContextCapability. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/capabilities/ContextCapability"], - function (ContextCapability) { - - var DOMAIN_OBJECT_METHODS = [ - "getId", - "getModel", - "getCapability", - "hasCapability", - "useCapability" - ]; - - describe("The context capability", function () { - var mockDomainObject, - mockParent, - mockGrandparent, - mockContext, - context; - - beforeEach(function () { - mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS); - mockParent = jasmine.createSpyObj("parent", DOMAIN_OBJECT_METHODS); - mockGrandparent = jasmine.createSpyObj("grandparent", DOMAIN_OBJECT_METHODS); - mockContext = jasmine.createSpyObj("context", ["getParent", "getRoot", "getPath"]); - - mockParent.getCapability.and.returnValue(mockContext); - mockContext.getParent.and.returnValue(mockGrandparent); - mockContext.getRoot.and.returnValue(mockGrandparent); - mockContext.getPath.and.returnValue([mockGrandparent, mockParent]); - - context = new ContextCapability(mockParent, mockDomainObject); - }); - - it("allows an object's parent to be retrieved", function () { - expect(context.getParent()).toEqual(mockParent); - }); - - it("allows an object's full ancestry to be retrieved", function () { - expect(context.getPath()).toEqual([mockGrandparent, mockParent, mockDomainObject]); - }); - - it("allows the deepest ancestor of an object to be retrieved", function () { - expect(context.getRoot()).toEqual(mockGrandparent); - }); - - it("treats ancestors with no context capability as deepest ancestors", function () { - mockParent.getCapability.and.returnValue(undefined); - expect(context.getPath()).toEqual([mockParent, mockDomainObject]); - expect(context.getRoot()).toEqual(mockParent); - }); - - }); - } -); diff --git a/platform/core/test/capabilities/ContextualDomainObjectSpec.js b/platform/core/test/capabilities/ContextualDomainObjectSpec.js deleted file mode 100644 index ced432ff97..0000000000 --- a/platform/core/test/capabilities/ContextualDomainObjectSpec.js +++ /dev/null @@ -1,78 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * ContextualDomainObjectSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/capabilities/ContextualDomainObject"], - function (ContextualDomainObject) { - - var DOMAIN_OBJECT_METHODS = [ - "getId", - "getModel", - "getCapability", - "hasCapability", - "useCapability" - ]; - - describe("A contextual domain object", function () { - var mockParent, - mockDomainObject, - model, - contextualDomainObject; - - beforeEach(function () { - mockParent = jasmine.createSpyObj("parent", DOMAIN_OBJECT_METHODS); - mockDomainObject = jasmine.createSpyObj("parent", DOMAIN_OBJECT_METHODS); - - model = { someKey: "some value" }; - - mockDomainObject.getCapability.and.returnValue("some capability"); - mockDomainObject.getModel.and.returnValue(model); - - contextualDomainObject = new ContextualDomainObject( - mockDomainObject, - mockParent - ); - }); - - it("adds a context capability to a domain object", function () { - var context = contextualDomainObject.getCapability('context'); - - // Expect something that looks like a context capability - expect(context).toBeDefined(); - expect(context.getPath).toBeDefined(); - expect(context.getRoot).toBeDefined(); - expect(context.getParent()).toEqual(mockParent); - }); - - it("does not shadow other domain object methods", function () { - expect(contextualDomainObject.getModel()) - .toEqual(model); - expect(contextualDomainObject.getCapability("other")) - .toEqual("some capability"); - }); - - }); - } -); diff --git a/platform/core/test/capabilities/CoreCapabilityProviderSpec.js b/platform/core/test/capabilities/CoreCapabilityProviderSpec.js deleted file mode 100644 index 2bcf984ac3..0000000000 --- a/platform/core/test/capabilities/CoreCapabilityProviderSpec.js +++ /dev/null @@ -1,112 +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. - *****************************************************************************/ - -/** - * CoreCapabilityProviderSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/capabilities/CoreCapabilityProvider"], - function (CoreCapabilityProvider) { - - describe("The core capability provider", function () { - var mockLog, - provider; - - function BasicCapability() { - return; - } - - BasicCapability.key = "basic"; - - function ApplicableCapability() { - return; - } - - ApplicableCapability.key = "applicable"; - ApplicableCapability.appliesTo = function (model) { - return !model.isNotApplicable; - }; - - function KeylessCapability() { - return; - } - - beforeEach(function () { - KeylessCapability.key = undefined; - - mockLog = jasmine.createSpyObj( - "$log", - ["error", "warn", "info", "debug"] - ); - - provider = new CoreCapabilityProvider([ - BasicCapability, - ApplicableCapability, - KeylessCapability - ], mockLog); - }); - - it("returns capabilities for models, from extensions", function () { - expect(provider.getCapabilities({})).toEqual({ - basic: BasicCapability, - applicable: ApplicableCapability - }); - }); - - it("filters out capabilities which do not apply to models", function () { - expect(provider.getCapabilities({ isNotApplicable: true })).toEqual({ - basic: BasicCapability - }); - }); - - it("logs a warning when capability extensions have not defined keys", function () { - // Verify precondition - expect(mockLog.warn).not.toHaveBeenCalled(); - - provider.getCapabilities({}); - - expect(mockLog.warn).toHaveBeenCalled(); - - }); - - it("does not log a warning when all capability extensions are valid", function () { - KeylessCapability.key = "someKey"; - provider.getCapabilities({}); - expect(mockLog.warn).not.toHaveBeenCalled(); - }); - - it("prefers higher-priority capability", function () { - KeylessCapability.key = BasicCapability.key; - expect(provider.getCapabilities({}).basic) - .toEqual(BasicCapability); - }); - - // https://github.com/nasa/openmctweb/issues/49 - it("does not log a warning for multiple capabilities with the same key", function () { - KeylessCapability.key = BasicCapability.key; - provider.getCapabilities({}); - expect(mockLog.warn).not.toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/core/test/capabilities/DelegationCapabilitySpec.js b/platform/core/test/capabilities/DelegationCapabilitySpec.js deleted file mode 100644 index 109b1eba70..0000000000 --- a/platform/core/test/capabilities/DelegationCapabilitySpec.js +++ /dev/null @@ -1,110 +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. - *****************************************************************************/ - -/** - * DelegationCapabilitySpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/capabilities/DelegationCapability"], - function (DelegationCapability) { - - describe("The delegation capability", function () { - var captured, - typeDef = {}, - type, - capabilities, - children = [], - object = {}, - delegation; - - function capture(k) { - return function (v) { - captured[k] = v; - }; - } - - function TestDomainObject(caps, id) { - return { - getId: function () { - return id; - }, - getCapability: function (name) { - return caps[name]; - }, - useCapability: function (name) { - return this.getCapability(name).invoke(); - }, - hasCapability: function (name) { - return this.getCapability(name) !== undefined; - } - }; - } - - function mockPromise(value) { - return { - then: function (callback) { - return value.then - ? value : mockPromise(callback(value)); - } - }; - } - - beforeEach(function () { - captured = {}; - typeDef = {}; - typeDef.delegates = ["foo"]; - type = { - getDefinition: function () { - return typeDef; - } - }; - children = []; - capabilities = { - type: type, - composition: { - invoke: function () { - return mockPromise(children); - } - } - }; - object = new TestDomainObject(capabilities); - - delegation = new DelegationCapability({ when: mockPromise }, object); - }); - - it("provides a list of children which expose a desired capability", function () { - - children = [ - new TestDomainObject({ foo: true }, 'has-capability'), - new TestDomainObject({ }, 'does-not-have-capability') - ]; - - // Look up delegates - delegation.getDelegates('foo').then(capture('delegates')); - - // Expect only the first child to be a delegate - expect(captured.delegates.length).toEqual(1); - expect(captured.delegates[0].getId()).toEqual('has-capability'); - }); - }); - } -); diff --git a/platform/core/test/capabilities/InstantiationCapabilitySpec.js b/platform/core/test/capabilities/InstantiationCapabilitySpec.js deleted file mode 100644 index 88bfcb4a39..0000000000 --- a/platform/core/test/capabilities/InstantiationCapabilitySpec.js +++ /dev/null @@ -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. - *****************************************************************************/ - -define( - ["../../src/capabilities/InstantiationCapability"], - function (InstantiationCapability) { - - describe("The 'instantiation' capability", function () { - var mockInjector, - mockIdentifierService, - mockInstantiate, - mockIdentifier, - mockNow, - mockDomainObject, - instantiation; - - beforeEach(function () { - mockInjector = jasmine.createSpyObj("$injector", ["get"]); - mockInstantiate = jasmine.createSpy("instantiate"); - mockIdentifierService = jasmine.createSpyObj( - 'identifierService', - ['parse', 'generate'] - ); - mockIdentifier = jasmine.createSpyObj( - 'identifier', - ['getSpace', 'getKey', 'getDefinedSpace'] - ); - mockDomainObject = jasmine.createSpyObj( - 'domainObject', - ['getId', 'getCapability', 'getModel'] - ); - - mockInjector.get.and.callFake(function (key) { - return { - 'instantiate': mockInstantiate - }[key]; - }); - mockIdentifierService.parse.and.returnValue(mockIdentifier); - mockIdentifierService.generate.and.returnValue("some-id"); - - mockNow = jasmine.createSpy(); - mockNow.and.returnValue(1234321); - - instantiation = new InstantiationCapability( - mockInjector, - mockIdentifierService, - mockNow, - mockDomainObject - ); - }); - - it("aliases 'instantiate' as 'invoke'", function () { - expect(instantiation.invoke).toBe(instantiation.instantiate); - }); - - it("uses instantiate and contextualize to create domain objects", function () { - var mockDomainObj = jasmine.createSpyObj('domainObject', [ - 'getId', - 'getModel', - 'getCapability', - 'useCapability', - 'hasCapability' - ]), testModel = { someKey: "some value" }; - mockInstantiate.and.returnValue(mockDomainObj); - instantiation.instantiate(testModel); - expect(mockInstantiate) - .toHaveBeenCalledWith({ - someKey: "some value", - modified: mockNow() - }, jasmine.any(String)); - }); - - }); - } -); diff --git a/platform/core/test/capabilities/MetadataCapabilitySpec.js b/platform/core/test/capabilities/MetadataCapabilitySpec.js deleted file mode 100644 index a94fac607e..0000000000 --- a/platform/core/test/capabilities/MetadataCapabilitySpec.js +++ /dev/null @@ -1,99 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['../../src/capabilities/MetadataCapability'], - function (MetadataCapability) { - - describe("The metadata capability", function () { - var mockDomainObject, - mockType, - mockProperties, - testModel, - metadata; - - function getCapability(key) { - return key === 'type' ? mockType : undefined; - } - - function findValue(properties, name) { - var i; - for (i = 0; i < properties.length; i += 1) { - if (properties[i].name === name) { - return properties[i].value; - } - } - } - - beforeEach(function () { - mockDomainObject = jasmine.createSpyObj( - 'domainObject', - ['getId', 'getCapability', 'useCapability', 'getModel'] - ); - mockType = jasmine.createSpyObj( - 'type', - ['getProperties', 'getName'] - ); - mockProperties = ['a', 'b', 'c'].map(function (k) { - var mockProperty = jasmine.createSpyObj( - 'property-' + k, - ['getValue', 'getDefinition'] - ); - mockProperty.getValue.and.returnValue("Value " + k); - mockProperty.getDefinition.and.returnValue({ name: "Property " + k}); - - return mockProperty; - }); - testModel = { name: "" }; - - mockDomainObject.getId.and.returnValue("Test id"); - mockDomainObject.getModel.and.returnValue(testModel); - mockDomainObject.getCapability.and.callFake(getCapability); - mockDomainObject.useCapability.and.callFake(getCapability); - mockType.getProperties.and.returnValue(mockProperties); - mockType.getName.and.returnValue("Test type"); - - metadata = new MetadataCapability(mockDomainObject); - }); - - it("reads properties from the domain object model", function () { - metadata.invoke(); - mockProperties.forEach(function (mockProperty) { - expect(mockProperty.getValue).toHaveBeenCalledWith(testModel); - }); - }); - - it("reports type-specific properties", function () { - var properties = metadata.invoke(); - expect(findValue(properties, 'Property a')).toEqual("Value a"); - expect(findValue(properties, 'Property b')).toEqual("Value b"); - expect(findValue(properties, 'Property c')).toEqual("Value c"); - }); - - it("reports generic properties", function () { - var properties = metadata.invoke(); - expect(findValue(properties, 'Type')).toEqual("Test type"); - }); - - }); - } -); diff --git a/platform/core/test/capabilities/MutationCapabilitySpec.js b/platform/core/test/capabilities/MutationCapabilitySpec.js deleted file mode 100644 index 05ac4af63b..0000000000 --- a/platform/core/test/capabilities/MutationCapabilitySpec.js +++ /dev/null @@ -1,139 +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. - *****************************************************************************/ - -/** - * MutationCapabilitySpec. Created by vwoeltje on 11/6/14. - */ -define( - [ - "../../src/capabilities/MutationCapability", - "../../src/services/Topic" - ], - function (MutationCapability, Topic) { - - describe("The mutation capability", function () { - var testModel, - topic, - mockNow, - domainObject = { - getId: function () { - return "test-id"; - }, - getModel: function () { - return testModel; - } - }, - mutation; - - beforeEach(function () { - testModel = { number: 6 }; - topic = new Topic(); - mockNow = jasmine.createSpy('now'); - mockNow.and.returnValue(12321); - mutation = new MutationCapability( - topic, - mockNow, - domainObject - ); - }); - - it("allows mutation of a model", function () { - mutation.invoke(function (m) { - m.number = m.number * 7; - }); - expect(testModel.number).toEqual(42); - }); - - it("allows setting a model", function () { - mutation.invoke(function () { - return { someKey: "some value" }; - }); - expect(testModel.number).toBeUndefined(); - expect(testModel.someKey).toEqual("some value"); - }); - - it("allows model mutation to be aborted", function () { - mutation.invoke(function (m) { - m.number = m.number * 7; - - return false; // Should abort change - }); - // Number should not have been changed - expect(testModel.number).toEqual(6); - }); - - it("attaches a timestamp on mutation", function () { - // Verify precondition - expect(testModel.modified).toBeUndefined(); - mutation.invoke(function (m) { - m.number = m.number * 7; - }); - // Should have gotten a timestamp from 'now' - expect(testModel.modified).toEqual(12321); - }); - - it("allows a timestamp to be provided", function () { - mutation.invoke(function (m) { - m.number = m.number * 7; - }, 42); - // Should have gotten a timestamp from 'now' - expect(testModel.modified).toEqual(42); - }); - - it("notifies listeners of mutation", function () { - var mockCallback = jasmine.createSpy('callback'); - mutation.listen(mockCallback); - mutation.invoke(function (m) { - m.number = 8; - }); - expect(mockCallback).toHaveBeenCalled(); - expect(mockCallback.calls.mostRecent().args[0].number) - .toEqual(8); - }); - - it("allows listeners to stop listening", function () { - var mockCallback = jasmine.createSpy('callback'); - mutation.listen(mockCallback)(); // Unlisten immediately - mutation.invoke(function (m) { - m.number = 8; - }); - expect(mockCallback).not.toHaveBeenCalled(); - }); - - it("shares listeners across instances", function () { - var mockCallback = jasmine.createSpy('callback'), - otherMutation = new MutationCapability( - topic, - mockNow, - domainObject - ); - mutation.listen(mockCallback); - otherMutation.invoke(function (m) { - m.number = 8; - }); - expect(mockCallback).toHaveBeenCalled(); - expect(mockCallback.calls.mostRecent().args[0].number) - .toEqual(8); - }); - }); - } -); diff --git a/platform/core/test/capabilities/PersistenceCapabilitySpec.js b/platform/core/test/capabilities/PersistenceCapabilitySpec.js deleted file mode 100644 index b876f8e6ec..0000000000 --- a/platform/core/test/capabilities/PersistenceCapabilitySpec.js +++ /dev/null @@ -1,196 +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. - *****************************************************************************/ -/** - * PersistenceCapabilitySpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/capabilities/PersistenceCapability"], - function (PersistenceCapability) { - - describe("The persistence capability", function () { - var mockPersistenceService, - mockIdentifierService, - mockDomainObject, - mockIdentifier, - mockNofificationService, - mockCacheService, - mockQ, - key = "persistence key", - id = "object identifier", - model, - SPACE = "some space", - persistence, - mockOpenMCT, - mockNewStyleDomainObject; - - function asPromise(value, doCatch) { - return (value || {}).then ? value : { - then: function (callback) { - return asPromise(callback(value)); - }, - catch: function (callback) { - //Define a default 'happy' catch, that skips over the - // catch callback - return doCatch ? asPromise(callback(value)) : asPromise(value); - } - }; - } - - beforeEach(function () { - model = { - someKey: "some value", - name: "domain object" - }; - - mockPersistenceService = jasmine.createSpyObj( - "persistenceService", - ["updateObject", "readObject", "createObject", "deleteObject"] - ); - - mockIdentifierService = jasmine.createSpyObj( - 'identifierService', - ['parse', 'generate'] - ); - mockIdentifier = jasmine.createSpyObj( - 'identifier', - ['getSpace', 'getKey', 'getDefinedSpace'] - ); - mockQ = jasmine.createSpyObj( - "$q", - ["reject", "when"] - ); - mockNofificationService = jasmine.createSpyObj( - "notificationService", - ["error"] - ); - mockCacheService = jasmine.createSpyObj( - "cacheService", - ["get", "put", "remove", "all"] - ); - - mockDomainObject = { - getId: function () { - return id; - }, - getModel: function () { - return model; - }, - useCapability: jasmine.createSpy() - }; - - mockNewStyleDomainObject = Object.assign({}, model); - mockNewStyleDomainObject.identifier = { - namespace: SPACE, - key: key - }; - - // Simulate mutation capability - mockDomainObject.useCapability.and.callFake(function (capability, mutator) { - if (capability === 'mutation') { - model = mutator(model) || model; - } - }); - - mockOpenMCT = {}; - mockOpenMCT.objects = jasmine.createSpyObj('Object API', ['save']); - - mockIdentifierService.parse.and.returnValue(mockIdentifier); - mockIdentifier.getSpace.and.returnValue(SPACE); - mockIdentifier.getKey.and.returnValue(key); - mockQ.when.and.callFake(asPromise); - persistence = new PersistenceCapability( - mockCacheService, - mockPersistenceService, - mockIdentifierService, - mockNofificationService, - mockQ, - mockOpenMCT, - mockDomainObject - ); - }); - - describe("successful persistence", function () { - beforeEach(function () { - mockOpenMCT.objects.save.and.returnValue(Promise.resolve(true)); - }); - it("creates unpersisted objects with the persistence service", function () { - // Verify precondition; no call made during constructor - expect(mockOpenMCT.objects.save).not.toHaveBeenCalled(); - - persistence.persist(); - - expect(mockOpenMCT.objects.save).toHaveBeenCalledWith(mockNewStyleDomainObject); - }); - - it("reports which persistence space an object belongs to", function () { - expect(persistence.getSpace()).toEqual(SPACE); - }); - - it("refreshes the domain object model from persistence", function () { - var refreshModel = {someOtherKey: "some other value"}; - model.persisted = 1; - mockPersistenceService.readObject.and.returnValue(asPromise(refreshModel)); - persistence.refresh(); - expect(model).toEqual(refreshModel); - }); - - it("does not trigger error notification on successful" - + " persistence", function () { - let rejected = false; - - return persistence.persist() - .catch(() => rejected = true) - .then(() => { - expect(rejected).toBe(false); - expect(mockNofificationService.error).not.toHaveBeenCalled(); - }); - }); - }); - - describe("unsuccessful persistence", function () { - beforeEach(function () { - mockOpenMCT.objects.save.and.returnValue(Promise.resolve(false)); - }); - it("rejects on falsey persistence result", function () { - let rejected = false; - - return persistence.persist() - .catch(() => rejected = true) - .then(() => { - expect(rejected).toBe(true); - }); - }); - - it("notifies user on persistence failure", function () { - let rejected = false; - - return persistence.persist() - .catch(() => rejected = true) - .then(() => { - expect(rejected).toBe(true); - expect(mockNofificationService.error).toHaveBeenCalled(); - }); - }); - }); - }); - } -); diff --git a/platform/core/test/capabilities/RelationshipCapabilitySpec.js b/platform/core/test/capabilities/RelationshipCapabilitySpec.js deleted file mode 100644 index 8821714f2a..0000000000 --- a/platform/core/test/capabilities/RelationshipCapabilitySpec.js +++ /dev/null @@ -1,144 +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. - *****************************************************************************/ - -/** - * CompositionCapabilitySpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/capabilities/RelationshipCapability"], - function (RelationshipCapability) { - - var DOMAIN_OBJECT_METHODS = [ - "getId", - "getModel", - "getCapability", - "hasCapability", - "useCapability" - ]; - - describe("The relationship capability", function () { - var mockDomainObject, - mockInjector, - mockObjectService, - relationship; - - // Composition Capability makes use of promise chaining, - // so support that, but don't introduce complication of - // native promises. - function mockPromise(value) { - return { - then: function (callback) { - return mockPromise(callback(value)); - } - }; - } - - beforeEach(function () { - mockDomainObject = jasmine.createSpyObj( - "domainObject", - DOMAIN_OBJECT_METHODS - ); - - mockObjectService = jasmine.createSpyObj( - "objectService", - ["getObjects"] - ); - - mockInjector = { - get: function (name) { - return (name === "objectService") && mockObjectService; - } - }; - - mockObjectService.getObjects.and.returnValue(mockPromise([])); - - relationship = new RelationshipCapability( - mockInjector, - mockDomainObject - ); - }); - - it("applies only to models with a 'relationships' field", function () { - expect(RelationshipCapability.appliesTo({ relationships: {} })) - .toBeTruthy(); - expect(RelationshipCapability.appliesTo({})) - .toBeFalsy(); - }); - - it("requests ids found in model's composition from the object service", function () { - var ids = ["a", "b", "c", "xyz"]; - - mockDomainObject.getModel.and.returnValue({ relationships: { xyz: ids } }); - - relationship.getRelatedObjects('xyz'); - - expect(mockObjectService.getObjects).toHaveBeenCalledWith(ids); - }); - - it("provides a list of relationship types", function () { - mockDomainObject.getModel.and.returnValue({ - relationships: { - abc: ['a', 'b'], - def: "not an array, should be ignored", - xyz: [] - } - }); - expect(relationship.listRelationships()).toEqual(['abc', 'xyz']); - }); - - it("avoids redundant requests", function () { - // Lookups can be expensive, so this capability - // should have some self-caching - mockDomainObject.getModel - .and.returnValue({ relationships: { xyz: ['a'] } }); - - // Call twice; response should be the same object instance - expect(relationship.getRelatedObjects('xyz')) - .toBe(relationship.getRelatedObjects('xyz')); - - // Should have only made one call - expect(mockObjectService.getObjects.calls.count()) - .toEqual(1); - }); - - it("makes new requests on modification", function () { - // Lookups can be expensive, so this capability - // should have some self-caching - var testModel; - - testModel = { relationships: { xyz: ['a'] } }; - - mockDomainObject.getModel.and.returnValue(testModel); - - // Call twice, but as if modification had occurred in between - relationship.getRelatedObjects('xyz'); - testModel.modified = 123; - relationship.getRelatedObjects('xyz'); - - // Should have only made one call - expect(mockObjectService.getObjects.calls.count()) - .toEqual(2); - }); - - }); - } -); diff --git a/platform/core/test/identifiers/IdentifierProviderSpec.js b/platform/core/test/identifiers/IdentifierProviderSpec.js deleted file mode 100644 index a29ccd39ba..0000000000 --- a/platform/core/test/identifiers/IdentifierProviderSpec.js +++ /dev/null @@ -1,56 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/identifiers/IdentifierProvider"], - function (IdentifierProvider) { - - describe("IdentifierProvider", function () { - var defaultSpace, - provider; - - beforeEach(function () { - defaultSpace = "some-default-space"; - provider = new IdentifierProvider(defaultSpace); - }); - - it("generates unique identifiers", function () { - expect(provider.generate()) - .not.toEqual(provider.generate()); - }); - - it("allows spaces to be specified for generated identifiers", function () { - var specificSpace = "some-specific-space", - id = provider.generate(specificSpace); - expect(id).toEqual(jasmine.any(String)); - expect(provider.parse(id).getDefinedSpace()) - .toEqual(specificSpace); - }); - - it("parses identifiers using the default space", function () { - expect(provider.parse("some-unprefixed-id").getSpace()) - .toEqual(defaultSpace); - }); - - }); - } -); diff --git a/platform/core/test/identifiers/IdentifierSpec.js b/platform/core/test/identifiers/IdentifierSpec.js deleted file mode 100644 index 2025d97dad..0000000000 --- a/platform/core/test/identifiers/IdentifierSpec.js +++ /dev/null @@ -1,80 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/identifiers/Identifier"], - function (Identifier) { - - describe("A parsed domain object identifier", function () { - var id, - defaultSpace, - identifier; - - beforeEach(function () { - defaultSpace = "someDefaultSpace"; - }); - - describe("when space is encoded", function () { - var idSpace, idKey; - - beforeEach(function () { - idSpace = "a-specific-space"; - idKey = "a-specific-key"; - id = idSpace + ":" + idKey; - identifier = new Identifier(id, defaultSpace); - }); - - it("provides the encoded space", function () { - expect(identifier.getSpace()).toEqual(idSpace); - }); - - it("provides the key within that space", function () { - expect(identifier.getKey()).toEqual(idKey); - }); - - it("provides the defined space", function () { - expect(identifier.getDefinedSpace()).toEqual(idSpace); - }); - }); - - describe("when space is not encoded", function () { - beforeEach(function () { - id = "a-generic-id"; - identifier = new Identifier(id, defaultSpace); - }); - - it("provides the default space", function () { - expect(identifier.getSpace()).toEqual(defaultSpace); - }); - - it("provides the id as the key", function () { - expect(identifier.getKey()).toEqual(id); - }); - - it("provides no defined space", function () { - expect(identifier.getDefinedSpace()).toEqual(undefined); - }); - }); - - }); - } -); diff --git a/platform/core/test/models/CachingModelDecoratorSpec.js b/platform/core/test/models/CachingModelDecoratorSpec.js deleted file mode 100644 index 1fcf4b3aca..0000000000 --- a/platform/core/test/models/CachingModelDecoratorSpec.js +++ /dev/null @@ -1,157 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - "../../src/models/CachingModelDecorator", - "../../src/models/ModelCacheService" - ], - function (CachingModelDecorator, ModelCacheService) { - - xdescribe("The caching model decorator", function () { - var mockModelService, - mockCallback, - testModels, - decorator; - - function asPromise(value) { - return (value || {}).then ? value : { - then: function (callback) { - return asPromise(callback(value)); - } - }; - } - - function fakePromise() { - var chains = [], - callbacks = []; - - return { - then: function (callback) { - var next = fakePromise(); - callbacks.push(callback); - chains.push(next); - - return next; - }, - resolve: function (value) { - callbacks.forEach(function (cb, i) { - chains[i].resolve(cb(value)); - }); - } - }; - } - - beforeEach(function () { - mockCallback = jasmine.createSpy(); - mockModelService = jasmine.createSpyObj('modelService', ['getModels']); - testModels = { - a: { someKey: "some value" }, - b: { someOtherKey: "some other value" } - }; - mockModelService.getModels.and.returnValue(asPromise(testModels)); - decorator = new CachingModelDecorator( - new ModelCacheService(), - mockModelService - ); - }); - - it("loads models from its wrapped model service", function () { - decorator.getModels(['a', 'b']).then(mockCallback); - expect(mockCallback).toHaveBeenCalledWith(testModels); - }); - - it("does not try to reload cached models", function () { - mockModelService.getModels.and.returnValue(asPromise({ a: testModels.a })); - decorator.getModels(['a']); - mockModelService.getModels.and.returnValue(asPromise(testModels)); - decorator.getModels(['a', 'b']); - expect(mockModelService.getModels).not.toHaveBeenCalledWith(['a', 'b']); - expect(mockModelService.getModels.calls.mostRecent().args[0]).toEqual(['b']); - }); - - it("does not call its wrapped model service if not needed", function () { - decorator.getModels(['a', 'b']); - expect(mockModelService.getModels.calls.count()).toEqual(1); - decorator.getModels(['a', 'b']).then(mockCallback); - expect(mockModelService.getModels.calls.count()).toEqual(1); - // Verify that we still got back our models, even though - // no new call to the wrapped service was made - expect(mockCallback).toHaveBeenCalledWith(testModels); - }); - - it("ensures a single object instance, even for multiple concurrent calls", function () { - var promiseA, promiseB; - - promiseA = fakePromise(); - promiseB = fakePromise(); - - // Issue two calls before those promises resolve - mockModelService.getModels.and.returnValue(promiseA); - decorator.getModels(['a']); - mockModelService.getModels.and.returnValue(promiseB); - decorator.getModels(['a']).then(mockCallback); - - // Then resolve those promises. Note that we're whiteboxing here - // to figure out which promises to resolve (that is, we know that - // two thens are chained after each getModels) - promiseA.resolve(testModels); - promiseB.resolve({ - a: { someNewKey: "some other value" } - }); - - // Ensure that we have a pointer-identical instance - expect(mockCallback.calls.mostRecent().args[0].a) - .toEqual({ someNewKey: "some other value" }); - expect(mockCallback.calls.mostRecent().args[0].a) - .toBe(testModels.a); - }); - - it("is robust against updating with undefined values", function () { - var promiseA, promiseB; - - promiseA = fakePromise(); - promiseB = fakePromise(); - - // Issue two calls before those promises resolve - mockModelService.getModels.and.returnValue(promiseA); - decorator.getModels(['a']); - mockModelService.getModels.and.returnValue(promiseB); - decorator.getModels(['a']).then(mockCallback); - - // Some model providers might erroneously add undefined values - // under requested keys, so handle that - promiseA.resolve({ - a: undefined - }); - promiseB.resolve({ - a: { someNewKey: "some other value" } - }); - - // Should still have gotten the model - expect(mockCallback.calls.mostRecent().args[0].a) - .toEqual({ someNewKey: "some other value" }); - }); - - }); - } -); diff --git a/platform/core/test/models/ModelAggregatorSpec.js b/platform/core/test/models/ModelAggregatorSpec.js deleted file mode 100644 index 3f94234918..0000000000 --- a/platform/core/test/models/ModelAggregatorSpec.js +++ /dev/null @@ -1,84 +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. - *****************************************************************************/ - -/** - * ModelAggregatorSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/models/ModelAggregator"], - function (ModelAggregator) { - - describe("The model aggregator", function () { - var mockQ, - mockProviders, - modelList = [ - { - "a": { someKey: "some value" }, - "b": undefined - }, - { - "b": { someOtherKey: "some other value" }, - "a": undefined - } - ], - aggregator; - - beforeEach(function () { - mockQ = jasmine.createSpyObj("$q", ["all"]); - mockProviders = modelList.map(function (models, i) { - var mockProvider = jasmine.createSpyObj( - "mockProvider" + i, - ["getModels"] - ); - mockProvider.getModels.and.returnValue(models); - - return mockProvider; - }); - - mockQ.all.and.returnValue({ - then: function (c) { - return c(modelList); - } - }); - - aggregator = new ModelAggregator(mockQ, mockProviders); - }); - - it("aggregates results promised by multiple providers", function () { - expect(aggregator.getModels(["a", "b"])).toEqual({ - "a": { someKey: "some value" }, - "b": { someOtherKey: "some other value" } - }); - }); - - it("passes ids to all aggregated providers", function () { - aggregator.getModels(["a", "b"]); - - mockProviders.forEach(function (mockProvider) { - expect(mockProvider.getModels) - .toHaveBeenCalledWith(["a", "b"]); - }); - }); - - }); - } -); diff --git a/platform/core/test/models/ModelCacheServiceSpec.js b/platform/core/test/models/ModelCacheServiceSpec.js deleted file mode 100644 index 134146e794..0000000000 --- a/platform/core/test/models/ModelCacheServiceSpec.js +++ /dev/null @@ -1,68 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define(['../../src/models/ModelCacheService'], function (ModelCacheService) { - describe("ModelCacheService", function () { - var testIds, - testModels, - cacheService; - - beforeEach(function () { - testIds = ['a', 'b', 'c', 'd']; - testModels = testIds.reduce(function (models, id) { - models[id] = { someKey: "some value for " + id }; - - return models; - }, {}); - cacheService = new ModelCacheService(); - }); - - describe("when populated with models", function () { - beforeEach(function () { - testIds.forEach(function (id) { - cacheService.put(id, testModels[id]); - }); - }); - - it("indicates that it has these models", function () { - testIds.forEach(function (id) { - expect(cacheService.has(id)).toBe(true); - }); - }); - - it("provides all of these models", function () { - expect(cacheService.all()).toEqual(testModels); - }); - - it("allows models to be retrieved", function () { - testIds.forEach(function (id) { - expect(cacheService.get(id)).toEqual(testModels[id]); - }); - }); - - it("allows models to be removed", function () { - cacheService.remove('a'); - expect(cacheService.has('a')).toBe(false); - }); - }); - }); -}); diff --git a/platform/core/test/models/PersistedModelProviderSpec.js b/platform/core/test/models/PersistedModelProviderSpec.js deleted file mode 100644 index 3aca935442..0000000000 --- a/platform/core/test/models/PersistedModelProviderSpec.js +++ /dev/null @@ -1,173 +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. - *****************************************************************************/ - -/** - * PersistedModelProviderSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/models/PersistedModelProvider"], - function (PersistedModelProvider) { - - describe("The persisted model provider", function () { - var mockQ, - mockPersistenceService, - SPACE = "space0", - modTimes, - mockNow, - provider; - - function mockPromise(value) { - return (value || {}).then ? value : { - then: function (callback) { - return mockPromise(callback(value)); - }, - testValue: value - }; - } - - function mockAll(mockPromises) { - return mockPromise(mockPromises.map(function (p) { - return p.testValue; - })); - } - - beforeEach(function () { - modTimes = {}; - mockQ = { - when: mockPromise, - all: mockAll - }; - mockPersistenceService = jasmine.createSpyObj( - 'persistenceService', - [ - 'createObject', - 'readObject', - 'updateObject', - 'deleteObject', - 'listSpaces', - 'listObjects' - ] - ); - mockNow = jasmine.createSpy("now"); - - mockPersistenceService.readObject - .and.callFake(function (space, id) { - return mockPromise({ - space: space, - id: id, - modified: (modTimes[space] || {})[id], - persisted: 0 - }); - }); - mockPersistenceService.listSpaces - .and.returnValue(mockPromise([SPACE])); - - provider = new PersistedModelProvider( - mockPersistenceService, - mockQ, - mockNow, - SPACE - ); - }); - - it("reads object models from persistence", function () { - var models; - - provider.getModels(["a", "x", "zz"]).then(function (m) { - models = m; - }); - - expect(models).toEqual({ - a: { - space: SPACE, - id: "a", - persisted: 0, - modified: undefined - }, - x: { - space: SPACE, - id: "x", - persisted: 0, - modified: undefined - }, - zz: { - space: SPACE, - id: "zz", - persisted: 0, - modified: undefined - } - }); - }); - - it("ensures that persisted timestamps are present", function () { - var mockCallback = jasmine.createSpy("callback"), - testModels = { - a: { - modified: 123, - persisted: 1984, - name: "A" - }, - b: { - persisted: 1977, - name: "B" - }, - c: { - modified: 42, - name: "C" - }, - d: { name: "D" } - }; - - mockPersistenceService.readObject.and.callFake( - function (space, id) { - return mockPromise(testModels[id]); - } - ); - mockNow.and.returnValue(12321); - - provider.getModels(Object.keys(testModels)).then(mockCallback); - - expect(mockCallback).toHaveBeenCalledWith({ - a: { - modified: 123, - persisted: 1984, - name: "A" - }, - b: { - persisted: 1977, - name: "B" - }, - c: { - modified: 42, - persisted: 42, - name: "C" - }, - d: { - persisted: 12321, - name: "D" - } - }); - }); - - }); - } -); diff --git a/platform/core/test/models/StaticModelProviderSpec.js b/platform/core/test/models/StaticModelProviderSpec.js deleted file mode 100644 index 8f2dd59f63..0000000000 --- a/platform/core/test/models/StaticModelProviderSpec.js +++ /dev/null @@ -1,109 +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. - *****************************************************************************/ - -/** - * StaticModelProviderSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/models/StaticModelProvider"], - function (StaticModelProvider) { - - describe("The static model provider", function () { - var models = [ - { - "id": "a", - "model": { - "name": "Thing A", - "someProperty": "Some Value A" - } - }, - { - "id": "b", - "model": { - "name": "Thing B", - "someProperty": "Some Value B" - } - } - ], - mockLog, - mockQ, - provider; - - beforeEach(function () { - mockQ = jasmine.createSpyObj("$q", ["when"]); - mockLog = jasmine.createSpyObj("$log", ["error", "warn", "info", "debug"]); - provider = new StaticModelProvider(models, mockQ, mockLog); - }); - - it("provides models from extension declarations", function () { - var mockPromise = { - then: function () { - return; - } - }; - mockQ.when.and.returnValue(mockPromise); - - // Verify that we got the promise as the return value - expect(provider.getModels(["a", "b"])).toEqual(mockPromise); - - // Verify that the promise has the desired models - expect(mockQ.when.calls.count()).toEqual(1); - expect(mockQ.when.calls.mostRecent().args[0].a.name).toEqual("Thing A"); - expect(mockQ.when.calls.mostRecent().args[0].a.someProperty).toEqual("Some Value A"); - expect(mockQ.when.calls.mostRecent().args[0].b.name).toEqual("Thing B"); - expect(mockQ.when.calls.mostRecent().args[0].b.someProperty).toEqual("Some Value B"); - }); - - it("does not provide models which are not in extension declarations", function () { - provider.getModels(["c"]); - - // Verify that the promise has the desired models - expect(mockQ.when.calls.count()).toEqual(1); - expect(mockQ.when.calls.mostRecent().args[0].c).toBeUndefined(); - }); - - it("logs a warning when model definitions are malformed", function () { - // Verify precondition - expect(mockLog.warn).not.toHaveBeenCalled(); - - // Shouldn't fail with an exception - expect(new StaticModelProvider([ - { "bad": "no id" }, - { "id": "...but no model..." }, - { "model": "...and no id..." }, - { - "id": -40, - "model": {} - }, - { - "model": "should be an object", - "id": "x" - } - ], mockQ, mockLog)).toBeDefined(); - - // Should show warnings - expect(mockLog.warn.calls.count()).toEqual(5); - }); - - }); - } -); diff --git a/platform/core/test/objects/DomainObjectProviderSpec.js b/platform/core/test/objects/DomainObjectProviderSpec.js deleted file mode 100644 index ffab540d41..0000000000 --- a/platform/core/test/objects/DomainObjectProviderSpec.js +++ /dev/null @@ -1,86 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * DomainObjectProviderSpec. Created by vwoeltje on 11/6/14. - */ -define( - [ - "../../src/objects/DomainObjectProvider", - "../../src/objects/DomainObjectImpl" - ], - function (DomainObjectProvider, DomainObjectImpl) { - - describe("The domain object provider", function () { - var mockModelService, - mockInstantiate, - provider; - - function mockPromise(value) { - return (value && value.then) ? value : { - then: function (callback) { - return mockPromise(callback(value)); - }, - // Provide a synchronous way to get a value out - // of this phony promise. - testValue: value - }; - } - - beforeEach(function () { - mockModelService = jasmine.createSpyObj( - "modelService", - ["getModels"] - ); - mockInstantiate = jasmine.createSpy("instantiate"); - - mockInstantiate.and.callFake(function (model, id) { - return new DomainObjectImpl(id, model, {}); - }); - - provider = new DomainObjectProvider( - mockModelService, - mockInstantiate - ); - }); - - it("requests models from the model service", function () { - var ids = ["a", "b", "c"]; - mockModelService.getModels.and.returnValue(mockPromise({})); - provider.getObjects(ids); - expect(mockModelService.getModels).toHaveBeenCalledWith(ids); - }); - - it("instantiates objects with provided models", function () { - var ids = ["a", "b", "c"], - model = { someKey: "some value"}, - result; - mockModelService.getModels.and.returnValue(mockPromise({ a: model })); - result = provider.getObjects(ids).testValue; - expect(mockInstantiate).toHaveBeenCalledWith(model, 'a'); - expect(result.a.getId()).toEqual("a"); - expect(result.a.getModel()).toEqual(model); - }); - - }); - } -); diff --git a/platform/core/test/objects/DomainObjectSpec.js b/platform/core/test/objects/DomainObjectSpec.js deleted file mode 100644 index b024edb03e..0000000000 --- a/platform/core/test/objects/DomainObjectSpec.js +++ /dev/null @@ -1,87 +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. - *****************************************************************************/ - -/** - * DomainObjectSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/objects/DomainObjectImpl"], - function (DomainObject) { - - describe("A domain object", function () { - var testId = "test id", - testModel = { someKey: "some value"}, - testCapabilities = { - "static": "some static capability", - "dynamic": function (domainObject) { - return "Dynamically generated for " - + domainObject.getId(); - }, - "invokable": { - invoke: function (arg) { - return "invoked with " + arg; - } - } - }, - domainObject; - - beforeEach(function () { - domainObject = new DomainObject( - testId, - testModel, - testCapabilities - ); - }); - - it("reports its id", function () { - expect(domainObject.getId()).toEqual(testId); - }); - - it("reports its model", function () { - expect(domainObject.getModel()).toEqual(testModel); - }); - - it("reports static capabilities", function () { - expect(domainObject.getCapability("static")) - .toEqual("some static capability"); - }); - - it("instantiates dynamic capabilities", function () { - expect(domainObject.getCapability("dynamic")) - .toEqual("Dynamically generated for test id"); - }); - - it("allows for checking for the presence of capabilities", function () { - Object.keys(testCapabilities).forEach(function (capability) { - expect(domainObject.hasCapability(capability)).toBeTruthy(); - }); - expect(domainObject.hasCapability("somethingElse")).toBeFalsy(); - }); - - it("allows for shorthand capability invocation", function () { - expect(domainObject.useCapability("invokable", "a specific value")) - .toEqual("invoked with a specific value"); - }); - - }); - } -); diff --git a/platform/core/test/services/InstantiateSpec.js b/platform/core/test/services/InstantiateSpec.js deleted file mode 100644 index ffd2e281d1..0000000000 --- a/platform/core/test/services/InstantiateSpec.js +++ /dev/null @@ -1,108 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/services/Instantiate"], - function (Instantiate) { - - describe("The 'instantiate' service", function () { - - var mockCapabilityService, - mockIdentifierService, - mockCapabilityConstructor, - mockCapabilityInstance, - mockCacheService, - idCounter, - testModel, - instantiate, - domainObject; - - beforeEach(function () { - idCounter = 0; - - mockCapabilityService = jasmine.createSpyObj( - 'capabilityService', - ['getCapabilities'] - ); - mockIdentifierService = jasmine.createSpyObj( - 'identifierService', - ['parse', 'generate'] - ); - mockCapabilityConstructor = jasmine.createSpy('capability'); - mockCapabilityInstance = {}; - mockCapabilityService.getCapabilities.and.returnValue({ - something: mockCapabilityConstructor - }); - mockCapabilityConstructor.and.returnValue(mockCapabilityInstance); - - mockIdentifierService.generate.and.callFake(function (space) { - return (space ? (space + ":") : "") - + "some-id-" + (idCounter += 1); - }); - - mockCacheService = jasmine.createSpyObj( - 'cacheService', - ['get', 'put', 'remove', 'all'] - ); - - testModel = { someKey: "some value" }; - - instantiate = new Instantiate( - mockCapabilityService, - mockIdentifierService, - mockCacheService - ); - domainObject = instantiate(testModel); - }); - - it("loads capabilities from the capability service", function () { - expect(mockCapabilityService.getCapabilities) - .toHaveBeenCalledWith(testModel); - }); - - it("exposes loaded capabilities from the created object", function () { - expect(domainObject.getCapability('something')) - .toBe(mockCapabilityInstance); - expect(mockCapabilityConstructor) - .toHaveBeenCalledWith(domainObject); - }); - - it("exposes the provided model", function () { - expect(domainObject.getModel()).toEqual(testModel); - }); - - it("provides unique identifiers", function () { - expect(domainObject.getId()).toEqual(jasmine.any(String)); - expect(instantiate(testModel).getId()) - .not.toEqual(domainObject.getId()); - }); - - it("caches the instantiated model", function () { - expect(mockCacheService.put).toHaveBeenCalledWith( - domainObject.getId(), - testModel - ); - }); - }); - - } -); diff --git a/platform/core/test/services/ThrottleSpec.js b/platform/core/test/services/ThrottleSpec.js deleted file mode 100644 index d53b57d6d6..0000000000 --- a/platform/core/test/services/ThrottleSpec.js +++ /dev/null @@ -1,71 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/services/Throttle"], - function (Throttle) { - - describe("The 'throttle' service", function () { - var throttle, - mockTimeout, - mockFn, - mockPromise; - - beforeEach(function () { - mockTimeout = jasmine.createSpy("$timeout"); - mockPromise = jasmine.createSpyObj("promise", ["then"]); - mockFn = jasmine.createSpy("fn"); - mockTimeout.and.returnValue(mockPromise); - throttle = new Throttle(mockTimeout); - }); - - it("provides functions which run on a timeout", function () { - var throttled = throttle(mockFn); - // Verify precondition: Not called at throttle-time - expect(mockTimeout).not.toHaveBeenCalled(); - expect(throttled()).toEqual(mockPromise); - expect(mockFn).not.toHaveBeenCalled(); - expect(mockTimeout) - .toHaveBeenCalledWith(jasmine.any(Function), 0, false); - }); - - it("schedules only one timeout at a time", function () { - var throttled = throttle(mockFn); - throttled(); - throttled(); - throttled(); - expect(mockTimeout.calls.count()).toEqual(1); - }); - - it("schedules additional invocations after resolution", function () { - var throttled = throttle(mockFn); - throttled(); - mockTimeout.calls.mostRecent().args[0](); // Resolve timeout - throttled(); - mockTimeout.calls.mostRecent().args[0](); - throttled(); - mockTimeout.calls.mostRecent().args[0](); - expect(mockTimeout.calls.count()).toEqual(3); - }); - }); - } -); diff --git a/platform/core/test/services/TopicSpec.js b/platform/core/test/services/TopicSpec.js deleted file mode 100644 index 2bbd86dc10..0000000000 --- a/platform/core/test/services/TopicSpec.js +++ /dev/null @@ -1,88 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/services/Topic"], - function (Topic) { - - describe("The 'topic' service", function () { - var topic, - mockLog, - testMessage, - mockCallback; - - beforeEach(function () { - testMessage = { someKey: "some value"}; - mockLog = jasmine.createSpyObj( - '$log', - ['error', 'warn', 'info', 'debug'] - ); - mockCallback = jasmine.createSpy('callback'); - topic = new Topic(mockLog); - }); - - it("notifies listeners on a topic", function () { - topic("abc").listen(mockCallback); - topic("abc").notify(testMessage); - expect(mockCallback).toHaveBeenCalledWith(testMessage); - }); - - it("does not notify listeners across topics", function () { - topic("abc").listen(mockCallback); - topic("xyz").notify(testMessage); - expect(mockCallback).not.toHaveBeenCalledWith(testMessage); - }); - - it("does not notify listeners after unlistening", function () { - topic("abc").listen(mockCallback)(); // Unlisten immediately - topic("abc").notify(testMessage); - expect(mockCallback).not.toHaveBeenCalledWith(testMessage); - }); - - it("provides anonymous private topics", function () { - var t1 = topic(), t2 = topic(); - - t1.listen(mockCallback); - t2.notify(testMessage); - expect(mockCallback).not.toHaveBeenCalledWith(testMessage); - t1.notify(testMessage); - expect(mockCallback).toHaveBeenCalledWith(testMessage); - }); - - it("is robust against errors thrown by listeners", function () { - var mockBadCallback = jasmine.createSpy("bad-callback"), - t = topic(); - - mockBadCallback.and.callFake(function () { - throw new Error("I'm afraid I can't do that."); - }); - - t.listen(mockBadCallback); - t.listen(mockCallback); - - t.notify(testMessage); - expect(mockCallback).toHaveBeenCalledWith(testMessage); - }); - - }); - } -); diff --git a/platform/core/test/types/MergeModelsSpec.js b/platform/core/test/types/MergeModelsSpec.js deleted file mode 100644 index 47276a32a7..0000000000 --- a/platform/core/test/types/MergeModelsSpec.js +++ /dev/null @@ -1,63 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * MergeModelsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/types/MergeModels"], - function (mergeModels) { - - describe("Model merger", function () { - it("merges models", function () { - expect(mergeModels( - { - "a": "property a", - "b": [1, 2, 3], - "c": { - x: 42, - z: [0] - }, - "d": "should be ignored" - }, - { - "b": [4], - "c": { - y: "property y", - z: ["h"] - }, - "d": "property d" - } - )).toEqual({ - "a": "property a", - "b": [1, 2, 3, 4], - "c": { - x: 42, - y: "property y", - z: [0, "h"] - }, - "d": "property d" - }); - }); - }); - } -); diff --git a/platform/core/test/types/TypeCapabilitySpec.js b/platform/core/test/types/TypeCapabilitySpec.js deleted file mode 100644 index aa76c86684..0000000000 --- a/platform/core/test/types/TypeCapabilitySpec.js +++ /dev/null @@ -1,66 +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. - *****************************************************************************/ - -/** - * TypeCapabilitySpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/types/TypeCapability"], - function (TypeCapability) { - - describe("The type capability", function () { - var mockTypeService, - mockDomainObject, - mockType, - type; - - beforeEach(function () { - mockTypeService = jasmine.createSpyObj( - "typeService", - ["getType"] - ); - mockDomainObject = jasmine.createSpyObj( - "domainObject", - ["getId", "getModel", "getCapability"] - ); - mockType = { - someKey: "some value", - someOtherProperty: "some other property", - aThirdProperty: "a third property" - }; - - mockTypeService.getType.and.returnValue(mockType); - mockDomainObject.getModel.and.returnValue({type: "mockType"}); - - type = new TypeCapability(mockTypeService, mockDomainObject); - }); - - it("looks up an object's type from type service", function () { - expect(type.someKey).toEqual(mockType.someKey); - expect(type.someOtherProperty).toEqual(mockType.someOtherProperty); - expect(type.aThirdProperty).toEqual(mockType.aThirdProperty); - expect(mockTypeService.getType).toHaveBeenCalledWith("mockType"); - }); - - }); - } -); diff --git a/platform/core/test/types/TypeImplSpec.js b/platform/core/test/types/TypeImplSpec.js deleted file mode 100644 index 8a64f3994e..0000000000 --- a/platform/core/test/types/TypeImplSpec.js +++ /dev/null @@ -1,117 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['../../src/types/TypeImpl'], - function (TypeImpl) { - - describe("Type definition wrapper", function () { - var testTypeDef, - type; - - beforeEach(function () { - testTypeDef = { - key: 'test-type', - name: 'Test Type', - description: 'A type, for testing', - cssClass: 'icon-telemetry-panel', - inherits: ['test-parent-1', 'test-parent-2'], - features: ['test-feature-1'], - properties: [{}], - model: {someKey: "some value"} - }; - type = new TypeImpl(testTypeDef); - }); - - it("exposes key from definition", function () { - expect(type.getKey()).toEqual('test-type'); - }); - - it("exposes name from definition", function () { - expect(type.getName()).toEqual('Test Type'); - }); - - it("exposes description from definition", function () { - expect(type.getDescription()).toEqual('A type, for testing'); - }); - - it("exposes CSS class from definition", function () { - expect(type.getCssClass()).toEqual('icon-telemetry-panel'); - }); - - it("exposes its underlying type definition", function () { - expect(type.getDefinition()).toEqual(testTypeDef); - }); - - it("supports instance-of checks by type key", function () { - expect(type.instanceOf('test-parent-1')).toBeTruthy(); - expect(type.instanceOf('test-parent-2')).toBeTruthy(); - expect(type.instanceOf('some-other-type')).toBeFalsy(); - }); - - it("supports instance-of checks by specific type key", function () { - expect(type.instanceOf('test-type')).toBeTruthy(); - }); - - it("supports instance-of checks by type object", function () { - expect(type.instanceOf({ - getKey: function () { - return 'test-parent-1'; - } - })).toBeTruthy(); - expect(type.instanceOf({ - getKey: function () { - return 'some-other-type'; - } - })).toBeFalsy(); - }); - - it("correctly recognizes instance-of checks upon itself", function () { - expect(type.instanceOf(type)).toBeTruthy(); - }); - - it("recognizes that all types are instances of the undefined type", function () { - expect(type.instanceOf()).toBeTruthy(); - expect(type.instanceOf({ getKey: function () {} })).toBeTruthy(); - }); - - it("allows features to be exposed", function () { - expect(type.hasFeature('test-feature-1')).toBeTruthy(); - expect(type.hasFeature('test-feature-2')).toBeFalsy(); - }); - - it("provides an initial model, if defined", function () { - expect(type.getInitialModel().someKey).toEqual("some value"); - }); - - it("provides a fresh initial model each time", function () { - var model = type.getInitialModel(); - model.someKey = "some other value"; - expect(type.getInitialModel().someKey).toEqual("some value"); - }); - - it("provides type properties", function () { - expect(type.getProperties().length).toEqual(1); - }); - }); - } -); diff --git a/platform/core/test/types/TypePropertyConversionSpec.js b/platform/core/test/types/TypePropertyConversionSpec.js deleted file mode 100644 index c62e1047fd..0000000000 --- a/platform/core/test/types/TypePropertyConversionSpec.js +++ /dev/null @@ -1,65 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['../../src/types/TypePropertyConversion'], - function (TypePropertyConversion) { - - describe("Type property conversion", function () { - - it("allows non-conversion when parameter is 'identity'", function () { - var conversion = new TypePropertyConversion("identity"); - [42, "42", { a: 42 }].forEach(function (v) { - expect(conversion.toFormValue(v)).toBe(v); - expect(conversion.toModelValue(v)).toBe(v); - }); - }); - - it("allows numeric conversion", function () { - var conversion = new TypePropertyConversion("number"); - expect(conversion.toFormValue(42)).toBe("42"); - expect(conversion.toModelValue("42")).toBe(42); - }); - - it("supports array conversions", function () { - var conversion = new TypePropertyConversion("number[]"); - expect(conversion.toFormValue([42, 44]).length).toEqual(2); - expect(conversion.toFormValue([42, 44])[0]).toBe("42"); - expect(conversion.toModelValue(["11", "42"])[1]).toBe(42); - }); - - it("throws exceptions on unrecognized conversions", function () { - var caught = false; - - try { - // eslint-disable-next-line - new TypePropertyConversion("some-unknown-conversion"); - } catch (e) { - caught = true; - } - - expect(caught).toBeTruthy(); - }); - - }); - } -); diff --git a/platform/core/test/types/TypePropertySpec.js b/platform/core/test/types/TypePropertySpec.js deleted file mode 100644 index 4fb32a2e75..0000000000 --- a/platform/core/test/types/TypePropertySpec.js +++ /dev/null @@ -1,129 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['../../src/types/TypeProperty'], - function (TypeProperty) { - - describe("Type property", function () { - - it("allows retrieval of its definition", function () { - var definition = { - key: "hello", - someOtherKey: "hm?" - }; - expect( - new TypeProperty(definition).getDefinition() - ).toEqual(definition); - }); - - it("sets properties in object models", function () { - var definition = { - key: "someKey", - property: "someProperty" - }, - model = {}, - property = new TypeProperty(definition); - property.setValue(model, "some value"); - expect(model.someProperty).toEqual("some value"); - }); - - it("gets properties from object models", function () { - var definition = { - key: "someKey", - property: "someProperty" - }, - model = { someProperty: "some value"}, - property = new TypeProperty(definition); - expect(property.getValue(model)).toEqual("some value"); - }); - - it("sets properties by path", function () { - var definition = { - key: "someKey", - property: ["some", "property"] - }, - model = {}, - property = new TypeProperty(definition); - property.setValue(model, "some value"); - expect(model.some.property).toEqual("some value"); - }); - - it("gets properties by path", function () { - var definition = { - key: "someKey", - property: ["some", "property"] - }, - model = { some: { property: "some value" } }, - property = new TypeProperty(definition); - expect(property.getValue(model)).toEqual("some value"); - }); - - it("stops looking for properties when a path is invalid", function () { - var definition = { - key: "someKey", - property: ["some", "property"] - }, - property = new TypeProperty(definition); - expect(property.getValue(undefined)).toBeUndefined(); - }); - - it("gives undefined for empty paths", function () { - var definition = { - key: "someKey", - property: [] - }, - model = { some: { property: "some value" } }, - property = new TypeProperty(definition); - expect(property.getValue(model)).toBeUndefined(); - }); - - it("provides empty arrays for values that are array-like", function () { - var definition = { - property: "someProperty", - items: [{}, {}, {}] - }, - model = {}, - property = new TypeProperty(definition); - expect(property.getValue(model)) - .toEqual([undefined, undefined, undefined]); - }); - - it("detects and ignores empty arrays on setValue", function () { - var definition = { - property: "someProperty", - items: [{}, {}, {}] - }, - model = {}, - property = new TypeProperty(definition); - - property.setValue(model, [undefined, undefined, undefined]); - expect(model.someProperty).toBeUndefined(); - - // Verify that this only happens when all are undefined - property.setValue(model, [undefined, "x", 42]); - expect(model.someProperty).toEqual([undefined, "x", 42]); - }); - - }); - } -); diff --git a/platform/core/test/types/TypeProviderSpec.js b/platform/core/test/types/TypeProviderSpec.js deleted file mode 100644 index d3e293fde0..0000000000 --- a/platform/core/test/types/TypeProviderSpec.js +++ /dev/null @@ -1,147 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['../../src/types/TypeProvider'], - function (TypeProvider) { - - describe("Type provider", function () { - - var captured = {}, - testTypeDefinitions = [ - { - key: 'basic', - cssClass: "icon-magnify-in", - name: "Basic Type" - }, - { - key: 'multi1', - cssClass: "icon-trash", - description: "Multi1 Description", - capabilities: ['a1', 'b1'] - }, - { - key: 'multi2', - cssClass: "icon-magnify-out", - capabilities: ['a2', 'b2', 'c2'] - }, - { - key: 'single-subtype', - inherits: 'basic', - name: "Basic Subtype", - description: "A test subtype" - }, - { - key: 'multi-subtype', - inherits: ['multi1', 'multi2'], - name: "Multi-parent Subtype", - capabilities: ['a3'] - }, - { - name: "Default" - } - ], - provider; - - beforeEach(function () { - captured = {}; - provider = new TypeProvider(testTypeDefinitions); - }); - - it("looks up non-inherited types by name", function () { - captured.type = provider.getType('basic'); - - expect(captured.type.getCssClass()).toEqual("icon-magnify-in"); - expect(captured.type.getName()).toEqual("Basic Type"); - expect(captured.type.getDescription()).toBeUndefined(); - }); - - it("supports single inheritance", function () { - captured.type = provider.getType('single-subtype'); - - expect(captured.type.getCssClass()).toEqual("icon-magnify-in"); - expect(captured.type.getName()).toEqual("Basic Subtype"); - expect(captured.type.getDescription()).toEqual("A test subtype"); - }); - - it("supports multiple inheritance", function () { - captured.type = provider.getType('multi-subtype'); - - expect(captured.type.getCssClass()).toEqual("icon-magnify-out"); - expect(captured.type.getName()).toEqual("Multi-parent Subtype"); - expect(captured.type.getDescription()).toEqual("Multi1 Description"); - }); - - it("concatenates capabilities in order", function () { - captured.type = provider.getType('multi-subtype'); - - expect(captured.type.getDefinition().capabilities).toEqual( - ['a1', 'b1', 'a2', 'b2', 'c2', 'a3'] - ); - }); - - it("allows lookup of the undefined type", function () { - captured.type = provider.getType(undefined); - - expect(captured.type.getName()).toEqual("Default"); - }); - - it("concatenates capabilities of all undefined types", function () { - captured.type = new TypeProvider( - testTypeDefinitions.concat([ - { capabilities: ['a', 'b', 'c'] }, - { capabilities: ['x', 'y', 'z'] } - ]) - ).getType(undefined); - - expect(captured.type.getDefinition().capabilities).toEqual( - ['a', 'b', 'c', 'x', 'y', 'z'] - ); - - }); - - it("includes capabilities from undefined type in all types", function () { - captured.type = new TypeProvider( - testTypeDefinitions.concat([ - { capabilities: ['a', 'b', 'c'] }, - { capabilities: ['x', 'y', 'z'] } - ]) - ).getType('multi-subtype'); - - expect(captured.type.getDefinition().capabilities).toEqual( - ['a', 'b', 'c', 'x', 'y', 'z', 'a1', 'b1', 'a2', 'b2', 'c2', 'a3'] - ); - }); - - it("allows types to be listed", function () { - captured.types = provider.listTypes(); - - expect(captured.types.length).toEqual( - testTypeDefinitions.filter(function (t) { - return t.key; - }).length - ); - }); - - }); - } -); diff --git a/platform/core/test/views/ViewCapabilitySpec.js b/platform/core/test/views/ViewCapabilitySpec.js deleted file mode 100644 index e6085fc418..0000000000 --- a/platform/core/test/views/ViewCapabilitySpec.js +++ /dev/null @@ -1,58 +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. - *****************************************************************************/ - -/** - * ViewCapabilitySpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/views/ViewCapability"], - function (ViewCapability) { - - describe("A view capability", function () { - var mockViewService, - mockDomainObject, - views = [{key: "someView"}], - view; - - beforeEach(function () { - mockViewService = jasmine.createSpyObj( - "viewService", - ["getViews"] - ); - mockDomainObject = jasmine.createSpyObj( - "domainObject", - ["getId", "getModel", "getCapability"] - ); - mockViewService.getViews.and.returnValue(views); - view = new ViewCapability(mockViewService, mockDomainObject); - }); - - it("issues invocations to the view service", function () { - expect(view.invoke()).toEqual(views); - expect(mockViewService.getViews).toHaveBeenCalledWith( - mockDomainObject - ); - }); - - }); - } -); diff --git a/platform/core/test/views/ViewProviderSpec.js b/platform/core/test/views/ViewProviderSpec.js deleted file mode 100644 index 8682c8afc0..0000000000 --- a/platform/core/test/views/ViewProviderSpec.js +++ /dev/null @@ -1,167 +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. - *****************************************************************************/ - -/** - * ViewProviderSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/views/ViewProvider"], - function (ViewProvider) { - - describe("The view provider", function () { - var viewA = { - key: "a" - }, - viewB = { - key: "b", - needs: ["someCapability"] - }, - viewC = { - key: "c", - needs: ["someCapability"], - delegation: true - }, - capabilities = {}, - delegates = {}, - delegation, - mockDomainObject = {}, - mockLog, - provider; - - beforeEach(function () { - // Simulate the expected API - mockDomainObject.hasCapability = function (c) { - return capabilities[c] !== undefined; - }; - - mockDomainObject.getCapability = function (c) { - return capabilities[c]; - }; - - mockDomainObject.useCapability = function (c, v) { - return capabilities[c] && capabilities[c].invoke(v); - }; - - mockLog = jasmine.createSpyObj("$log", ["warn", "info", "debug"]); - - capabilities = {}; - delegates = {}; - - delegation = { - doesDelegateCapability: function (c) { - return delegates[c] !== undefined; - } - }; - - provider = new ViewProvider([viewA, viewB, viewC], mockLog); - }); - - it("reports views provided as extensions", function () { - capabilities.someCapability = true; - expect(provider.getViews(mockDomainObject)) - .toEqual([viewA, viewB, viewC]); - }); - - it("filters views by needed capabilities", function () { - //capabilities.someCapability = true; - expect(provider.getViews(mockDomainObject)) - .toEqual([viewA]); - }); - - it("allows delegation of needed capabilities when specified", function () { - //capabilities.someCapability = true; - capabilities.delegation = delegation; - delegates.someCapability = true; - expect(provider.getViews(mockDomainObject)) - .toEqual([viewA, viewC]); - }); - - it("warns if keys are omitted from views", function () { - // Verify that initial construction issued no warning - expect(mockLog.warn).not.toHaveBeenCalled(); - // Recreate with no keys; that view should be filtered out - expect( - new ViewProvider( - [viewA, { some: "bad view" }], - mockLog - ).getViews(mockDomainObject) - ).toEqual([viewA]); - // We should have also received a warning, to support debugging - expect(mockLog.warn).toHaveBeenCalledWith(jasmine.any(String)); - }); - - it("restricts typed views to matching types", function () { - var testType = "testType", - testView = { - key: "x", - type: testType - }, - viewProvider = new ViewProvider([testView], mockLog); - - // Include a "type" capability - capabilities.type = jasmine.createSpyObj( - "type", - ["instanceOf", "invoke", "getDefinition"] - ); - capabilities.type.invoke.and.returnValue(capabilities.type); - - // Should be included when types match - capabilities.type.instanceOf.and.returnValue(true); - expect(viewProvider.getViews(mockDomainObject)) - .toEqual([testView]); - expect(capabilities.type.instanceOf) - .toHaveBeenCalledWith(testType); - - // ...but not when they don't - capabilities.type.instanceOf.and.returnValue(false); - expect(viewProvider.getViews(mockDomainObject)) - .toEqual([]); - - }); - - it("enforces view restrictions from types", function () { - var testView = { key: "x" }, - viewProvider = new ViewProvider([testView], mockLog); - - // Include a "type" capability - capabilities.type = jasmine.createSpyObj( - "type", - ["instanceOf", "invoke", "getDefinition"] - ); - capabilities.type.invoke.and.returnValue(capabilities.type); - - // Should be included when view keys match - capabilities.type.getDefinition - .and.returnValue({ views: [testView.key]}); - expect(viewProvider.getViews(mockDomainObject)) - .toEqual([testView]); - - // ...but not when they don't - capabilities.type.getDefinition - .and.returnValue({ views: ["somethingElse"]}); - expect(viewProvider.getViews(mockDomainObject)) - .toEqual([]); - }); - - }); - } -); diff --git a/platform/data/README.md b/platform/data/README.md deleted file mode 100644 index dfcf85e2c9..0000000000 --- a/platform/data/README.md +++ /dev/null @@ -1,2 +0,0 @@ -This bundle is responsible for introducing a reusable infrastructure -and set of APIs for using time-series data in Open MCT. diff --git a/platform/entanglement/README.md b/platform/entanglement/README.md deleted file mode 100644 index aaf517decf..0000000000 --- a/platform/entanglement/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Entanglement - -Entanglement is the process of moving, copying, and linking domain objects -in such a way that their relationships are impossible to discern. - -This bundle provides move, copy, and link functionality. Achieving a state of -entanglement is left up to the end user. - - -## Services implement logic - -Each method (move, copy, link) is implemented as a service, and each service -provides two functions: `validate` and `perform`. - -`validate(object, parentCandidate)` returns true if the `object` can be -move/copy/linked into the `parentCandidate`'s composition. - -`perform(object, parentObject)` move/copy/links the `object` into the -`parentObject`'s composition. - -## Actions implement user interactions - -Actions are used to expose move/copy/link to the user. They prompt for input -where necessary, and complete the actions. diff --git a/platform/entanglement/bundle.js b/platform/entanglement/bundle.js deleted file mode 100644 index f4475d9a2e..0000000000 --- a/platform/entanglement/bundle.js +++ /dev/null @@ -1,124 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/actions/SetPrimaryLocationAction", - "./src/services/LocatingCreationDecorator", - "./src/services/LocatingObjectDecorator", - "./src/policies/CopyPolicy", - "./src/policies/CrossSpacePolicy", - "./src/capabilities/LocationCapability", - "./src/services/CopyService", - "./src/services/LocationService" -], function ( - SetPrimaryLocationAction, - LocatingCreationDecorator, - LocatingObjectDecorator, - CopyPolicy, - CrossSpacePolicy, - LocationCapability, - CopyService, - LocationService -) { - - return { - name: "platform/entanglement", - definition: { - "name": "Entanglement", - "description": "Tools to assist you in entangling the world of WARP.", - "configuration": {}, - "extensions": { - "actions": [ - { - "key": "locate", - "name": "Set Primary Location", - "description": "Set a domain object's primary location.", - "cssClass": "", - "category": "contextual", - "implementation": SetPrimaryLocationAction - } - ], - "components": [ - { - "type": "decorator", - "provides": "creationService", - "implementation": LocatingCreationDecorator - }, - { - "type": "decorator", - "provides": "objectService", - "implementation": LocatingObjectDecorator, - "depends": [ - "$q", - "$log" - ] - } - ], - "policies": [ - { - "category": "action", - "implementation": CrossSpacePolicy - }, - { - "category": "action", - "implementation": CopyPolicy - } - ], - "capabilities": [ - { - "key": "location", - "name": "Location Capability", - "description": "Provides a capability for retrieving the location of an object based upon it's context.", - "implementation": LocationCapability, - "depends": [ - "$q", - "$injector" - ] - } - ], - "services": [ - { - "key": "copyService", - "name": "Copy Service", - "description": "Provides a service for copying objects", - "implementation": CopyService, - "depends": [ - "$q", - "policyService", - "openmct" - ] - }, - { - "key": "locationService", - "name": "Location Service", - "description": "Provides a service for prompting a user for locations.", - "implementation": LocationService, - "depends": [ - "dialogService" - ] - } - ], - "licenses": [] - } - } - }; -}); diff --git a/platform/entanglement/src/actions/AbstractComposeAction.js b/platform/entanglement/src/actions/AbstractComposeAction.js deleted file mode 100644 index 2549a3b955..0000000000 --- a/platform/entanglement/src/actions/AbstractComposeAction.js +++ /dev/null @@ -1,163 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['./CancelError'], - function (CancelError) { - var CANCEL_MESSAGE = "User cancelled location selection."; - - /** - * Common interface exposed by services which support move, copy, - * and link actions. - * @interface platform/entanglement.AbstractComposeService - * @private - */ - /** - * Change the composition of the specified objects. Note that this - * should only be invoked after successfully validating. - * - * @param {DomainObject} domainObject the domain object to - * move, copy, or link. - * @param {DomainObject} parent the domain object whose composition - * will be changed to contain the domainObject (or its duplicate) - * @returns {Promise} A promise that is fulfilled when the - * duplicate operation has completed. - * @method platform/entanglement.AbstractComposeService#perform - */ - /** - * Check if this composition change is valid for these objects. - * - * @param {DomainObject} domainObject the domain object to - * move, copy, or link. - * @param {DomainObject} parent the domain object whose composition - * will be changed to contain the domainObject (or its duplicate) - * @returns {boolean} true if this composition change is allowed - * @method platform/entanglement.AbstractComposeService#validate - */ - - /** - * Template class for Move, Copy, and Link actions. - * - * @implements {Action} - * @constructor - * @private - * @memberof platform/entanglement - * @param {PolicyService} policyService the policy service to use to - * verify that variants of this action are allowed - * @param {platform/entanglement.LocationService} locationService a - * service to request destinations from the user - * @param {platform/entanglement.AbstractComposeService} composeService - * a service which will handle actual changes to composition - * @param {ActionContext} the context in which the action will be performed - * @param {string} verb the verb to display for the action (e.g. "Move") - * @param {string} [suffix] a string to display in the dialog title; - * default is "to a new location" - */ - function AbstractComposeAction( - policyService, - locationService, - composeService, - context, - verb, - suffix - ) { - if (context.selectedObject) { - this.newParent = context.domainObject; - this.object = context.selectedObject; - } else { - this.object = context.domainObject; - } - - this.currentParent = this.object - .getCapability('context') - .getParent(); - - this.context = context; - this.policyService = policyService; - this.locationService = locationService; - this.composeService = composeService; - this.verb = verb || "Compose"; - this.suffix = suffix || "To a New Location"; - } - - AbstractComposeAction.prototype.cloneContext = function () { - var clone = {}, original = this.context; - Object.keys(original).forEach(function (k) { - clone[k] = original[k]; - }); - - return clone; - }; - - AbstractComposeAction.prototype.perform = function () { - var dialogTitle, - label, - validateLocation, - self = this, - locationService = this.locationService, - composeService = this.composeService, - currentParent = this.currentParent, - newParent = this.newParent, - object = this.object; - - if (newParent) { - return composeService.perform(object, newParent); - } - - dialogTitle = [this.verb, object.getModel().name, this.suffix] - .join(" "); - - label = this.verb + " To"; - - validateLocation = function (newParentObj) { - var newContext = self.cloneContext(); - newContext.selectedObject = object; - newContext.domainObject = newParentObj; - - return composeService.validate(object, newParentObj) - && self.policyService.allow("action", self, newContext); - }; - - return locationService.getLocationFromUser( - dialogTitle, - label, - validateLocation, - currentParent - ).then(function (newParentObj) { - return composeService.perform(object, newParentObj); - }, function () { - return Promise.reject(new CancelError(CANCEL_MESSAGE)); - }.bind(this)); - }; - - AbstractComposeAction.appliesTo = function (context) { - var applicableObject = - context.selectedObject || context.domainObject; - - return Boolean(applicableObject - && applicableObject.hasCapability('context')); - }; - - return AbstractComposeAction; - } -); - diff --git a/platform/entanglement/src/actions/SetPrimaryLocationAction.js b/platform/entanglement/src/actions/SetPrimaryLocationAction.js deleted file mode 100644 index a5c4a257da..0000000000 --- a/platform/entanglement/src/actions/SetPrimaryLocationAction.js +++ /dev/null @@ -1,66 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - function () { - - /** - * Implements the "Set Primary Location" action, which sets a - * location property for objects to match their contextual - * location. - * - * @implements {Action} - * @constructor - * @private - * @memberof platform/entanglement - * @param {ActionContext} context the context in which the action - * will be performed - */ - function SetPrimaryLocationAction(context) { - this.domainObject = context.domainObject; - } - - SetPrimaryLocationAction.prototype.perform = function () { - var location = this.domainObject.getCapability('location'); - - return location.setPrimaryLocation( - location.getContextualLocation() - ); - }; - - SetPrimaryLocationAction.appliesTo = function (context, view, openmct) { - let domainObject = (context || {}).domainObject; - - if (!domainObject || (domainObject.model && domainObject.model.locked)) { - return false; - } - - let isPersistable = openmct.objects.isPersistable(domainObject.id); - - return isPersistable && domainObject.hasCapability("location") - && (domainObject.getModel().location === undefined); - }; - - return SetPrimaryLocationAction; - } -); - diff --git a/platform/entanglement/src/capabilities/LocationCapability.js b/platform/entanglement/src/capabilities/LocationCapability.js deleted file mode 100644 index 6f45ed8bb0..0000000000 --- a/platform/entanglement/src/capabilities/LocationCapability.js +++ /dev/null @@ -1,128 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - function () { - - /** - * The location capability allows a domain object to know its current - * parent, and also know its original parent. When a domain object's - * current parent is its original parent, the object is considered an - * original, otherwise it's a link. - * - * @constructor - */ - function LocationCapability($q, $injector, domainObject) { - this.domainObject = domainObject; - this.$q = $q; - this.$injector = $injector; - - return this; - } - - /** - * Get an instance of this domain object in its original location. - * - * @returns {Promise.} a promise for the original - * instance of this domain object - */ - LocationCapability.prototype.getOriginal = function () { - var id; - - if (this.isOriginal()) { - return this.$q.when(this.domainObject); - } - - id = this.domainObject.getId(); - - this.objectService = - this.objectService || this.$injector.get("objectService"); - - // Assume that an object will be correctly contextualized when - // loaded directly from the object service; this is true - // so long as LocatingObjectDecorator is present, and that - // decorator is also contained in this bundle. - return this.objectService.getObjects([id]) - .then(function (objects) { - return objects[id]; - }); - }; - - /** - * Set the primary location (the parent id) of the current domain - * object. - * - * @param {String} location the primary location to persist. - * @returns {Promise} a promise that is resolved when the operation - * completes. - */ - LocationCapability.prototype.setPrimaryLocation = function (location) { - return this.domainObject.useCapability( - 'mutation', - function (model) { - model.location = location; - } - ); - }; - - /** - * Returns the contextual location of the current domain object. Only - * valid for domain objects that have a context capability. - * - * @returns {String} the contextual location of the object; the id of - * its parent. - */ - LocationCapability.prototype.getContextualLocation = function () { - var context = this.domainObject.getCapability("context"); - - if (!context) { - return; - } - - return context.getParent().getId(); - }; - - /** - * Returns true if the domainObject is a link, false if it's an - * original. - * - * @returns {Boolean} - */ - LocationCapability.prototype.isLink = function () { - var model = this.domainObject.getModel(); - - return model.location !== this.getContextualLocation(); - }; - - /** - * Returns true if the domainObject is an original, false if it's a - * link. - * - * @returns {Boolean} - */ - LocationCapability.prototype.isOriginal = function () { - return !this.isLink(); - }; - - return LocationCapability; - } -); diff --git a/platform/entanglement/src/policies/CrossSpacePolicy.js b/platform/entanglement/src/policies/CrossSpacePolicy.js deleted file mode 100644 index 04759f94ec..0000000000 --- a/platform/entanglement/src/policies/CrossSpacePolicy.js +++ /dev/null @@ -1,69 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - var DISALLOWED_ACTIONS = ["move"]; - - /** - * This policy prevents performing move/copy/link actions across - * different persistence spaces (e.g. linking to an object in - * a private space from an object in a public space.) - * @memberof {platform/entanglement} - * @constructor - * @implements {Policy} - */ - function CrossSpacePolicy() { - } - - function lookupSpace(domainObject) { - var persistence = domainObject - && domainObject.getCapability("persistence"); - - return persistence && persistence.getSpace(); - } - - function isCrossSpace(context) { - var domainObject = context.domainObject, - selectedObject = context.selectedObject; - - return selectedObject !== undefined - && domainObject !== undefined - && lookupSpace(domainObject) !== lookupSpace(selectedObject); - } - - CrossSpacePolicy.prototype.allow = function (action, context) { - var key = action.getMetadata().key; - - if (DISALLOWED_ACTIONS.indexOf(key) !== -1) { - return !isCrossSpace(context); - } - - return true; - }; - - return CrossSpacePolicy; - - } -); diff --git a/platform/entanglement/src/services/CopyService.js b/platform/entanglement/src/services/CopyService.js deleted file mode 100644 index f2ff8294b2..0000000000 --- a/platform/entanglement/src/services/CopyService.js +++ /dev/null @@ -1,109 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["./CopyTask"], - function (CopyTask) { - - /** - * CopyService provides an interface for deep copying objects from one - * location to another. It also provides a method for determining if - * an object can be copied to a specific location. - * @constructor - * @memberof platform/entanglement - * @implements {platform/entanglement.AbstractComposeService} - */ - function CopyService($q, policyService, openmct) { - this.$q = $q; - this.policyService = policyService; - this.openmct = openmct; - } - - CopyService.prototype.validate = function (object, parentCandidate) { - if (!parentCandidate || !parentCandidate.getId) { - return false; - } - - if (parentCandidate.getId() === object.getId()) { - return false; - } - - return this.openmct.composition.checkPolicy(parentCandidate.useCapability('adapter'), object.useCapability('adapter')); - }; - - /** - * A function used to check if a domain object should be cloned - * or not. - * @callback platform/entanglement.CopyService~filter - * @param {DomainObject} domainObject the object to be cloned - * @returns {boolean} true if the object should be cloned; false - * if it should be linked - */ - - /** - * Creates a duplicate of the object tree starting at domainObject to - * the new parent specified. - * - * Any domain objects which cannot be created will not be cloned; - * instead, these will appear as links. If a filtering function - * is provided, any objects which fail that check will also be - * linked instead of cloned - * - * @param {DomainObject} domainObject the object to duplicate - * @param {DomainObject} parent the destination for the clone - * @param {platform/entanglement.CopyService~filter} [filter] - * an optional function used to filter out objects from - * the cloning process - * @returns a promise that will be completed with the clone of - * domainObject when the duplication is successful. - */ - CopyService.prototype.perform = function (domainObject, parent, filter) { - var policyService = this.policyService; - - // Combines caller-provided filter (if any) with the - // baseline behavior of respecting creation policy. - function filterWithPolicy(domainObj) { - return (!filter || filter(domainObj)) - && policyService.allow( - "creation", - domainObj.getCapability("type") - ); - } - - if (this.validate(domainObject, parent)) { - return new CopyTask( - domainObject, - parent, - filterWithPolicy, - this.$q - ).perform(); - } else { - throw new Error( - "Tried to copy objects without validating first." - ); - } - }; - - return CopyService; - } -); - diff --git a/platform/entanglement/src/services/CopyTask.js b/platform/entanglement/src/services/CopyTask.js deleted file mode 100644 index 326ee176b0..0000000000 --- a/platform/entanglement/src/services/CopyTask.js +++ /dev/null @@ -1,276 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * This class encapsulates the process of copying a domain object - * and all of its children. - * - * @param {DomainObject} domainObject The object to copy - * @param {DomainObject} parent The new location of the cloned object tree - * @param {platform/entanglement.CopyService~filter} filter - * a function used to filter out objects from - * the cloning process - * @param $q Angular's $q, for promises - * @constructor - */ - function CopyTask(domainObject, parent, filter, $q) { - this.domainObject = domainObject; - this.parent = parent; - this.firstClone = undefined; - this.$q = $q; - this.deferred = undefined; - this.filter = filter; - this.persisted = 0; - this.clones = []; - this.idMap = {}; - } - - function composeChild(child, parent, setLocation) { - //Once copied, associate each cloned - // composee with its parent clone - - parent.getModel().composition.push(child.getId()); - - //If a location is not specified, set it. - if (setLocation && child.getModel().location === undefined) { - child.getModel().location = parent.getId(); - } - - return child; - } - - function cloneObjectModel(objectModel) { - var clone = JSON.parse(JSON.stringify(objectModel)); - - /** - * Reset certain fields. - */ - //If has a composition, set it to an empty array. Will be - // recomposed later with the ids of its cloned children. - if (clone.composition) { - //Important to set it to an empty array here, otherwise - // hasCapability("composition") returns false; - clone.composition = []; - } - - delete clone.persisted; - delete clone.modified; - delete clone.location; - - return clone; - } - - /** - * Will persist a list of {@link objectClones}. It will persist all - * simultaneously, irrespective of order in the list. This may - * result in automatic request batching by the browser. - */ - function persistObjects(self) { - return self.$q.all(self.clones.map(function (clone) { - return clone.getCapability("persistence").persist().then(function () { - self.deferred.notify({ - phase: "copying", - totalObjects: self.clones.length, - processed: ++self.persisted - }); - }); - })).then(function () { - return self; - }); - } - - /** - * Will add a list of clones to the specified parent's composition - */ - function addClonesToParent(self) { - return self.parent.getCapability("composition") - .add(self.firstClone) - .then(function (addedClone) { - return self.parent.getCapability("persistence").persist() - .then(function () { - return addedClone; - }); - }); - } - - /** - * Update identifiers in a cloned object model (or part of - * a cloned object model) to reflect new identifiers after - * copying. - * @private - */ - CopyTask.prototype.rewriteIdentifiers = function (obj, idMap) { - function lookupValue(value) { - return (typeof value === 'string' && idMap[value]) || value; - } - - if (Array.isArray(obj)) { - obj.forEach(function (value, index) { - obj[index] = lookupValue(value); - this.rewriteIdentifiers(obj[index], idMap); - }, this); - } else if (obj && typeof obj === 'object') { - Object.keys(obj).forEach(function (key) { - var value = obj[key]; - obj[key] = lookupValue(value); - if (idMap[key]) { - delete obj[key]; - obj[idMap[key]] = value; - } - - this.rewriteIdentifiers(value, idMap); - }, this); - } - }; - - /** - * Given an array of objects composed by a parent, clone them, then - * add them to the parent. - * @private - * @returns {*} - */ - CopyTask.prototype.copyComposees = function (composees, clonedParent, originalParent) { - var self = this, - idMap = {}; - - return (composees || []).reduce(function (promise, originalComposee) { - //If the composee is composed of other - // objects, chain a promise.. - return promise.then(function () { - // ...to recursively copy it (and its children) - return self.copy(originalComposee, originalParent).then(function (clonedComposee) { - //Map the original composee's ID to that of its - // clone so that we can replace any references to it - // in the parent - idMap[originalComposee.getId()] = clonedComposee.getId(); - - //Compose the child within its parent. Cloned - // objects will need to also have their location - // set, however linked objects will not. - return composeChild(clonedComposee, clonedParent, clonedComposee !== originalComposee); - }); - }); - }, self.$q.when(undefined) - ).then(function () { - //Replace any references in the cloned parent to - // contained objects that have been composed with the - // Ids of the clones - self.rewriteIdentifiers(clonedParent.getModel(), idMap); - - //Add the clone to the list of clones that will - //be returned by this function - self.clones.push(clonedParent); - - return clonedParent; - }); - }; - - /** - * A recursive function that will perform a bottom-up copy of - * the object tree with originalObject at the root. Recurses to - * the farthest leaf, then works its way back up again, - * cloning objects, and composing them with their child clones - * as it goes - * @private - * @returns {DomainObject} If the type of the original object allows for - * duplication, then a duplicate of the object, otherwise the object - * itself (to allow linking to non duplicatable objects). - */ - CopyTask.prototype.copy = function (originalObject) { - var self = this, - clone; - - //Check if the type of the object being copied allows for - // creation of new instances. If it does not, then a link to the - // original will be created instead. - if (this.filter(originalObject)) { - //create a new clone of the original object. Use the - // creation capability of the targetParent to create the - // new clone. This will ensure that the correct persistence - // space is used. - clone = this.parent.useCapability("instantiation", cloneObjectModel(originalObject.getModel())); - - //Iterate through child tree - return this.$q.when(originalObject.useCapability('composition')).then(function (composees) { - self.deferred.notify({phase: "preparing"}); - - //Duplicate the object's children, and their children, and - // so on down to the leaf nodes of the tree. - //If it is a link, don't both with children - return self.copyComposees(composees, clone, originalObject); - }); - } else { - //Creating a link, no need to iterate children - return self.$q.when(originalObject); - } - - }; - - /** - * Will build a graph of an object and all of its child objects in - * memory - * @private - * @param domainObject The original object to be copied - * @param parent The parent of the original object to be copied - * @returns {Promise} resolved with an array of clones of the models - * of the object tree being copied. Copying is done in a bottom-up - * fashion, so that the last member in the array is a clone of the model - * object being copied. The clones are all full composed with - * references to their own children. - */ - CopyTask.prototype.buildCopyPlan = function () { - var self = this; - - return this.copy(self.domainObject, self.parent).then(function (domainObjectClone) { - if (domainObjectClone !== self.domainObject) { - domainObjectClone.getModel().location = self.parent.getId(); - } - - self.firstClone = domainObjectClone; - - return self; - }); - }; - - /** - * Execute the copy task with the objects provided in the constructor. - * @returns {promise} Which will resolve with a clone of the object - * once complete. - */ - CopyTask.prototype.perform = function () { - this.deferred = this.$q.defer(); - - this.buildCopyPlan() - .then(persistObjects) - .then(addClonesToParent) - .then(this.deferred.resolve, this.deferred.reject); - - return this.deferred.promise; - }; - - return CopyTask; - } -); diff --git a/platform/entanglement/src/services/LocatingCreationDecorator.js b/platform/entanglement/src/services/LocatingCreationDecorator.js deleted file mode 100644 index 36cfae1c07..0000000000 --- a/platform/entanglement/src/services/LocatingCreationDecorator.js +++ /dev/null @@ -1,47 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - function () { - - /** - * Adds a `location` property to newly-created domain objects. - * @constructor - * @augments {platform/commonUI/browse.CreationService} - * @memberof platform/entanglement - */ - function LocatingCreationDecorator(creationService) { - this.creationService = creationService; - } - - LocatingCreationDecorator.prototype.createObject = function (model, parent) { - if (parent && parent.getId) { - model.location = parent.getId(); - } - - return this.creationService.createObject(model, parent); - }; - - return LocatingCreationDecorator; - } -); - diff --git a/platform/entanglement/src/services/LocatingObjectDecorator.js b/platform/entanglement/src/services/LocatingObjectDecorator.js deleted file mode 100644 index ecdcec64e7..0000000000 --- a/platform/entanglement/src/services/LocatingObjectDecorator.js +++ /dev/null @@ -1,95 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['../../../core/src/capabilities/ContextualDomainObject'], - function (ContextualDomainObject) { - - /** - * Ensures that domain objects are loaded with a context capability - * that reflects their location. - * @constructor - * @implements {ObjectService} - * @memberof platform/entanglement - */ - function LocatingObjectDecorator($q, $log, objectService) { - this.$log = $log; - this.objectService = objectService; - this.$q = $q; - } - - LocatingObjectDecorator.prototype.getObjects = function (ids, abortSignal) { - var $q = this.$q, - $log = this.$log, - objectService = this.objectService, - result = {}; - - // Load a single object using location to establish a context - function loadObjectInContext(id, exclude) { - function attachContext(objects) { - var domainObject = (objects || {})[id], - model = domainObject && domainObject.getModel(), - location = (model || {}).location; - - // If no location is defined, we can't look up a context. - if (!location) { - return domainObject; - } - - // Avoid looping indefinitely on cyclical locations - if (exclude[id]) { - $log.warn([ - "LocatingObjectDecorator detected a cycle", - "while attempted to define a context for", - id + ";", - "no context will be added and unexpected behavior", - "may follow." - ].join(" ")); - - return domainObject; - } - - // Record that we've visited this ID to detect cycles. - exclude[id] = true; - - // Do the recursive step to get the parent... - return loadObjectInContext(location, exclude) - .then(function (parent) { - // ...and then contextualize with it! - return new ContextualDomainObject(domainObject, parent); - }); - } - - return objectService.getObjects([id], abortSignal).then(attachContext); - } - - ids.forEach(function (id) { - result[id] = loadObjectInContext(id, {}); - }); - - return $q.all(result); - }; - - return LocatingObjectDecorator; - } -); - diff --git a/platform/entanglement/src/services/LocationService.js b/platform/entanglement/src/services/LocationService.js deleted file mode 100644 index 4490c4c422..0000000000 --- a/platform/entanglement/src/services/LocationService.js +++ /dev/null @@ -1,90 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * This bundle implements actions which control the location of objects - * (move, copy, link.) - * @namespace platform/entanglement - */ -define( - function () { - - /** - * The LocationService allows for easily prompting the user for a - * location in the root tree. - * @constructor - * @memberof platform/entanglement - */ - function LocationService(dialogService) { - return { - /** Prompt the user to select a location. Returns a promise - * that is resolved with a domainObject representing the - * location selected by the user. - * - * @param {string} title - title of location dialog - * @param {string} label - label for location input field - * @param {function} validate - function that validates - * selections. - * @param {domainObject} initialLocation - tree location to - * display at start - * @returns {Promise} promise for a domain object. - * @memberof platform/entanglement.LocationService# - */ - getLocationFromUser: function (title, label, validate, initialLocation) { - var formStructure, - formState; - - formStructure = { - sections: [ - { - name: 'Location', - cssClass: "grows", - rows: [ - { - name: label, - control: "locator", - validate: validate, - key: 'location' - } - ] - } - ], - name: title - }; - - formState = { - location: initialLocation - }; - - return dialogService - .getUserInput(formStructure, formState) - .then(function (userFormState) { - return userFormState.location; - }); - } - }; - } - - return LocationService; - } -); - diff --git a/platform/entanglement/test/ControlledPromise.js b/platform/entanglement/test/ControlledPromise.js deleted file mode 100644 index 11eb3bfb13..0000000000 --- a/platform/entanglement/test/ControlledPromise.js +++ /dev/null @@ -1,97 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - function () { - - /** - * An instrumented promise implementation for better control of promises - * during tests. - * - */ - function ControlledPromise() { - this.resolveHandlers = []; - this.rejectHandlers = []; - spyOn(this, 'then').and.callThrough(); - } - - /** - * Resolve the promise, passing the supplied value to all resolve - * handlers. - */ - ControlledPromise.prototype.resolve = function (value) { - this.resolveHandlers.forEach(function (handler) { - handler(value); - }); - }; - - /** - * Reject the promise, passing the supplied value to all rejection - * handlers. - */ - ControlledPromise.prototype.reject = function (value) { - this.rejectHandlers.forEach(function (handler) { - handler(value); - }); - }; - - /** - * Standard promise.then, returns a promise that support chaining. - * TODO: Need to support resolve/reject handlers that return promises. - */ - ControlledPromise.prototype.then = function (onResolve, onReject) { - var returnPromise = new ControlledPromise(); - - if (onResolve) { - this.resolveHandlers.push(function (resolveWith) { - var chainResult = onResolve(resolveWith); - if (chainResult && chainResult.then) { - // chainResult is a promise, resolve when it resolves. - chainResult.then(function (pipedResult) { - return returnPromise.resolve(pipedResult); - }); - } else { - returnPromise.resolve(chainResult); - } - }); - } - - if (onReject) { - this.rejectHandlers.push(function (rejectWith) { - var chainResult = onReject(rejectWith); - if (chainResult && chainResult.then) { - chainResult.then(function (pipedResult) { - returnPromise.reject(pipedResult); - }); - } else { - returnPromise.reject(chainResult); - } - }); - } - - return returnPromise; - }; - - return ControlledPromise; - - } -); diff --git a/platform/entanglement/test/DomainObjectFactory.js b/platform/entanglement/test/DomainObjectFactory.js deleted file mode 100644 index 834646a5c7..0000000000 --- a/platform/entanglement/test/DomainObjectFactory.js +++ /dev/null @@ -1,160 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - function () { - - /** - * @typedef DomainObjectConfig - * @type {object} - * @property {string} [name] a name for the underlying jasmine spy - * object mockDomainObject. Used as - * @property {string} [id] initial id value for the domainOBject. - * @property {object} [model] initial values for the object's model. - * @property {object} [capabilities] an object containing - * capability definitions. - */ - - var configObjectProps = ['model', 'capabilities']; - - /** - * Internal function for ensuring an object is an instance of a - * DomainObjectConfig. - */ - function ensureValidConfigObject(config) { - if (!config || !config.hasOwnProperty) { - config = {}; - } - - if (!config.name) { - config.name = 'domainObject'; - } - - configObjectProps.forEach(function (prop) { - if (!config[prop] || !config[prop].hasOwnProperty) { - config[prop] = {}; - } - }); - - return config; - } - - /** - * Defines a factory function which takes a `config` object and returns - * a mock domainObject. The config object is an easy way to provide - * initial properties for the domainObject-- they can be changed at any - * time by directly modifying the domainObject's properties. - * - * @param {Object} [config] initial configuration for a domain object. - * @returns {Object} mockDomainObject - */ - function domainObjectFactory(config) { - config = ensureValidConfigObject(config); - - var domainObject = jasmine.createSpyObj(config.name, [ - 'getId', - 'getModel', - 'getCapability', - 'hasCapability', - 'useCapability' - ]); - - domainObject.model = JSON.parse(JSON.stringify(config.model)); - domainObject.capabilities = config.capabilities; - domainObject.id = config.id; - - /** - * getId: Returns `domainObject.id`. - * - * @returns {string} id - */ - domainObject.getId.and.callFake(function () { - return domainObject.id; - }); - - /** - * getModel: Returns `domainObject.model`. - * - * @returns {object} model - */ - domainObject.getModel.and.callFake(function () { - return domainObject.model; - }); - - /** - * getCapability: returns a `capability` object defined in - * domainObject.capabilities. Returns undefined if capability - * does not exist. - * - * @param {string} capability name of the capability to return. - * @returns {*} capability object - */ - domainObject.getCapability.and.callFake(function (capability) { - if (Object.prototype.hasOwnProperty.call(config.capabilities, capability)) { - return config.capabilities[capability]; - } - }); - - /** - * hasCapability: return true if domainObject.capabilities has a - * property named `capability`, otherwise returns false. - * - * @param {string} capability name of the capability to test for - * existence of. - * @returns {boolean} - */ - domainObject.hasCapability.and.callFake(function (capability) { - return Object.prototype.hasOwnProperty.call(config.capabilities, capability); - }); - - /** - * useCapability: find a capability in domainObject.capabilities - * and call that capabilities' invoke method. If the capability - * does not have an invoke method, will throw an error. - * - * @param {string} capability name of a capability to invoke. - * @param {...*} params to pass to the capability's `invoke` method. - * @returns {*} result whatever was returned by `invoke`. - */ - domainObject.useCapability.and.callFake(function (capability) { - if (Object.prototype.hasOwnProperty.call(config.capabilities, capability)) { - if (!config.capabilities[capability].invoke) { - throw new Error( - capability + ' missing invoke function.' - ); - } - - var passThroughArgs = [].slice.call(arguments, 1); - - return config - .capabilities[capability] - .invoke - .apply(null, passThroughArgs); - } - }); - - return domainObject; - } - - return domainObjectFactory; - } -); diff --git a/platform/entanglement/test/actions/AbstractComposeActionSpec.js b/platform/entanglement/test/actions/AbstractComposeActionSpec.js deleted file mode 100644 index e55ad9360e..0000000000 --- a/platform/entanglement/test/actions/AbstractComposeActionSpec.js +++ /dev/null @@ -1,227 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - '../../src/actions/AbstractComposeAction', - '../services/MockCopyService', - '../DomainObjectFactory' - ], - function (AbstractComposeAction, MockCopyService, domainObjectFactory) { - - describe("Move/copy/link Actions", function () { - - var action, - policyService, - locationService, - locationServicePromise, - composeService, - context, - selectedObject, - selectedObjectContextCapability, - currentParent, - newParent; - - beforeEach(function () { - policyService = jasmine.createSpyObj( - 'policyService', - ['allow'] - ); - - selectedObjectContextCapability = jasmine.createSpyObj( - 'selectedObjectContextCapability', - [ - 'getParent' - ] - ); - - selectedObject = domainObjectFactory({ - name: 'selectedObject', - model: { - name: 'selectedObject' - }, - capabilities: { - context: selectedObjectContextCapability - } - }); - - currentParent = domainObjectFactory({ - name: 'currentParent' - }); - - selectedObjectContextCapability - .getParent - .and.returnValue(currentParent); - - newParent = domainObjectFactory({ - name: 'newParent' - }); - - locationService = jasmine.createSpyObj( - 'locationService', - [ - 'getLocationFromUser' - ] - ); - - locationServicePromise = jasmine.createSpyObj( - 'locationServicePromise', - [ - 'then' - ] - ); - - policyService.allow.and.returnValue(true); - - locationService - .getLocationFromUser - .and.returnValue(locationServicePromise); - - composeService = new MockCopyService(); - }); - - it("are only applicable to domain objects with a context", function () { - var noContextObject = domainObjectFactory({ - name: 'selectedObject', - model: { name: 'selectedObject' }, - capabilities: {} - }); - - expect(AbstractComposeAction.appliesTo({ - selectedObject: selectedObject - })).toBe(true); - expect(AbstractComposeAction.appliesTo({ - domainObject: selectedObject - })).toBe(true); - - expect(AbstractComposeAction.appliesTo({ - selectedObject: noContextObject - })).toBe(false); - expect(AbstractComposeAction.appliesTo({ - domainObject: noContextObject - })).toBe(false); - }); - - describe("with context from context-action", function () { - beforeEach(function () { - context = { - domainObject: selectedObject - }; - - action = new AbstractComposeAction( - policyService, - locationService, - composeService, - context, - "Compose" - ); - }); - - it("initializes happily", function () { - expect(action).toBeDefined(); - }); - - describe("when performed it", function () { - beforeEach(function () { - action.perform(); - }); - - it("prompts for location", function () { - expect(locationService.getLocationFromUser) - .toHaveBeenCalledWith( - "Compose selectedObject To a New Location", - "Compose To", - jasmine.any(Function), - currentParent - ); - }); - - it("waits for location and handles cancellation by user", function () { - expect(locationServicePromise.then) - .toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function)); - }); - - it("copies object to selected location", function () { - locationServicePromise - .then - .calls.mostRecent() - .args[0](newParent); - - expect(composeService.perform) - .toHaveBeenCalledWith(selectedObject, newParent); - }); - - describe("provides a validator which", function () { - var validator; - - beforeEach(function () { - validator = locationService.getLocationFromUser - .calls.mostRecent().args[2]; - composeService.validate.and.returnValue(true); - policyService.allow.and.returnValue(true); - }); - - it("is sensitive to policy", function () { - expect(validator()).toBe(true); - policyService.allow.and.returnValue(false); - expect(validator()).toBe(false); - }); - - it("is sensitive to service-specific validation", function () { - expect(validator()).toBe(true); - composeService.validate.and.returnValue(false); - expect(validator()).toBe(false); - }); - - }); - }); - }); - - describe("with context from drag-drop", function () { - beforeEach(function () { - context = { - selectedObject: selectedObject, - domainObject: newParent - }; - - action = new AbstractComposeAction( - policyService, - locationService, - composeService, - context, - "Compose" - ); - }); - - it("initializes happily", function () { - expect(action).toBeDefined(); - }); - - it("performs copy immediately", function () { - action.perform(); - expect(composeService.perform) - .toHaveBeenCalledWith(selectedObject, newParent); - }); - }); - }); - } -); diff --git a/platform/entanglement/test/actions/SetPrimaryLocationActionSpec.js b/platform/entanglement/test/actions/SetPrimaryLocationActionSpec.js deleted file mode 100644 index 317ceb7e3d..0000000000 --- a/platform/entanglement/test/actions/SetPrimaryLocationActionSpec.js +++ /dev/null @@ -1,88 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - '../../src/actions/SetPrimaryLocationAction', - '../DomainObjectFactory' - ], - function (SetPrimaryLocation, domainObjectFactory) { - - describe("The 'set primary location' action", function () { - var testContext, - testModel, - testId, - mockLocationCapability, - openmct; - - beforeEach(function () { - openmct = { - objects: { - isPersistable: jasmine.createSpy('isPersistable') - } - }; - testId = "some-id"; - testModel = { name: "some name" }; - - mockLocationCapability = jasmine.createSpyObj( - 'location', - ['setPrimaryLocation', 'getContextualLocation'] - ); - - openmct.objects.isPersistable.and.returnValue(true); - mockLocationCapability.getContextualLocation.and.returnValue(testId); - - testContext = { - domainObject: domainObjectFactory({ - capabilities: { - location: mockLocationCapability - }, - model: testModel - }) - }; - }); - - it("is applicable to objects with no location specified", function () { - expect(SetPrimaryLocation.appliesTo(testContext, undefined, openmct)) - .toBe(true); - testContext.domainObject.getModel.and.returnValue({ - location: "something", - name: "some name" - }); - expect(SetPrimaryLocation.appliesTo(testContext, undefined, openmct)) - .toBe(false); - }); - - it("checks object persistability", function () { - SetPrimaryLocation.appliesTo(testContext, undefined, openmct); - expect(openmct.objects.isPersistable).toHaveBeenCalled(); - }); - - it("sets the location contextually when performed", function () { - new SetPrimaryLocation(testContext).perform(); - expect(mockLocationCapability.setPrimaryLocation) - .toHaveBeenCalledWith(testId); - }); - - }); - } -); diff --git a/platform/entanglement/test/capabilities/LocationCapabilitySpec.js b/platform/entanglement/test/capabilities/LocationCapabilitySpec.js deleted file mode 100644 index 071a6acd0f..0000000000 --- a/platform/entanglement/test/capabilities/LocationCapabilitySpec.js +++ /dev/null @@ -1,163 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - '../../src/capabilities/LocationCapability', - '../DomainObjectFactory', - '../ControlledPromise' - ], - function (LocationCapability, domainObjectFactory, ControlledPromise) { - - describe("LocationCapability", function () { - - describe("instantiated with domain object", function () { - var locationCapability, - mutationPromise, - mockQ, - mockInjector, - mockObjectService, - domainObject; - - beforeEach(function () { - domainObject = domainObjectFactory({ - id: "testObject", - capabilities: { - context: { - getParent: function () { - return domainObjectFactory({id: 'root'}); - } - }, - mutation: jasmine.createSpyObj( - 'mutationCapability', - ['invoke'] - ) - } - }); - - mockQ = jasmine.createSpyObj("$q", ["when"]); - mockInjector = jasmine.createSpyObj("$injector", ["get"]); - mockObjectService = - jasmine.createSpyObj("objectService", ["getObjects"]); - - mutationPromise = new ControlledPromise(); - domainObject.capabilities.mutation.invoke.and.callFake( - function (mutator) { - return mutationPromise.then(function () { - mutator(domainObject.model); - }); - } - ); - - locationCapability = new LocationCapability( - mockQ, - mockInjector, - domainObject - ); - }); - - it("returns contextual location", function () { - expect(locationCapability.getContextualLocation()) - .toBe('root'); - }); - - it("knows when the object is an original", function () { - domainObject.model.location = 'root'; - expect(locationCapability.isOriginal()).toBe(true); - expect(locationCapability.isLink()).toBe(false); - }); - - it("knows when the object is a link.", function () { - domainObject.model.location = 'different-root'; - expect(locationCapability.isLink()).toBe(true); - expect(locationCapability.isOriginal()).toBe(false); - }); - - it("can mutate location", function () { - var result = locationCapability - .setPrimaryLocation('root'), - whenComplete = jasmine.createSpy('whenComplete'); - - result.then(whenComplete); - - expect(domainObject.model.location).not.toBeDefined(); - mutationPromise.resolve(); - expect(domainObject.model.location).toBe('root'); - - expect(whenComplete).toHaveBeenCalled(); - }); - - describe("when used to load an original instance", function () { - var objectPromise, - qPromise, - originalObjects, - mockCallback; - - function resolvePromises() { - if (mockQ.when.calls.count() > 0) { - qPromise.resolve(mockQ.when.calls.mostRecent().args[0]); - } - - if (mockObjectService.getObjects.calls.count() > 0) { - objectPromise.resolve(originalObjects); - } - } - - beforeEach(function () { - objectPromise = new ControlledPromise(); - qPromise = new ControlledPromise(); - originalObjects = { - testObject: domainObjectFactory() - }; - - mockInjector.get.and.callFake(function (key) { - return key === 'objectService' && mockObjectService; - }); - mockObjectService.getObjects.and.returnValue(objectPromise); - mockQ.when.and.returnValue(qPromise); - - mockCallback = jasmine.createSpy('callback'); - }); - - it("provides originals directly", function () { - domainObject.model.location = 'root'; - locationCapability.getOriginal().then(mockCallback); - expect(mockCallback).not.toHaveBeenCalled(); - resolvePromises(); - expect(mockCallback) - .toHaveBeenCalledWith(domainObject); - }); - - it("loads from the object service for links", function () { - domainObject.model.location = 'some-other-root'; - locationCapability.getOriginal().then(mockCallback); - expect(mockCallback).not.toHaveBeenCalled(); - resolvePromises(); - expect(mockCallback) - .toHaveBeenCalledWith(originalObjects.testObject); - }); - }); - - }); - }); - } -); diff --git a/platform/entanglement/test/policies/CopyPolicySpec.js b/platform/entanglement/test/policies/CopyPolicySpec.js deleted file mode 100644 index cb08f718e2..0000000000 --- a/platform/entanglement/test/policies/CopyPolicySpec.js +++ /dev/null @@ -1,92 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - '../../src/policies/CopyPolicy', - '../DomainObjectFactory' -], function (CopyPolicy, domainObjectFactory) { - - describe("CopyPolicy", function () { - var testMetadata, - testContext, - mockDomainObject, - mockType, - mockAction, - policy; - - beforeEach(function () { - mockType = - jasmine.createSpyObj('type', ['hasFeature']); - - testMetadata = {}; - - mockDomainObject = domainObjectFactory({ - capabilities: { type: mockType } - }); - - mockType.hasFeature.and.callFake(function (feature) { - return feature === 'creation'; - }); - - mockAction = jasmine.createSpyObj('action', ['getMetadata']); - mockAction.getMetadata.and.returnValue(testMetadata); - - testContext = { domainObject: mockDomainObject }; - - policy = new CopyPolicy(); - }); - - describe("for copy actions", function () { - beforeEach(function () { - testMetadata.key = 'copy'; - }); - - describe("when an object is non-creatable", function () { - beforeEach(function () { - mockType.hasFeature.and.returnValue(false); - }); - - it("disallows the action", function () { - expect(policy.allow(mockAction, testContext)).toBe(false); - }); - }); - - describe("when an object is creatable", function () { - it("allows the action", function () { - expect(policy.allow(mockAction, testContext)).toBe(true); - }); - }); - }); - - describe("for other actions", function () { - beforeEach(function () { - testMetadata.key = 'foo'; - }); - - it("simply allows the action", function () { - expect(policy.allow(mockAction, testContext)).toBe(true); - mockType.hasFeature.and.returnValue(false); - expect(policy.allow(mockAction, testContext)).toBe(true); - }); - }); - }); -}); diff --git a/platform/entanglement/test/policies/CrossSpacePolicySpec.js b/platform/entanglement/test/policies/CrossSpacePolicySpec.js deleted file mode 100644 index c8b70e5152..0000000000 --- a/platform/entanglement/test/policies/CrossSpacePolicySpec.js +++ /dev/null @@ -1,117 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - '../../src/policies/CrossSpacePolicy', - '../DomainObjectFactory' - ], - function (CrossSpacePolicy, domainObjectFactory) { - - describe("CrossSpacePolicy", function () { - var mockAction, - testActionMetadata, - sameSpaceContext, - crossSpaceContext, - policy; - - function makeObject(space) { - var mockPersistence = jasmine.createSpyObj( - 'persistence', - ['getSpace'] - ); - mockPersistence.getSpace.and.returnValue(space); - - return domainObjectFactory({ - id: space + ":foo", - model: {}, - capabilities: { persistence: mockPersistence } - }); - } - - beforeEach(function () { - testActionMetadata = {}; - - // Policy should only call passive methods, so - // only define those in mocks. - mockAction = jasmine.createSpyObj( - 'action', - ['getMetadata'] - ); - mockAction.getMetadata.and.returnValue(testActionMetadata); - - sameSpaceContext = { - domainObject: makeObject('a'), - selectedObject: makeObject('a') - }; - crossSpaceContext = { - domainObject: makeObject('a'), - selectedObject: makeObject('b') - }; - - policy = new CrossSpacePolicy(); - }); - - describe("for move actions", function () { - beforeEach(function () { - testActionMetadata.key = 'move'; - }); - - it("allows same-space changes", function () { - expect(policy.allow(mockAction, sameSpaceContext)) - .toBe(true); - }); - - it("disallows cross-space changes", function () { - expect(policy.allow(mockAction, crossSpaceContext)) - .toBe(false); - }); - - it("allows actions with no selectedObject", function () { - expect(policy.allow(mockAction, { - domainObject: makeObject('a') - })).toBe(true); - }); - }); - - describe("for other actions", function () { - beforeEach(function () { - testActionMetadata.key = "some-other-action"; - }); - - it("allows same-space and cross-space changes", function () { - expect(policy.allow(mockAction, crossSpaceContext)) - .toBe(true); - expect(policy.allow(mockAction, sameSpaceContext)) - .toBe(true); - }); - - it("allows actions with no selectedObject", function () { - expect(policy.allow(mockAction, { - domainObject: makeObject('a') - })).toBe(true); - }); - }); - - }); - } -); diff --git a/platform/entanglement/test/services/CopyServiceSpec.js b/platform/entanglement/test/services/CopyServiceSpec.js deleted file mode 100644 index 45bef982b0..0000000000 --- a/platform/entanglement/test/services/CopyServiceSpec.js +++ /dev/null @@ -1,479 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - '../../src/services/CopyService', - '../DomainObjectFactory' - ], - function (CopyService, domainObjectFactory) { - - function synchronousPromise(value) { - if (value && value.then) { - return value; - } - - var promise = { - then: function (callback) { - return synchronousPromise(callback(value)); - } - }; - spyOn(promise, 'then').and.callThrough(); - - return promise; - } - - xdescribe("CopyService", function () { - var policyService; - - beforeEach(function () { - policyService = jasmine.createSpyObj( - 'policyService', - ['allow'] - ); - }); - - describe("validate", function () { - - var copyService, - object, - parentCandidate, - validate; - - beforeEach(function () { - copyService = new CopyService( - null, - policyService - ); - object = domainObjectFactory({ - name: 'object', - capabilities: { - type: { type: 'object' } - } - }); - parentCandidate = domainObjectFactory({ - name: 'parentCandidate', - capabilities: { - type: { type: 'parentCandidate' } - } - }); - validate = function () { - return copyService.validate(object, parentCandidate); - }; - }); - - it("does not allow invalid parentCandidate", function () { - parentCandidate = undefined; - expect(validate()).toBe(false); - parentCandidate = {}; - expect(validate()).toBe(false); - }); - - it("does not allow copying into source object", function () { - object.id = parentCandidate.id = 'abc'; - expect(validate()).toBe(false); - }); - - describe("defers to policyService", function () { - beforeEach(function () { - object.id = 'a'; - parentCandidate.id = 'b'; - }); - - it("calls policy service with correct args", function () { - validate(); - expect(policyService.allow).toHaveBeenCalledWith( - "composition", - parentCandidate, - object - ); - }); - - it("and returns false", function () { - policyService.allow.and.returnValue(false); - expect(validate()).toBe(false); - }); - - it("and returns true", function () { - policyService.allow.and.returnValue(true); - expect(validate()).toBe(true); - }); - }); - }); - - describe("perform", function () { - - var mockQ, - mockDeferred, - copyService, - object, - newParent, - copyResult, - copyFinished, - persistObjectPromise, - persistenceCapability, - instantiationCapability, - compositionCapability, - locationCapability, - resolvedValue; - - beforeEach(function () { - policyService.allow.and.returnValue(true); - - persistObjectPromise = synchronousPromise(undefined); - - instantiationCapability = jasmine.createSpyObj( - "instantiation", - ["invoke"] - ); - - persistenceCapability = jasmine.createSpyObj( - "persistenceCapability", - ["persist", "getSpace"] - ); - persistenceCapability.persist.and.returnValue(persistObjectPromise); - - compositionCapability = jasmine.createSpyObj( - 'compositionCapability', - ['invoke', 'add'] - ); - compositionCapability.add.and.callFake(synchronousPromise); - - locationCapability = jasmine.createSpyObj( - 'locationCapability', - ['isLink'] - ); - locationCapability.isLink.and.returnValue(false); - - mockDeferred = jasmine.createSpyObj( - 'mockDeferred', - ['notify', 'resolve', 'reject'] - ); - mockDeferred.notify.and.callFake(function () {}); - mockDeferred.resolve.and.callFake(function (value) { - resolvedValue = value; - }); - mockDeferred.promise = { - then: function (callback) { - return synchronousPromise(callback(resolvedValue)); - } - }; - - mockQ = jasmine.createSpyObj( - 'mockQ', - ['when', 'all', 'reject', 'defer'] - ); - mockQ.reject.and.returnValue(synchronousPromise(undefined)); - mockQ.when.and.callFake(synchronousPromise); - mockQ.all.and.callFake(function (promises) { - var result = {}; - Object.keys(promises).forEach(function (k) { - promises[k].then(function (v) { - result[k] = v; - }); - }); - - return synchronousPromise(result); - }); - mockQ.defer.and.returnValue(mockDeferred); - - }); - - describe("on domain object without composition", function () { - beforeEach(function () { - var objectCopy; - - newParent = domainObjectFactory({ - name: 'newParent', - id: '456', - model: { - composition: [] - }, - capabilities: { - instantiation: instantiationCapability, - persistence: persistenceCapability, - composition: compositionCapability - } - }); - - object = domainObjectFactory({ - name: 'object', - id: 'abc', - model: { - name: 'some object', - location: '456', - someOtherAttribute: 'some other value', - embeddedObjectAttribute: { - name: 'Some embedded object' - } - }, - capabilities: { - persistence: persistenceCapability - } - }); - - objectCopy = domainObjectFactory({ - name: 'object', - id: 'abc.copy.fdgdfgdf', - capabilities: { - persistence: persistenceCapability, - location: locationCapability - } - }); - - instantiationCapability.invoke.and.callFake( - function (model) { - objectCopy.model = model; - - return objectCopy; - } - ); - - copyService = new CopyService(mockQ, policyService); - copyResult = copyService.perform(object, newParent); - copyFinished = jasmine.createSpy('copyFinished'); - copyResult.then(copyFinished); - }); - - it("uses persistence capability", function () { - expect(persistenceCapability.persist) - .toHaveBeenCalled(); - }); - - it("deep clones object model", function () { - var newModel = copyFinished.calls.all()[0].args[0].getModel(); - expect(newModel).toEqual(object.model); - expect(newModel).not.toBe(object.model); - }); - - it("returns a promise", function () { - expect(copyResult).toBeDefined(); - expect(copyFinished).toHaveBeenCalled(); - }); - - }); - - describe("on domainObject with composition", function () { - var childObject, - objectClone, - childObjectClone; - - beforeEach(function () { - var invocationCount = 0, - objectClones; - - instantiationCapability.invoke.and.callFake( - function (model) { - var cloneToReturn = objectClones[invocationCount++]; - cloneToReturn.model = model; - - return cloneToReturn; - } - ); - - newParent = domainObjectFactory({ - name: 'newParent', - id: '456', - model: { - composition: [] - }, - capabilities: { - instantiation: instantiationCapability, - persistence: persistenceCapability, - composition: compositionCapability - } - }); - - childObject = domainObjectFactory({ - name: 'childObject', - id: 'def', - model: { - name: 'a child object', - location: 'abc' - }, - capabilities: { - persistence: persistenceCapability, - location: locationCapability - } - }); - - childObjectClone = domainObjectFactory({ - name: 'childObject', - id: 'def.clone', - capabilities: { - persistence: persistenceCapability, - location: locationCapability - } - }); - - compositionCapability - .invoke - .and.returnValue(synchronousPromise([childObject])); - - object = domainObjectFactory({ - name: 'some object', - id: 'abc', - model: { - name: 'some object', - composition: ['def'], - location: 'testLocation' - }, - capabilities: { - instantiation: instantiationCapability, - composition: compositionCapability, - location: locationCapability, - persistence: persistenceCapability - } - }); - - objectClone = domainObjectFactory({ - name: 'some object', - id: 'abc.clone', - capabilities: { - instantiation: instantiationCapability, - composition: compositionCapability, - location: locationCapability, - persistence: persistenceCapability - } - }); - - objectClones = [objectClone, childObjectClone]; - - copyService = new CopyService(mockQ, policyService); - }); - - describe("the cloning process", function () { - beforeEach(function () { - copyResult = copyService.perform(object, newParent); - copyFinished = jasmine.createSpy('copyFinished'); - copyResult.then(copyFinished); - }); - - it("returns a promise", function () { - expect(copyResult.then).toBeDefined(); - expect(copyFinished).toHaveBeenCalled(); - }); - - it("returns a promise", function () { - expect(copyResult.then).toBeDefined(); - expect(copyFinished).toHaveBeenCalled(); - }); - - it ("correctly locates cloned objects", function () { - expect(childObjectClone.getModel().location).toEqual(objectClone.getId()); - }); - }); - - describe("when cloning non-creatable objects", function () { - beforeEach(function () { - policyService.allow.and.callFake(function (category) { - //Return false for 'creation' policy - return category !== 'creation'; - }); - - copyResult = copyService.perform(object, newParent); - copyFinished = jasmine.createSpy('copyFinished'); - copyResult.then(copyFinished); - }); - it ("creates link instead of clone", function () { - var copiedObject = copyFinished.calls.all()[0].args[0]; - expect(copiedObject).toBe(object); - expect(compositionCapability.add) - .toHaveBeenCalledWith(copiedObject); - }); - }); - - describe("when provided a filtering function", function () { - beforeEach(function () { - copyFinished = jasmine.createSpy('copyFinished'); - }); - - function accept() { - return true; - } - - function reject() { - return false; - } - - it("does not create new instances of objects " - + "rejected by the filter", function () { - copyService.perform(object, newParent, reject) - .then(copyFinished); - expect(copyFinished.calls.mostRecent().args[0]) - .toBe(object); - }); - - it("does create new instances of objects " - + "accepted by the filter", function () { - copyService.perform(object, newParent, accept) - .then(copyFinished); - expect(copyFinished.calls.mostRecent().args[0]) - .not.toBe(object); - }); - }); - }); - - describe("on invalid inputs", function () { - beforeEach(function () { - object = domainObjectFactory({ - name: 'object', - capabilities: { - type: { type: 'object' }, - location: locationCapability, - persistence: persistenceCapability - } - }); - - newParent = domainObjectFactory({ - name: 'parentCandidate', - capabilities: { - type: { type: 'parentCandidate' }, - instantiation: instantiationCapability, - composition: compositionCapability, - persistence: persistenceCapability - } - }); - - instantiationCapability.invoke.and.returnValue(object); - }); - - it("throws an error", function () { - var service = - new CopyService(mockQ, policyService); - - function perform() { - service.perform(object, newParent); - } - - spyOn(service, "validate"); - service.validate.and.returnValue(true); - expect(perform).not.toThrow(); - service.validate.and.returnValue(false); - expect(perform).toThrow(); - }); - }); - - }); - }); - } -); diff --git a/platform/entanglement/test/services/CopyTaskSpec.js b/platform/entanglement/test/services/CopyTaskSpec.js deleted file mode 100644 index 2bc0fc2581..0000000000 --- a/platform/entanglement/test/services/CopyTaskSpec.js +++ /dev/null @@ -1,267 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - '../../src/services/CopyTask', - '../DomainObjectFactory' - ], - function (CopyTask, domainObjectFactory) { - - var ID_A = "some-string-with-vaguely-uuidish-uniqueness", - ID_B = "some-other-similarly-unique-string"; - - function synchronousPromise(value) { - return (value && value.then) ? value : { - then: function (callback) { - return synchronousPromise(callback(value)); - } - }; - } - - describe("CopyTask", function () { - var mockDomainObject, - mockParentObject, - mockFilter, - mockQ, - mockDeferred, - testModel, - counter, - cloneIds, - task; - - function makeMockCapabilities(childIds) { - var mockCapabilities = { - persistence: jasmine.createSpyObj( - 'persistence', - ['persist'] - ), - composition: jasmine.createSpyObj( - 'composition', - ['add', 'invoke'] - ), - instantiation: jasmine.createSpyObj( - 'instantiation', - ['instantiate', 'invoke'] - ) - }, - mockChildren = (childIds || []).map(function (id) { - return domainObjectFactory({ - id: id, - capabilities: makeMockCapabilities([]), - model: { originalId: id } - }); - }); - - mockCapabilities.persistence.persist - .and.returnValue(synchronousPromise(true)); - mockCapabilities.composition.add.and.callFake(function (obj) { - return synchronousPromise(obj); - }); - mockCapabilities.composition.invoke - .and.returnValue(synchronousPromise(mockChildren)); - mockCapabilities.instantiation.invoke - .and.callFake(function (model) { - var id = "some-id-" + counter; - cloneIds[model.originalId] = id; - counter += 1; - - return domainObjectFactory({ - id: id, - model: model, - capabilities: makeMockCapabilities() - }); - }); - - return mockCapabilities; - } - - beforeEach(function () { - counter = 0; - cloneIds = {}; - - testModel = { - composition: [ID_A, ID_B], - someObj: {}, - someArr: [ID_A, ID_B], - objArr: [{"id": ID_A}, {"id": ID_B}], - singleElementArr: [ID_A] - }; - testModel.someObj[ID_A] = "some value"; - testModel.someObj.someProperty = ID_B; - - mockDomainObject = domainObjectFactory({ - capabilities: makeMockCapabilities(testModel.composition), - model: testModel - }); - mockParentObject = domainObjectFactory({ - capabilities: makeMockCapabilities() - }); - mockFilter = jasmine.createSpy('filter'); - mockQ = jasmine.createSpyObj('$q', ['when', 'defer', 'all']); - mockDeferred = jasmine.createSpyObj( - 'deferred', - ['notify', 'resolve', 'reject'] - ); - - mockFilter.and.returnValue(true); - - mockQ.when.and.callFake(synchronousPromise); - mockQ.defer.and.returnValue(mockDeferred); - mockQ.all.and.callFake(function (promises) { - return synchronousPromise(promises.map(function (promise) { - var value; - promise.then(function (v) { - value = v; - }); - - return value; - })); - }); - - mockDeferred.resolve.and.callFake(function (value) { - mockDeferred.promise = synchronousPromise(value); - }); - - }); - - describe("produces models which", function () { - var model; - - beforeEach(function () { - task = new CopyTask( - mockDomainObject, - mockParentObject, - mockFilter, - mockQ - ); - - task.perform().then(function (clone) { - model = clone.getModel(); - }); - }); - - it("contain rewritten identifiers in arrays", function () { - expect(model.someArr) - .toEqual(testModel.someArr.map(function (id) { - return cloneIds[id]; - })); - }); - - it("contain rewritten identifiers in properties", function () { - expect(model.someObj.someProperty) - .toEqual(cloneIds[testModel.someObj.someProperty]); - }); - - it("contain rewritten identifiers in property names", function () { - expect(model.someObj[cloneIds[ID_A]]) - .toEqual(testModel.someObj[ID_A]); - }); - - it("contain rewritten identifiers in single-element arrays", function () { - expect(model.singleElementArr) - .toEqual(testModel.singleElementArr.map(function (id) { - return cloneIds[id]; - })); - }); - }); - - describe("copies object trees with multiple references to the" - + " same object", function () { - var mockDomainObjectB, - mockComposingObject, - composingObjectModel, - domainObjectClone, - domainObjectBClone; - - beforeEach(function () { - mockDomainObjectB = domainObjectFactory({ - capabilities: makeMockCapabilities(testModel.composition), - model: testModel - }); - composingObjectModel = { - name: 'mockComposingObject', - composition: [mockDomainObject.getId(), mockDomainObjectB.getId()] - }; - mockComposingObject = domainObjectFactory({ - capabilities: makeMockCapabilities(composingObjectModel.composition), - model: composingObjectModel - }); - - mockComposingObject.capabilities.composition.invoke.and.returnValue([mockDomainObject, mockDomainObjectB]); - task = new CopyTask( - mockComposingObject, - mockParentObject, - mockFilter, - mockQ - ); - - task.perform(); - domainObjectClone = task.clones[2]; - domainObjectBClone = task.clones[5]; - }); - - /** - * mockDomainObject and mockDomainObjectB have the same - * model with references to children ID_A and ID_B. Expect - * that after duplication the references should differ - * because they are each now referencing different child - * objects. This tests the issue reported in #428 - */ - it(" and correctly updates child identifiers in models ", function () { - var childA_ID = task.clones[0].getId(), - childB_ID = task.clones[1].getId(), - childC_ID = task.clones[3].getId(), - childD_ID = task.clones[4].getId(); - - expect(domainObjectClone.model.someArr[0]).not.toBe(domainObjectBClone.model.someArr[0]); - expect(domainObjectClone.model.someArr[0]).toBe(childA_ID); - expect(domainObjectBClone.model.someArr[0]).toBe(childC_ID); - expect(domainObjectClone.model.someArr[1]).not.toBe(domainObjectBClone.model.someArr[1]); - expect(domainObjectClone.model.someArr[1]).toBe(childB_ID); - expect(domainObjectBClone.model.someArr[1]).toBe(childD_ID); - expect(domainObjectClone.model.someObj.someProperty).not.toBe(domainObjectBClone.model.someObj.someProperty); - expect(domainObjectClone.model.someObj.someProperty).toBe(childB_ID); - expect(domainObjectBClone.model.someObj.someProperty).toBe(childD_ID); - - }); - - /** - * This a bug found in testathon when testing issue #428 - */ - it(" and correctly updates child identifiers in object" - + " arrays within models ", function () { - var childA_ID = task.clones[0].getId(), - childB_ID = task.clones[1].getId(); - - expect(domainObjectClone.model.objArr[0].id).not.toBe(ID_A); - expect(domainObjectClone.model.objArr[0].id).toBe(childA_ID); - expect(domainObjectClone.model.objArr[1].id).not.toBe(ID_B); - expect(domainObjectClone.model.objArr[1].id).toBe(childB_ID); - - }); - }); - - }); - - } -); diff --git a/platform/entanglement/test/services/LocatingCreationDecoratorSpec.js b/platform/entanglement/test/services/LocatingCreationDecoratorSpec.js deleted file mode 100644 index c9ee60ea37..0000000000 --- a/platform/entanglement/test/services/LocatingCreationDecoratorSpec.js +++ /dev/null @@ -1,76 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - '../../src/services/LocatingCreationDecorator' - ], - function (LocatingCreationDecorator) { - - describe("LocatingCreationDecorator", function () { - var mockCreationService, - mockPromise, - mockParent, - decorator; - - beforeEach(function () { - mockCreationService = jasmine.createSpyObj( - 'creationService', - ['createObject'] - ); - mockPromise = jasmine.createSpyObj( - 'promise', - ['then'] - ); - mockParent = jasmine.createSpyObj( - 'domainObject', - ['getCapability', 'getId', 'getModel', 'hasCapability', 'useCapability'] - ); - mockCreationService.createObject.and.returnValue(mockPromise); - mockParent.getId.and.returnValue('test-id'); - decorator = new LocatingCreationDecorator(mockCreationService); - }); - - it("delegates to its decorated service", function () { - expect(decorator.createObject( - { someKey: "some value" }, - mockParent - )).toEqual(mockPromise); // promise returned by decoratee - }); - - it("adds a location property", function () { - decorator.createObject( - { someKey: "some value" }, - mockParent - ); - expect(mockCreationService.createObject).toHaveBeenCalledWith( - { - someKey: "some value", - location: "test-id" // Parent's identifier - }, - mockParent - ); - }); - - }); - } -); diff --git a/platform/entanglement/test/services/LocatingObjectDecoratorSpec.js b/platform/entanglement/test/services/LocatingObjectDecoratorSpec.js deleted file mode 100644 index 53cb493ecc..0000000000 --- a/platform/entanglement/test/services/LocatingObjectDecoratorSpec.js +++ /dev/null @@ -1,131 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - '../../src/services/LocatingObjectDecorator', - '../../../core/src/capabilities/ContextualDomainObject' - ], - function (LocatingObjectDecorator, ContextualDomainObject) { - - describe("LocatingObjectDecorator", function () { - var mockQ, - mockLog, - mockObjectService, - mockCallback, - testObjects, - testModels, - decorator; - - function testPromise(v) { - return (v || {}).then ? v : { - then: function (callback) { - return testPromise(callback(v)); - } - }; - } - - beforeEach(function () { - // A <- B <- C - // D <-> E, to verify cycle detection - testModels = { - a: { name: "A" }, - b: { - name: "B", - location: "a" - }, - c: { - name: "C", - location: "b" - }, - d: { - name: "D", - location: "e" - }, - e: { - name: "E", - location: "d" - } - }; - testObjects = {}; - - mockQ = jasmine.createSpyObj("$q", ["when", "all"]); - mockLog = - jasmine.createSpyObj("$log", ["error", "warn", "info", "debug"]); - mockObjectService = - jasmine.createSpyObj("objectService", ["getObjects"]); - - mockQ.when.and.callFake(testPromise); - mockQ.all.and.callFake(function (promises) { - var result = {}; - Object.keys(promises).forEach(function (k) { - promises[k].then(function (v) { - result[k] = v; - }); - }); - - return testPromise(result); - }); - - mockObjectService.getObjects.and.returnValue(testPromise(testObjects)); - - mockCallback = jasmine.createSpy("callback"); - - Object.keys(testModels).forEach(function (id) { - testObjects[id] = jasmine.createSpyObj( - "domainObject-" + id, - ["getId", "getModel", "getCapability"] - ); - testObjects[id].getId.and.returnValue(id); - testObjects[id].getModel.and.returnValue(testModels[id]); - }); - - decorator = new LocatingObjectDecorator( - mockQ, - mockLog, - mockObjectService - ); - }); - - it("contextualizes domain objects", function () { - decorator.getObjects(['b', 'c']).then(mockCallback); - expect(mockCallback).toHaveBeenCalled(); - - var callbackObj = mockCallback.calls.mostRecent().args[0]; - expect(testObjects.b.getCapability('context')).not.toBeDefined(); - expect(testObjects.c.getCapability('context')).not.toBeDefined(); - expect(callbackObj.b.getCapability('context')).toBeDefined(); - expect(callbackObj.c.getCapability('context')).toBeDefined(); - }); - - it("warns on cycle detection", function () { - // Base case, no cycle, no warning - decorator.getObjects(['a', 'b', 'c']); - expect(mockLog.warn).not.toHaveBeenCalled(); - - decorator.getObjects(['e']); - expect(mockLog.warn).toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/entanglement/test/services/LocationServiceSpec.js b/platform/entanglement/test/services/LocationServiceSpec.js deleted file mode 100644 index 5ab5eecf4f..0000000000 --- a/platform/entanglement/test/services/LocationServiceSpec.js +++ /dev/null @@ -1,151 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - '../../src/services/LocationService' - ], - function (LocationService) { - - describe("LocationService", function () { - var dialogService, - locationService, - dialogServicePromise, - chainedPromise; - - beforeEach(function () { - dialogService = jasmine.createSpyObj( - 'dialogService', - ['getUserInput'] - ); - dialogServicePromise = jasmine.createSpyObj( - 'dialogServicePromise', - ['then'] - ); - chainedPromise = jasmine.createSpyObj( - 'chainedPromise', - ['then'] - ); - dialogServicePromise.then.and.returnValue(chainedPromise); - dialogService.getUserInput.and.returnValue(dialogServicePromise); - locationService = new LocationService(dialogService); - }); - - describe("getLocationFromUser", function () { - var title, - label, - validate, - initialLocation, - locationResult, - formStructure, - formState; - - beforeEach(function () { - title = "Get a location to do something"; - label = "a location"; - validate = function () { - return true; - }; - - initialLocation = { key: "a key" }; - locationResult = locationService.getLocationFromUser( - title, - label, - validate, - initialLocation - ); - formStructure = dialogService - .getUserInput - .calls.mostRecent() - .args[0]; - formState = dialogService - .getUserInput - .calls.mostRecent() - .args[1]; - }); - - it("calls through to dialogService", function () { - expect(dialogService.getUserInput).toHaveBeenCalledWith( - jasmine.any(Object), - jasmine.any(Object) - ); - expect(formStructure.name).toBe(title); - }); - - it("returns a promise", function () { - expect(locationResult.then).toBeDefined(); - }); - - describe("formStructure", function () { - var locationSection, - inputRow; - - beforeEach(function () { - locationSection = formStructure.sections[0]; - inputRow = locationSection.rows[0]; - }); - - it("has a location section", function () { - expect(locationSection).toBeDefined(); - expect(locationSection.name).toBe('Location'); - }); - - it("has a input row", function () { - expect(inputRow.control).toBe('locator'); - expect(inputRow.key).toBe('location'); - expect(inputRow.name).toBe(label); - expect(inputRow.validate).toBe(validate); - }); - }); - - describe("formState", function () { - it("has an initial location", function () { - expect(formState.location).toBe(initialLocation); - }); - }); - - describe("resolution of dialog service promise", function () { - var resolution, - resolver, - dialogResult, - selectedLocation; - - beforeEach(function () { - resolver = - dialogServicePromise.then.calls.mostRecent().args[0]; - - selectedLocation = { key: "i'm a location key" }; - dialogResult = { - location: selectedLocation - }; - - resolution = resolver(dialogResult); - }); - - it("returns selectedLocation", function () { - expect(resolution).toBe(selectedLocation); - }); - }); - }); - }); - } -); diff --git a/platform/entanglement/test/services/MockCopyService.js b/platform/entanglement/test/services/MockCopyService.js deleted file mode 100644 index 51c4a9d06f..0000000000 --- a/platform/entanglement/test/services/MockCopyService.js +++ /dev/null @@ -1,96 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - function () { - - /** - * MockCopyService provides the same interface as the copyService, - * returning promises where it would normally do so. At it's core, - * it is a jasmine spy object, but it also tracks the promises it - * returns and provides shortcut methods for resolving those promises - * synchronously. - * - * Usage: - * - * ```javascript - * var copyService = new MockCopyService(); - * - * // validate is a standard jasmine spy. - * copyService.validate.and.returnValue(true); - * var isValid = copyService.validate(object, parentCandidate); - * expect(isValid).toBe(true); - * - * // perform returns promises and tracks them. - * var whenCopied = jasmine.createSpy('whenCopied'); - * copyService.perform(object, parentObject).then(whenCopied); - * expect(whenCopied).not.toHaveBeenCalled(); - * copyService.perform.calls.mostRecent().resolve('someArg'); - * expect(whenCopied).toHaveBeenCalledWith('someArg'); - * ``` - */ - function MockCopyService() { - // track most recent call of a function, - // perform automatically returns - var mockCopyService = jasmine.createSpyObj( - 'MockCopyService', - [ - 'validate', - 'perform' - ] - ); - - mockCopyService.perform.and.callFake(() => { - var performPromise, - callExtensions, - spy; - - performPromise = jasmine.createSpyObj( - 'performPromise', - ['then'] - ); - - callExtensions = { - promise: performPromise, - resolve: function (resolveWith) { - performPromise.then.calls.all().forEach(function (call) { - call.args[0](resolveWith); - }); - } - }; - - spy = mockCopyService.perform; - - Object.keys(callExtensions).forEach(function (key) { - spy.calls.mostRecent()[key] = callExtensions[key]; - spy.calls.all()[spy.calls.count() - 1][key] = callExtensions[key]; - }); - - return performPromise; - }); - - return mockCopyService; - } - - return MockCopyService; - } -); diff --git a/platform/entanglement/test/services/MockLinkService.js b/platform/entanglement/test/services/MockLinkService.js deleted file mode 100644 index 27cb60f46d..0000000000 --- a/platform/entanglement/test/services/MockLinkService.js +++ /dev/null @@ -1,86 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - '../ControlledPromise' - ], - function (ControlledPromise) { - - /** - * MockLinkService provides the same interface as the linkService, - * returning promises where it would normally do so. At it's core, - * it is a jasmine spy object, but it also tracks the promises it - * returns and provides shortcut methods for resolving those promises - * synchronously. - * - * Usage: - * - * ```javascript - * var linkService = new MockLinkService(); - * - * // validate is a standard jasmine spy. - * linkService.validate.and.returnValue(true); - * var isValid = linkService.validate(object, parentObject); - * expect(isValid).toBe(true); - * - * // perform returns promises and tracks them. - * var whenLinked = jasmine.createSpy('whenLinked'); - * linkService.perform(object, parentObject).then(whenLinked); - * expect(whenLinked).not.toHaveBeenCalled(); - * linkService.perform.calls.mostRecent().promise.resolve('someArg'); - * expect(whenLinked).toHaveBeenCalledWith('someArg'); - * ``` - */ - function MockLinkService() { - // track most recent call of a function, - // perform automatically returns - var mockLinkService = jasmine.createSpyObj( - 'MockLinkService', - [ - 'validate', - 'perform' - ] - ); - - mockLinkService.perform.and.callFake(object => { - var performPromise = new ControlledPromise(); - - mockLinkService.perform.calls.mostRecent().promise = performPromise; - mockLinkService.perform.calls.all()[mockLinkService.perform.calls.count() - 1].promise = - performPromise; - - return performPromise.then(function (overrideObject) { - if (overrideObject) { - return overrideObject; - } - - return object; - }); - }); - - return mockLinkService; - } - - return MockLinkService; - } -); diff --git a/platform/exporters/ExportService.js b/platform/exporters/ExportService.js deleted file mode 100644 index 76ba17902b..0000000000 --- a/platform/exporters/ExportService.js +++ /dev/null @@ -1,92 +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. - *****************************************************************************/ - -/** - * @namespace platform/exporters - */ -define(['csv'], function (CSV) { - - /** - * Callback used to initiate saving files from the export service; - * typical implementation is - * [FileSaver.js](https://github.com/eligrey/FileSaver.js/). - * @callback platform/exporters.ExportService~saveAs - * @param {Blob} blob the contents of the file to export - * @param {string} filename the name of the file to export - */ - - /** - * The `exportService` provides a means to initiate downloads of - * structured data in the CSV format. - * @param {platform/exporters.ExportService~saveAs} saveAs function - * used to initiate saving files - * @constructor - * @memberof platform/exporters - */ - function ExportService(saveAs) { - this.saveAs = saveAs; - } - - /** - * Export a set of data as comma-separated values. Triggers a download - * using the function provided when the ExportService was instantiated. - * - * @param {Object[]} rows an array of objects containing key-value pairs, - * where keys are header names, and values are values - * @param {ExportOptions} [options] additional parameters for the file - * export - */ - ExportService.prototype.exportCSV = function (rows, options) { - var headers = (options && options.headers) - || (Object.keys((rows[0] || {})).sort()), - filename = (options && options.filename) || "export.csv", - csvText = new CSV(rows, { header: headers }).encode(), - blob = new Blob([csvText], { type: "text/csv" }); - this.saveAs(blob, filename); - }; - - /** - * Export an object as a JSON file. Triggers a download using the function - * provided when the ExportService was instantiated. - * - * @param {Object} obj an object to be exported as JSON - * @param {ExportOptions} [options] additional parameters for the file - * export - */ - ExportService.prototype.exportJSON = function (obj, options) { - var filename = (options && options.filename) || "test-export.json"; - var jsonText = JSON.stringify(obj); - var blob = new Blob([jsonText], {type: "application/json"}); - this.saveAs(blob, filename); - }; - /** - * Additional parameters for file export. - * @typedef ExportOptions - * @property {string} filename the name of the file to write - * @property {string[]} headers column header names, both as they - * should appear in the output and as they should be - * used to look up values from the data set. Defaults - * to the keys in the first object in the data set. - */ - - return ExportService; -}); diff --git a/platform/exporters/ExportServiceSpec.js b/platform/exporters/ExportServiceSpec.js deleted file mode 100644 index f21e880718..0000000000 --- a/platform/exporters/ExportServiceSpec.js +++ /dev/null @@ -1,159 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["./ExportService", "csv"], - function (ExportService, CSV) { - - describe("ExportService", function () { - var mockSaveAs, - testRows, - csvContents, - readCSVPromise, - exportService; - - beforeEach(function () { - var resolveFunction; - csvContents = undefined; - testRows = [ - { - a: 1, - b: 2, - c: 3 - }, - { - a: 4, - b: 5, - c: 6 - }, - { - a: 7, - b: 8, - c: 9 - } - ]; - mockSaveAs = jasmine.createSpy('saveAs'); - readCSVPromise = new Promise(function (resolve) { - resolveFunction = resolve; - }); - mockSaveAs.and.callFake(function (blob) { - var reader = new FileReader(); - reader.onloadend = function () { - csvContents = new CSV(reader.result).parse(); - resolveFunction(); - }; - - reader.readAsText(blob); - }); - exportService = new ExportService(mockSaveAs); - }); - - describe("#exportCSV(rows)", function () { - beforeEach(function () { - exportService.exportCSV(testRows); - - return readCSVPromise; - }); - - it("triggers saving of a file", function () { - expect(mockSaveAs).toHaveBeenCalledWith( - jasmine.any(Blob), - jasmine.any(String) - ); - }); - - it("includes headers from the data set", function () { - expect(csvContents[0]) - .toEqual(Object.keys(testRows[0]).sort()); - }); - - it("includes data from the data set", function () { - var headers = csvContents[0], - expectedData = testRows.map(function (row) { - return headers.map(function (key) { - return String(row[key]); - }); - }); - // Everything after header should be data - expect(csvContents.slice(1)).toEqual(expectedData); - }); - }); - - describe("#exportCSV(rows, options.headers)", function () { - var testHeaders; - - beforeEach(function () { - testHeaders = ['a', 'b']; - exportService - .exportCSV(testRows, { headers: testHeaders }); - - return readCSVPromise; - }); - - it("triggers saving of a file", function () { - expect(mockSaveAs).toHaveBeenCalledWith( - jasmine.any(Blob), - jasmine.any(String) - ); - }); - - it("includes only the specified headers", function () { - expect(csvContents[0]) - .toEqual(testHeaders); - expect(csvContents[0]) - .not.toEqual(Object.keys(testRows[0]).sort()); - }); - - it("includes a subset data from the data set", function () { - var headers = testHeaders, - expectedData = testRows.map(function (row) { - return headers.map(function (key) { - return String(row[key]); - }); - }); - expect(csvContents.slice(1)).toEqual(expectedData); - }); - }); - - describe("#exportCSV(rows, options.filename)", function () { - var testFilename; - - beforeEach(function () { - testFilename = "some-test-filename.csv"; - exportService - .exportCSV(testRows, { filename: testFilename }); - - return readCSVPromise; - }); - - it("saves a file with the specified name", function () { - expect(mockSaveAs).toHaveBeenCalledWith( - jasmine.any(Blob), - testFilename - ); - }); - }); - - }); - - } -); diff --git a/platform/exporters/bundle.js b/platform/exporters/bundle.js deleted file mode 100644 index 55b3e86451..0000000000 --- a/platform/exporters/bundle.js +++ /dev/null @@ -1,65 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./ExportService", - "saveAs" -], function (ExportService, saveAs) { - - return { - name: "platform/exporters", - definition: { - extensions: { - services: [ - { - key: "exportService", - implementation: function () { - return new ExportService(saveAs.saveAs); - } - } - ], - licenses: [ - { - "name": "CSV.js", - "version": "3.6.4", - "author": "Kash Nouroozi", - "description": "Encoder for CSV (comma separated values) export", - "website": "https://github.com/knrz/CSV.js", - "copyright": "Copyright (c) 2014 Kash Nouroozi", - "license": "license-mit", - "link": "https://github.com/knrz/CSV.js/blob/3.6.4/LICENSE" - }, - { - "name": "FileSaver.js", - "version": "0.0.2", - "author": "Eli Grey", - "description": "File download initiator (for file exports)", - "website": "https://github.com/eligrey/FileSaver.js/", - "copyright": "Copyright © 2015 Eli Grey.", - "license": "license-mit", - "link": "https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md" - } - ] - } - } - }; -}); diff --git a/platform/features/README.md b/platform/features/README.md deleted file mode 100644 index c839461c4d..0000000000 --- a/platform/features/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This directory contains bundles which represent specific user-facing -features of Open MCT, such as plots and other visualizations. -Bundles in this directory should be effectively optional. diff --git a/platform/features/pages/bundle.js b/platform/features/pages/bundle.js deleted file mode 100644 index b2c631b325..0000000000 --- a/platform/features/pages/bundle.js +++ /dev/null @@ -1,77 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/EmbeddedPageController", - "./res/iframe.html" -], function ( - EmbeddedPageController, - iframeTemplate -) { - - return { - name: "platform/features/pages", - definition: { - "extensions": { - "types": [ - { - "key": "example.page", - "name": "Web Page", - "cssClass": "icon-page", - "description": "Embed a web page or web-based image in a resizeable window component. Can be added to Display Layouts. Note that the URL being embedded must allow iframing.", - "priority": 50, - "features": [ - "creation" - ], - "properties": [ - { - "key": "url", - "name": "URL", - "control": "textfield", - "required": true, - "cssClass": "l-input-lg" - } - ] - } - ], - "views": [ - { - "template": iframeTemplate, - "name": "Page", - "type": "example.page", - "key": "example.page", - "editable": false - } - ], - "controllers": [ - { - "key": "EmbeddedPageController", - "implementation": EmbeddedPageController, - "depends": [ - "$sce" - ] - } - ] - } - } - }; -}); diff --git a/platform/features/static-markup/bundle.js b/platform/features/static-markup/bundle.js deleted file mode 100644 index ceddc93ac9..0000000000 --- a/platform/features/static-markup/bundle.js +++ /dev/null @@ -1,56 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - - "./res/markup.html" -], function ( - markupTemplate -) { - - return { - name: "platform/features/static-markup", - definition: { - "extensions": { - "types": [ - { - "key": "static.markup", - "name": "Static Markup", - "cssClass": "icon-pencil", - "description": "Static markup sandbox", - "features": [ - "creation" - ] - } - ], - "views": [ - { - "template": markupTemplate, - "name": "Static Markup", - "type": "static.markup", - "key": "static.markup" - } - ] - } - } - }; -}); diff --git a/platform/features/static-markup/res/markup.html b/platform/features/static-markup/res/markup.html deleted file mode 100644 index 72d3c9c44e..0000000000 --- a/platform/features/static-markup/res/markup.html +++ /dev/null @@ -1,24 +0,0 @@ -

      Static Markup Sandbox

      - -

      Plot limits

      -
      -
      -
      -
      -
      -
      -
      -
      - -

      Animation

      -
      This should pulse
      diff --git a/platform/framework/README.md b/platform/framework/README.md deleted file mode 100644 index 5b5fbc8bc8..0000000000 --- a/platform/framework/README.md +++ /dev/null @@ -1,192 +0,0 @@ -Framework-level components for Open MCT. This is Angular and Require, -with an extra layer to mediate between them and act as an extension -mechanism to allow plug-ins to be introduced declaratively. - -# Usage - -This section needs to be written. For now, refer to implementation notes and -examples in `example/builtins`, `example/extensions`, and `example/composite`. - -## Circular dependencies - -The framework layer (like Angular itself) does not support circular -dependencies among extensions. Generally, circular dependencies can be -avoided by refactoring; for instance, a dependency-less intermediary can be -added by two parties which depend upon one another, and both can depend upon -this intermediary while one abandons its dependency to the other (the -intermediary must then provide the functionality that was needed in the -abandoned dependency.) - -In some cases this refactoring is non-obvious or ineffective (for instance, -when a service component depends upon the whole.) In these cases, Angular's -`$injector` may be used to break the declaration-time dependency, by allowing -retrieval of the dependency at use-time instead. (This is essentially the -same solution as above, where `$injector` acts as an application-global -generalized intermediary.) - -# Implementation Notes - -The framework layer is responsible for performing a four-stage initialization -process. These stages are: - -1. __Loading definitions.__ JSON declarations are loaded for all bundles which - will constitute the application, and wrapped in a useful API for subsequent - stages. _Sources in `src/load`_ -2. __Resolving extensions.__ Any scripts which provide implementations for - extensions exposed by bundles are loaded, using Require. - _Sources in `src/resolve`_ -3. __Registering extensions.__ Resolved extensions are registered with Angular, - such that they can be used by the application at run-time. This stage - includes both registration of Angular built-ins (directives, controllers, - routes, constants, and services) as well as registration of non-Angular - extensions. _Sources in `src/register`_ -4. __Bootstrapping.__ JSON declarations are loaded for all bundles which - will constitute the application, and wrapped in a useful API for subsequent - stages. _Sources in `src/bootstrap`_ - -Additionally, the framework layer takes responsibility for initializing -other application state. Currently this simply means adding Promise to the -global namespace if it is not defined. - -## Load stage - -Using Angular's `$http`, the list of installed bundles is loaded from -`bundles.json`; then, each bundle's declaration (its path + `bundle.json`) -is loaded. These are wrapped by `Bundle` objects, and the extensions they -expose are wrapped by `Extension` objects; this is only to provide a -useful API for subsequent stages. - -A bundle is a set of related extensions; an extension is an individual -unit of the application that is meant to be used by other pieces of the -application. - -## Resolution stage - -Some, but not all, individual extensions have corresponding scripts. -These are referred to by the `implementation` field in their extension -definition. The implementation name should not include the bundle path, -or the name of the source folder; these will be pre-pended by the framework -during this stage. The implementation name should include a `.js` extension. - -Bundles may utilize third-party libraries, and may wish to expose these such -that other bundles may use them. Require JS may need special configuration -to recognize and utilize third-party libraries, and when exposing a -third-party library it may be desirable to do so under a short name -(to avoid long relative paths.) Such configuration is performed during the -resolution stage, immediately before implementations are loaded. Any -`configuration` properties from a bundle's definition (`bundle.json`) will -be used to perform this configuration; these `configuration` should take -the same form as needed to populate a -[`require.config`](http://requirejs.org/docs/api.html#config) call. -At present, only `shim` and `paths` options are supported; any `paths` will -be prepended with the bundle's library path (the bundle's `lib` folder, by -default; this directory name can be overridden by specifying a `libraries` -property in `bundles.json`.) - -An extension is resolved by loading its implementing script, if one has been -declared. If none is declared, the extension's raw definition is used -instead. To ensure that extensions look similar regardless of whether or -not an implementation is present, all key-value pairs from the definition -are copied to the loaded implementation (if one has been loaded.) - -## Registration stage - -Following implementation resolution, extensions are registered by Angular. -How this registration occurs depends on whether or not there is built in -support for the category of extension being registered. - -* For _built-in_ extension types (recognized by Angular), these are - registered with the application module. These categories are `directives`, - `controllers`, `services`, `constants`, and `routes`. -* For _composite services_, extensions of category `components` are passed - to the service compositor, which builds up a dependency graph among - the components such that their fully-wired whole is exposed as a single - service. -* For _general extensions_, the resolved extensions are assembled into a - list, with Angular-level dependencies are declared, and the full set - is exposed as a single Angular "service." - -### Priority order - -Within each category, registration occurs in priority order. An extension's -priority may be specified as a `priority` property in its extension -definition; this may be a number, or a symbolic string. Extensions are -registered in reverse numeric order (highest-priority first), and symbolic -strings are mapped to the numeric values as follows: - -* `fallback`: Negative infinity. Used for extensions that are not intended - for use (that is, they are meant to be overridden) but are present as an - option of last resort. -* `default`: -100. Used for extensions that are expected to be overridden, but - need a useful default. -* `none`: 0. Also used if no priority is specified, or if an unknown or - malformed priority is specified. -* `optional`: 100. Used for extensions that are meant to be used, but may be - overridden. -* `preferred`: 1000. Used for extensions that are specifically intended to - be used, but still may be overridden in principle. -* `mandatory`: Positive infinity. Used when an extension should definitely - not be overridden. - -These symbolic names are chosen to reflect usage where many extensions may -satisfy a given usage, but only one may be used; in this case, as a -convention it should be the lowest-ordered (highest-priority) extensions -available. In other cases, a full set (or multi-element subset) of -extensions may be desired, with a specific ordering; in these cases, it -is preferable to specify priority numerically when declaring extensions, -and to understand that extensions will be sorted according to these -conventions when using them. - -### Composite services - -Composite services are assumed to follow a provider-aggregator-decorator -pattern where: - -* _Providers_ have dependencies as usual, and expose the API associated - with the service they compose. Providers are full service implementations - in-and-of-themselves. -* _Aggregators_ have dependencies as usual plus one additional dependency, - which will be satisfied by the array of all providers registered of - that type of service. Implementations are assumed to include an extra - argument (after what they declare in `depends`) to receive this array. - Aggregators make multiple providers appear as one. -* _Decorators_ have dependencies as usual plus one additional dependency, - which will be satisfied by either an aggregator (if one is present), - the latest provider (if no aggregator is present), or another decorator - (if multiple decorators are present.) As with aggregators, an additional - argument should be accepted by the implementation to receive this. - Decorators modify or augment the behavior of a service, but do not - provide its core functionality. -* All of the above must be declared with a `provides` property, which - indicates which type of service they compose. Providers will only be - paired with aggregators of matching types, and so on. The value of - this property is also the name of the service that is ultimately - registered with Angular to represent the composite service as a whole. - -The service compositor handles this in five steps: - -1. All providers are registered. -2. Arrays of providers are registered. -3. All aggregators are registered (with dependencies to the arrays - registered in the previous step.) -4. All decorators are registered (with dependencies on the most recent - components of matching types.) -5. Full composite services are registered (essentially aliasing back - to the latest component registered of a given type.) - -Throughout these steps, components are registered with Angular using -generated names like `typeService[decorator#11]`. It is technically possible -to reference these dependencies elsewhere but that is not the intent. -Rather, the resulting composed service should be referred to as -`typeService` (or, more generally, the value matched from the `provides` -field of the paired service components.) - -### General extensions - -Similar to composite services, each individual general extension gets -registered using a generated name, like `types[extension#0]`. These are -not intended to be referenced directly; instead, they are declared -dependencies of the full list of general extensions of a given category. -This list of extensions is registered with a square-brackets suffix, -like `types[]`; this _is_ intended to be declared as a dependency by -non-framework code. diff --git a/platform/framework/bundle.js b/platform/framework/bundle.js deleted file mode 100644 index aaead5e2f8..0000000000 --- a/platform/framework/bundle.js +++ /dev/null @@ -1,107 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([], function () { - - return { - name: "platform/framework", - definition: { - "name": "Open MCT Framework Component", - "description": "Framework layer for Open MCT; interprets bundle definitions and serves as an intermediary between Require and Angular", - "libraries": "lib", - "configuration": { - "paths": { - "angular": "angular.min" - }, - "shim": { - "angular": { - "exports": "angular" - } - } - }, - "extensions": { - "licenses": [ - { - "name": "Blanket.js", - "version": "1.1.5", - "description": "Code coverage measurement and reporting", - "author": "Alex Seville", - "website": "http://blanketjs.org/", - "copyright": "Copyright (c) 2013 Alex Seville", - "license": "license-mit", - "link": "http://opensource.org/licenses/MIT" - }, - { - "name": "Jasmine", - "version": "1.3.1", - "description": "Unit testing", - "author": "Pivotal Labs", - "website": "http://jasmine.github.io/", - "copyright": "Copyright (c) 2008-2011 Pivotal Labs", - "license": "license-mit", - "link": "http://opensource.org/licenses/MIT" - }, - { - "name": "RequireJS", - "version": "2.1.22", - "description": "Script loader", - "author": "The Dojo Foundation", - "website": "http://requirejs.org/", - "copyright": "Copyright (c) 2010-2015, The Dojo Foundation", - "license": "license-mit", - "link": "https://github.com/jrburke/requirejs/blob/master/LICENSE" - }, - { - "name": "AngularJS", - "version": "1.4.4", - "description": "Client-side web application framework", - "author": "Google", - "website": "http://angularjs.org/", - "copyright": "Copyright (c) 2010-2015 Google, Inc. http://angularjs.org", - "license": "license-mit", - "link": "https://github.com/angular/angular.js/blob/v1.4.4/LICENSE" - }, - { - "name": "Angular-Route", - "version": "1.4.4", - "description": "Client-side view routing", - "author": "Google", - "website": "http://angularjs.org/", - "copyright": "Copyright (c) 2010-2015 Google, Inc. http://angularjs.org", - "license": "license-mit", - "link": "https://github.com/angular/angular.js/blob/v1.4.4/LICENSE" - }, - { - "name": "ES6-Promise", - "version": "3.0.2", - "description": "Promise polyfill for pre-ECMAScript 6 browsers", - "author": "Yehuda Katz, Tom Dale, Stefan Penner and contributors", - "website": "https://github.com/jakearchibald/es6-promise", - "copyright": "Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors", - "license": "license-mit", - "link": "https://github.com/jakearchibald/es6-promise/blob/master/LICENSE" - } - ] - } - } - }; -}); diff --git a/platform/framework/src/Constants.js b/platform/framework/src/Constants.js deleted file mode 100644 index 1e06ad24e5..0000000000 --- a/platform/framework/src/Constants.js +++ /dev/null @@ -1,48 +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. - *****************************************************************************/ - -/** - * Constants used by the framework layer. - */ -define({ - MODULE_NAME: "OpenMCTWeb", - BUNDLE_FILE: "bundle.json", - SEPARATOR: "/", - EXTENSION_SUFFIX: "[]", - DEFAULT_BUNDLE: { - "sources": "src", - "resources": "res", - "libraries": "lib", - "tests": "test", - "configuration": {}, - "extensions": {} - }, - PRIORITY_LEVELS: { - "fallback": Number.NEGATIVE_INFINITY, - "default": -100, - "none": 0, - "optional": 100, - "preferred": 1000, - "mandatory": Number.POSITIVE_INFINITY - }, - DEFAULT_PRIORITY: 0 -}); diff --git a/platform/framework/src/FrameworkInitializer.js b/platform/framework/src/FrameworkInitializer.js deleted file mode 100644 index 43502bb787..0000000000 --- a/platform/framework/src/FrameworkInitializer.js +++ /dev/null @@ -1,73 +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. - *****************************************************************************/ - -/** - * Module defining FrameworkInitializer. Created by vwoeltje on 11/3/14. - */ -define( - [], - function () { - - /** - * Responsible for managing the four stages of framework - * initialization: - * - * * Loading bundle metadata (JSON files) - * * Resolving extension implementations with Require - * * Registering extensions with Angular - * * Bootstrapping the Angular application. - * - * @memberof platform/framework - * @constructor - * @param {platform/framework.BundleLoader} loader - * @param {platform/framework.BundleResolver} resolver - * @param {platform/framework.ExtensionRegistrar} registrar - * @param {platform/framework.ApplicationBootstrapper} bootstrapper - */ - function FrameworkInitializer(loader, resolver, registrar, bootstrapper) { - this.loader = loader; - this.resolver = resolver; - this.registrar = registrar; - this.bootstrapper = bootstrapper; - } - - function bind(method, thisArg) { - return function () { - return method.apply(thisArg, arguments); - }; - } - - /** - * Run the application defined by this set of bundles. - * @param bundleList - * @returns {*} - */ - FrameworkInitializer.prototype.runApplication = function () { - return this.loader.loadBundles([]) - .then(bind(this.resolver.resolveBundles, this.resolver)) - .then(bind(this.registrar.registerExtensions, this.registrar)) - .then(bind(this.bootstrapper.bootstrap, this.bootstrapper)); - }; - - return FrameworkInitializer; - } -); diff --git a/platform/framework/src/FrameworkLayer.js b/platform/framework/src/FrameworkLayer.js deleted file mode 100644 index 2c9e86be52..0000000000 --- a/platform/framework/src/FrameworkLayer.js +++ /dev/null @@ -1,104 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - './Constants', - './FrameworkInitializer', - './LogLevel', - './load/BundleLoader', - './resolve/ImplementationLoader', - './resolve/ExtensionResolver', - './resolve/BundleResolver', - './register/CustomRegistrars', - './register/ExtensionRegistrar', - './register/ExtensionSorter', - './bootstrap/ApplicationBootstrapper' -], function ( - Constants, - FrameworkInitializer, - LogLevel, - BundleLoader, - ImplementationLoader, - ExtensionResolver, - BundleResolver, - CustomRegistrars, - ExtensionRegistrar, - ExtensionSorter, - ApplicationBootstrapper -) { - - function FrameworkLayer($http, $log) { - this.$http = $http; - this.$log = $log; - } - - FrameworkLayer.prototype.initializeApplication = function ( - angular, - openmct, - logLevel - ) { - var $http = this.$http, - $log = this.$log, - app = angular.module(Constants.MODULE_NAME, ["ngRoute"]), - loader = new BundleLoader($http, $log, openmct.legacyRegistry), - resolver = new BundleResolver( - new ExtensionResolver( - new ImplementationLoader({}), - $log - ), - $log - ), - registrar = new ExtensionRegistrar( - app, - new CustomRegistrars(app, $log), - new ExtensionSorter($log), - $log - ), - bootstrapper = new ApplicationBootstrapper( - angular, - openmct.element, - $log - ), - initializer = new FrameworkInitializer( - loader, - resolver, - registrar, - bootstrapper - ); - - // Override of angular1.6 ! hashPrefix - app.config(['$locationProvider', function ($locationProvider) { - $locationProvider.hashPrefix(''); - }]); - - // Apply logging levels; this must be done now, before the - // first log statement. - new LogLevel(logLevel).configure(app, $log); - - // Initialize the application - $log.info("Initializing application."); - - return initializer.runApplication(); - }; - - return FrameworkLayer; -}); diff --git a/platform/framework/src/LogLevel.js b/platform/framework/src/LogLevel.js deleted file mode 100644 index f90ea6c848..0000000000 --- a/platform/framework/src/LogLevel.js +++ /dev/null @@ -1,100 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - // Log levels; note that these must be in order of - // most-important-first for LogLevel to function correctly - // as implemented. - var LOG_LEVELS = [ - 'error', - 'warn', - 'info', - 'log', - 'debug' - ]; - - // No-op, to replace undesired log levels with - function NOOP() {} - - /** - * Handles enforcement of logging at different levels, specified - * at load time. The provided level should be one of "error", - * "warn", "info", "log", or "debug"; otherwise, "warn" is used - * as a default. Only log messages of levels equal to or greater - * than the specified level will be passed to console. - * - * @memberof platform/framework - * @constructor - * @param {string} level the logging level - */ - function LogLevel(level) { - // Find the numeric level associated with the string - this.index = LOG_LEVELS.indexOf(level); - - // Default to 'warn' level if unspecified - if (this.index < 0) { - this.index = 1; - } - } - - /** - * Configure logging to suppress log output if it is - * not of an appropriate level. Both the Angular app - * being initialized and a reference to `$log` should be - * passed; the former is used to configure application - * logging, while the latter is needed to apply the - * same configuration during framework initialization - * (since the framework also logs.) - * - * @param app the Angular app to configure - * @param $log Angular's $log (also configured) - * @memberof platform/framework.LogLevel# - */ - LogLevel.prototype.configure = function (app, $log) { - var index = this.index; - - // Replace logging methods with no-ops, if they are - // not of an appropriate level. - function decorate(log) { - LOG_LEVELS.forEach(function (m, i) { - // Determine applicability based on index - // (since levels are in descending order) - if (i > index) { - log[m] = NOOP; - } - }); - } - - decorate($log); - app.decorator('$log', ['$delegate', function ($delegate) { - decorate($delegate); - - return $delegate; - }]); - }; - - return LogLevel; - } -); diff --git a/platform/framework/src/Main.js b/platform/framework/src/Main.js deleted file mode 100644 index 750c338502..0000000000 --- a/platform/framework/src/Main.js +++ /dev/null @@ -1,60 +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. - *****************************************************************************/ - -/** - * Implements the framework layer, which handles the loading of bundles - * and the wiring-together of the extensions they expose. - * @namespace platform/framework - */ -define( - [ - './FrameworkLayer', - 'angular', - 'angular-route' - ], - function ( - FrameworkLayer, - angular - ) { - - function Main() { - } - - Main.prototype.run = function (openmct) { - // Get a reference to Angular's injector, so we can get $http and $log - // services, which are useful to the framework layer. - var injector = angular.injector(['ng']); - - // Look up log level from query string - function logLevel() { - var match = /[?&]log=([a-z]+)/.exec(window.location.search); - - return match ? match[1] : ""; - } - - return injector.instantiate(['$http', '$log', FrameworkLayer]) - .initializeApplication(angular, openmct, logLevel()); - }; - - return Main; - } -); diff --git a/platform/framework/src/bootstrap/ApplicationBootstrapper.js b/platform/framework/src/bootstrap/ApplicationBootstrapper.js deleted file mode 100644 index 20e9fb581a..0000000000 --- a/platform/framework/src/bootstrap/ApplicationBootstrapper.js +++ /dev/null @@ -1,70 +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. - *****************************************************************************/ - -/** - * Module defining Bootstrapper. Created by vwoeltje on 11/4/14. - * - * The bootstrapper is responsible - */ -define( - [], - function () { - - /** - * The application bootstrapper is responsible for issuing the - * bootstrap call to Angular. This would normally not be needed - * with an appropriately-placed ng-app directive, but the - * framework needs to wait until all extensions have been loaded - * and registered. - * - * @memberof platform/framework - * @constructor - */ - function ApplicationBootstrapper(angular, document, $log) { - this.angular = angular; - this.document = document; - this.$log = $log; - } - - /** - * Bootstrap the application. - * - * @param {angular.Module} app the Angular application to - * bootstrap - */ - ApplicationBootstrapper.prototype.bootstrap = function (app) { - var angular = this.angular, - document = this.document, - $log = this.$log; - - return new Promise(function (resolve, reject) { - $log.info("Bootstrapping application " + (app || {}).name); - angular.element(document).ready(function () { - angular.bootstrap(document, [app.name], { strictDi: true }); - resolve(angular); - }); - }); - }; - - return ApplicationBootstrapper; - } -); diff --git a/platform/framework/src/load/Bundle.js b/platform/framework/src/load/Bundle.js deleted file mode 100644 index cfe5cf18f2..0000000000 --- a/platform/framework/src/load/Bundle.js +++ /dev/null @@ -1,207 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['../Constants', './Extension'], - function (Constants, Extension) { - - /** - * A bundle's plain JSON definition. - * - * @name BundleDefinition - * @property {string} name the human-readable name of this bundle - * @property {string} sources the name of the directory which - * contains source code used by this bundle - * @property {string} resources the name of the directory which - * contains resource files used by this bundle - * @property {Object.} [extensions={}] - * all extensions exposed by this bundle - * @constructor - * @memberof platform/framework - */ - - /** - * Instantiate a new reference to a bundle, based on its human-readable - * definition. - * - * @param {string} path the path to the directory containing - * this bundle - * @param {BundleDefinition} bundleDefinition - * @returns {{getDefinition: Function}} - * @constructor - */ - function Bundle(path, bundleDefinition) { - // Start with defaults - var definition = Object.create(Constants.DEFAULT_BUNDLE), - logName = path; - - // Override defaults with specifics from bundle definition - Object.keys(bundleDefinition).forEach(function (k) { - definition[k] = bundleDefinition[k]; - }); - - // Record path to bundle in definition - definition.path = path; - - // Build up the log-friendly name for this bundle - if (definition.key || definition.name) { - logName += "("; - logName += definition.key || ""; - logName += (definition.key && definition.name) ? " " : ""; - logName += definition.name || ""; - logName += ")"; - } - - this.path = path; - this.definition = definition; - this.logName = logName; - } - - // Utility function for resolving paths in this bundle - Bundle.prototype.resolvePath = function (elements) { - var path = this.path; - - return [path].concat(elements || []).join(Constants.SEPARATOR); - }; - - /** - * Get the path to this bundle. - * @returns {string} path to this bundle; - */ - Bundle.prototype.getPath = function () { - return this.path; - }; - - /** - * Get the path to this bundle's source folder. If an - * argument is provided, the path will be to the source - * file within the bundle's source file. - * - * @param {string} [sourceFile] optionally, give a path to - * a specific source file in the bundle. - * @returns {string} path to the source folder (or to the - * source file within it) - */ - Bundle.prototype.getSourcePath = function (sourceFile) { - var subpath = sourceFile - ? [this.definition.sources, sourceFile] - : [this.definition.sources]; - - return this.resolvePath(subpath); - }; - - /** - * Get the path to this bundle's resource folder. If an - * argument is provided, the path will be to the resource - * file within the bundle's resource file. - * - * @param {string} [resourceFile] optionally, give a path to - * a specific resource file in the bundle. - * @returns {string} path to the resource folder (or to the - * resource file within it) - */ - Bundle.prototype.getResourcePath = function (resourceFile) { - var subpath = resourceFile - ? [this.definition.resources, resourceFile] - : [this.definition.resources]; - - return this.resolvePath(subpath); - }; - - /** - * Get the path to this bundle's library folder. If an - * argument is provided, the path will be to the library - * file within the bundle's resource file. - * - * @param {string} [libraryFile] optionally, give a path to - * a specific library file in the bundle. - * @returns {string} path to the resource folder (or to the - * resource file within it) - */ - Bundle.prototype.getLibraryPath = function (libraryFile) { - var subpath = libraryFile - ? [this.definition.libraries, libraryFile] - : [this.definition.libraries]; - - return this.resolvePath(subpath); - }; - - /** - * Get library configuration for this bundle. This is read - * from the bundle's definition; if the bundle is well-formed, - * it will resemble a require.config object. - * @returns {object} library configuration - */ - Bundle.prototype.getConfiguration = function () { - return this.definition.configuration || {}; - }; - - /** - * Get a log-friendly name for this bundle; this will - * include both the key (machine-readable name for this - * bundle) and the name (human-readable name for this - * bundle.) - * @returns {string} log-friendly name for this bundle - */ - Bundle.prototype.getLogName = function () { - return this.logName; - }; - - /** - * Get all extensions exposed by this bundle of a given - * category. - * - * @param {string} category name of the extension category - * @returns {Array} extension definitions of that category - */ - Bundle.prototype.getExtensions = function (category) { - var extensions = this.definition.extensions[category] || [], - self = this; - - return extensions.map(function objectify(extDefinition) { - return new Extension(self, category, extDefinition); - }); - }; - - /** - * Get a list of all extension categories exposed by this bundle. - * - * @returns {string[]} the extension categories - */ - Bundle.prototype.getExtensionCategories = function () { - return Object.keys(this.definition.extensions); - }; - - /** - * Get the plain definition of this bundle, as read from - * its JSON declaration. - * - * @returns {platform/framework.BundleDefinition} the raw - * definition of this bundle - */ - Bundle.prototype.getDefinition = function () { - return this.definition; - }; - - return Bundle; - } -); diff --git a/platform/framework/src/load/BundleLoader.js b/platform/framework/src/load/BundleLoader.js deleted file mode 100644 index 7d6865aa09..0000000000 --- a/platform/framework/src/load/BundleLoader.js +++ /dev/null @@ -1,160 +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. - *****************************************************************************/ - -/** - * Module defining BundleLoader.js. Created by vwoeltje on 10/31/14. - */ -define( - ['../Constants', './Bundle'], - function (Constants, Bundle) { - - var INVALID_ARGUMENT_MESSAGE = "Malformed loadBundles argument; " - + "expected string or array", - BAD_CONTENTS_PREFIX = "Invalid bundle contents for ", - LOAD_ERROR_PREFIX = "Failed to load bundle "; - - /** - * Loads bundle definitions and wraps them in interfaces which are - * useful to the framework. This provides the base information which - * will be used by later phases of framework layer initialization. - * - * @memberof platform/framework - * @constructor - * @param $http Angular's HTTP requester - * @param $log Angular's logging service - */ - function BundleLoader($http, $log, legacyRegistry) { - this.$http = $http; - this.$log = $log; - this.legacyRegistry = legacyRegistry; - } - - /** - * Load a group of bundles, to be used to constitute the - * application by later framework initialization phases. - * - * @param {string|string[]} an array of bundle names to load, or - * the name of a JSON file containing that array - * @returns {Promise.} a promise for the loaded bundles - */ - BundleLoader.prototype.loadBundles = function (bundles) { - var $http = this.$http, - $log = this.$log, - legacyRegistry = this.legacyRegistry; - - // Utility function; load contents of JSON file using $http - function getJSON(file) { - return $http.get(file).then(function (response) { - return response.data; - }); - } - - // Remove bundles which failed to load properly. - // These should have been logged when loaded by - // loadBundleDefinition, so at this point they are safe - // to discard. - function filterBundles(array) { - return array.filter(function (x) { - return x !== undefined; - }); - } - - // Load a definition for a bundle - function loadBundleDefinition(bundlePath) { - return getJSON(bundlePath + "/" + Constants.BUNDLE_FILE).then( - function (x) { - if (x === null || typeof x !== 'object') { - $log.warn(BAD_CONTENTS_PREFIX + bundlePath); - - return undefined; - } - - return x; - }, - function () { - $log.warn(LOAD_ERROR_PREFIX + bundlePath); - - return undefined; - } - ); - } - - // Load an individual bundle, as a Bundle object. - // Returns undefined if the definition could not be loaded. - function loadBundle(bundlePath) { - if (legacyRegistry.contains(bundlePath)) { - return Promise.resolve(new Bundle( - bundlePath, - legacyRegistry.get(bundlePath) - )); - } - - return loadBundleDefinition(bundlePath).then(function (definition) { - return definition && (new Bundle(bundlePath, definition)); - }); - } - - // Used to filter out redundant bundles - function unique(element, index, array) { - return array.indexOf(element) === index; - } - - // Load all named bundles from the array, returned as an array - // of Bundle objects. - function loadBundlesFromArray(bundleArray) { - var bundlePromises = legacyRegistry.list() - .concat(bundleArray) - .filter(unique) - .map(loadBundle); - - return Promise.all(bundlePromises) - .then(filterBundles); - } - - // Load all bundles named in the referenced file. The file is - // presumed to be a JSON file - function loadBundlesFromFile(listFile) { - function handleError(err) { - $log.info([ - "No external bundles loaded;", - "could not load bundle listing in", - listFile, - "due to error", - err.status, - err.statusText - ].join(' ')); - - return loadBundlesFromArray([]); - } - - return getJSON(listFile) - .then(loadBundlesFromArray, handleError); - } - - return Array.isArray(bundles) ? loadBundlesFromArray(bundles) - : (typeof bundles === 'string') ? loadBundlesFromFile(bundles) - : Promise.reject(new Error(INVALID_ARGUMENT_MESSAGE)); - }; - - return BundleLoader; - } -); diff --git a/platform/framework/src/load/Extension.js b/platform/framework/src/load/Extension.js deleted file mode 100644 index 53310c15cf..0000000000 --- a/platform/framework/src/load/Extension.js +++ /dev/null @@ -1,190 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * An extension's plain JSON definition. - * - * @name ExtensionDefinition - * @property {string} [key] the machine-readable identifier for this - * extension - * @property {string} [implementation] the path to the AMD module - * which implements this extension; this path is relative - * to the containing bundle's source folder. - * @property {string[]} [depends=[]] the dependencies needed by this - * extension; these are strings as shall be passed to - * Angular's dependency resolution mechanism. - * @constructor - * @memberof platform/framework - */ - - /** - * Instantiate a new extension based on its definition. This serves - * primarily as a wrapper around the extension's definition to expose - * a useful interface. - * - * An extension - * - * @param {Bundle} bundle the bundle which exposed this extension - * @param {string} category the type of extension being exposed - * @param {ExtensionDefinition} definition the plain definition of - * this extension - * @constructor - */ - function Extension(bundle, category, definition) { - var logName = category, - extensionDefinition = {}; - - // Build up the log-friendly name for this bundle - if (definition.key || definition.name) { - logName += "("; - logName += definition.key || ""; - logName += (definition.key && definition.name) ? " " : ""; - logName += definition.name || ""; - logName += ")"; - } - - logName += " from " + bundle.getLogName(); - - // Copy over definition. This allows us to attach the bundle - // definition without modifying the original definition object. - Object.keys(definition).forEach(function (k) { - extensionDefinition[k] = definition[k]; - }); - - // Attach bundle metadata - extensionDefinition.bundle = bundle.getDefinition(); - - this.logName = logName; - this.bundle = bundle; - this.category = category; - this.definition = definition; - this.extensionDefinition = extensionDefinition; - } - - /** - * Get the machine-readable identifier for this extension. - * - * @returns {string} the identifier for this extension - */ - Extension.prototype.getKey = function () { - return this.definition.key || "undefined"; - }; - - /** - * Get the bundle which declared this extension. - * - * @returns {Bundle} the declaring bundle - */ - Extension.prototype.getBundle = function () { - return this.bundle; - }; - - /** - * Get the category into which this extension falls. - * (e.g. "directives") - * - * @returns {string} the extension category - */ - Extension.prototype.getCategory = function () { - return this.category; - }; - - /** - * Check whether or not this extension should have an - * associated implementation module which may need to - * be loaded. - * - * @returns {boolean} true if an implementation separate - * from this definition should also be loaded - */ - Extension.prototype.hasImplementation = function () { - return this.definition.implementation !== undefined; - }; - - /** - * Get the path to the AMD module which implements this - * extension. Will return undefined if there is no - * implementation associated with this extension. - * - * @returns {string} path to implementation, or undefined - */ - Extension.prototype.getImplementationPath = function () { - return (this.hasImplementation() && !this.hasImplementationValue()) - ? this.bundle.getSourcePath(this.definition.implementation) - : undefined; - }; - - /** - * Check if an extension has an actual implementation value - * (and not just a path to an implementation) defined. - * @returns {function} the constructor for this extension instance - */ - Extension.prototype.getImplementationValue = function () { - return typeof this.definition.implementation === 'function' - ? this.definition.implementation - : undefined; - }; - - /** - * Check if an extension has an actual implementation value - * (and not just a path to an implementation) defined. - * @returns {boolean} true if a value is available - */ - Extension.prototype.hasImplementationValue = function () { - return typeof this.definition.implementation === 'function'; - }; - - /** - * Get a log-friendly name for this extension; this will - * include both the key (machine-readable name for this - * extension) and the name (human-readable name for this - * extension.) - * - * @returns {string} log-friendly name for this extension - */ - Extension.prototype.getLogName = function () { - return this.logName; - }; - - /** - * Get the plain definition of the extension. - * - * Note that this definition will have an additional "bundle" - * field which points back to the bundle which defined the - * extension, as a convenience. - * - * @returns {ExtensionDefinition} the plain definition of - * this extension, as read from the bundle - * declaration. - */ - Extension.prototype.getDefinition = function () { - return this.extensionDefinition; - }; - - return Extension; - - } -); diff --git a/platform/framework/src/register/CustomRegistrars.js b/platform/framework/src/register/CustomRegistrars.js deleted file mode 100644 index 4dcfc2af46..0000000000 --- a/platform/framework/src/register/CustomRegistrars.js +++ /dev/null @@ -1,248 +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. - *****************************************************************************/ -/* eslint-disable no-invalid-this */ - -/** - * Module defining CustomRegistrars. Created by vwoeltje on 11/3/14. - */ -define( - ['../Constants', './ServiceCompositor'], - function (Constants, ServiceCompositor) { - /*jshint validthis:true */ - - /** - * Handles registration of a few specific extension types that are - * understood natively by Angular. This includes services and - * directives. - * @memberof platform/framework - * @constructor - */ - function CustomRegistrars(app, $log) { - this.app = app; - this.$log = $log; - this.registered = {}; // Track registered keys by extension - } - - // Utility; bind a function to a "this" pointer - function bind(fn, thisArg) { - return function () { - return fn.apply(thisArg, arguments); - }; - } - - // Used to create custom registration functions which map to - // named methods on Angular modules, which follow the normal - // app.method(key, [ deps..., function ]) pattern. - function customRegistrar(angularFunction) { - return function (extension, index) { - var app = this.app, - $log = this.$log, - key = extension.key, - dependencies = extension.depends || [], - registered = this.registered[angularFunction] || {}; - - this.registered[angularFunction] = registered; - - if (!key) { - $log.warn([ - "Cannot register ", - angularFunction, - " ", - index, - ", no key specified. ", - JSON.stringify(extension) - ].join("")); - } else if (registered[key]) { - $log.debug([ - "Already registered ", - angularFunction, - " with key ", - key, - "; skipping." - ].join("")); - } else { - $log.info([ - "Registering ", - angularFunction, - ": ", - key - ].join("")); - registered[key] = true; - app[angularFunction]( - key, - dependencies.concat([extension]) - ); - } - }; - } - - function registerConstant(extension) { - var app = this.app, - $log = this.$log, - key = extension.key, - value = extension.value; - - if (typeof key === "string" && value !== undefined) { - $log.info([ - "Registering constant: ", - key, - " with value ", - value - ].join("")); - app.constant(key, value); - } else { - $log.warn([ - "Cannot register constant ", - key, - " with value ", - value - ].join("")); - } - - } - - // Custom registration function for extensions of category "runs" - function registerRun(extension) { - var app = this.app, - $log = this.$log; - - if (typeof extension === 'function') { - // Prepend dependencies, and schedule to run - app.run((extension.depends || []).concat([extension])); - } else { - // If it's not a function, no implementation was given - $log.warn([ - "Cannot register run extension from ", - (extension.bundle || {}).path, - "; no implementation." - ].join("")); - } - } - - // Custom registration function for extensions of category "route" - function registerRoute(extension) { - var app = this.app, - $log = this.$log, - route = Object.create(extension); - - // Adjust path for bundle - if (route.templateUrl) { - route.templateUrl = [ - route.bundle.path, - route.bundle.resources, - route.templateUrl - ].join(Constants.SEPARATOR); - } - - // Log the registration - $log.info("Registering route: " + (route.key || route.when)); - - // Register the route with Angular - app.config(['$routeProvider', function ($routeProvider) { - if (route.when) { - $routeProvider.when(route.when, route); - } else { - $routeProvider.otherwise(route); - } - }]); - } - - // Handle service compositing - function registerComponents(components) { - var app = this.app, - $log = this.$log; - - return new ServiceCompositor(app, $log) - .registerCompositeServices(components); - } - - // Utility; create a function which converts another function - // (which acts on single objects) to one which acts upon arrays. - function mapUpon(func) { - return function (array) { - return array.map(bind(func, this)); - }; - } - - // More like key-value pairs than methods; key is the - // name of the extension category to be handled, and the value - // is the function which handles it. - - /** - * Register constant values. - * @param {Array} extensions the resolved extensions - */ - CustomRegistrars.prototype.constants = - mapUpon(registerConstant); - - /** - * Register Angular routes. - * @param {Array} extensions the resolved extensions - */ - CustomRegistrars.prototype.routes = - mapUpon(registerRoute); - - /** - * Register Angular directives. - * @param {Array} extensions the resolved extensions - */ - CustomRegistrars.prototype.directives = - mapUpon(customRegistrar("directive")); - - /** - * Register Angular controllers. - * @param {Array} extensions the resolved extensions - */ - CustomRegistrars.prototype.controllers = - mapUpon(customRegistrar("controller")); - - /** - * Register Angular services. - * @param {Array} extensions the resolved extensions - */ - CustomRegistrars.prototype.services = - mapUpon(customRegistrar("service")); - - /** - * Register Angular filters. - * @param {Array} extensions the resolved extensions - */ - CustomRegistrars.prototype.filters = - mapUpon(customRegistrar("filter")); - - /** - * Register functions which will run after bootstrapping. - * @param {Array} extensions the resolved extensions - */ - CustomRegistrars.prototype.runs = - mapUpon(registerRun); - - /** - * Register components of composite services. - * @param {Array} extensions the resolved extensions - */ - CustomRegistrars.prototype.components = - registerComponents; - - return CustomRegistrars; - } -); diff --git a/platform/framework/src/register/ExtensionRegistrar.js b/platform/framework/src/register/ExtensionRegistrar.js deleted file mode 100644 index 04b14952fc..0000000000 --- a/platform/framework/src/register/ExtensionRegistrar.js +++ /dev/null @@ -1,232 +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. - *****************************************************************************/ - -/** - * Module defining ExtensionRegistrar. Created by vwoeltje on 11/3/14. - */ -define( - ['../Constants', './PartialConstructor'], - function (Constants, PartialConstructor) { - - /** - * Responsible for registering extensions with Angular. - * - * @memberof platform/framework - * @constructor - * @param {angular.Module} the Angular application with which - * extensions should be registered - * @param {Object.} customRegistrars an object - * containing custom registration functions, primarily for - * Angular built-ins. - * @param {ExtensionSorter} sorter the sorter which will impose - * priority ordering upon extensions - * @param {*} $log Angular's logging service - */ - function ExtensionRegistrar(app, customRegistrars, sorter, $log) { - // Track which extension categories have already been registered. - // Exceptions will be thrown if the same extension category is - // registered twice. - this.registeredCategories = {}; - this.customRegistrars = customRegistrars || {}; - this.app = app; - this.sorter = sorter; - this.$log = $log; - } - - /** - * Register a group of resolved extensions with the Angular - * module managed by this registrar. - * - * For convenient chaining (particularly from the framework - * initializer's perspective), this returns the Angular - * module with which extensions were registered. - * - * @param {Object.} extensionGroup an object - * containing key-value pairs, where keys are extension - * categories and values are arrays of resolved - * extensions - * @returns {angular.Module} the application module with - * which extensions were registered - */ - ExtensionRegistrar.prototype.registerExtensions = function (extensionGroup) { - var registeredCategories = this.registeredCategories, - customRegistrars = this.customRegistrars, - app = this.app, - sorter = this.sorter, - $log = this.$log; - - // Used to build unique identifiers for individual extensions, - // so that these can be registered separately with Angular - function identify(category, extension, index) { - var name = extension.key - ? ("extension-" + extension.key + "#" + index) - : ("extension#" + index); - - return category + "[" + name + "]"; - } - - // Echo arguments; used to represent groups of non-built-in - // extensions as a single dependency. - function echo() { - return Array.prototype.slice.call(arguments); - } - - // Always return a static value; used to represent plain - // metadata as a single dependency in Angular. - function staticFunction(value) { - return function () { - return value; - }; - } - - // Utility function; create the second argument for Angular's - // .service service registration method (an array containing - // both dependencies and a factory method for the service.) - function makeServiceArgument(category, extension) { - var dependencies = extension.depends || [], - factory = (typeof extension === 'function') - ? new PartialConstructor(extension) - : staticFunction(extension); - - return dependencies.concat([factory]); - } - - // Register extension arrays with Angular under an appropriately - // suffixed name, e.g. "types[]" - function registerExtensionArraysForCategory(category, names) { - var name = category + Constants.EXTENSION_SUFFIX; - app.factory(name, names.concat([echo])); - } - - function registerExtensionsForCategory(category, extensions) { - var names = []; - - function registerExtension(extension, index) { - var name = identify(category, extension, index); - - // Track individual extension names as-registered - names.push(name); - - app.factory( - name, - makeServiceArgument(category, extension) - ); - } - - if (registeredCategories[category]) { - $log.warn([ - "Tried to register extensions for category ", - category, - " more than once. Ignoring all but first set." - ].join("")); - } else { - // Register all extensions. Use custom registration - // code for services, directives, etc; otherwise, - // just register them under generic names. - if (customRegistrars[category]) { - customRegistrars[category](extensions); - } else { - extensions.forEach(registerExtension); - registerExtensionArraysForCategory(category, names); - } - - registeredCategories[category] = true; - - return true; - } - } - - // Check if a declared dependency looks like a dependency on - // an extension category (e.g. is suffixed by []) - function isExtensionDependency(dependency) { - var index = dependency.indexOf( - Constants.EXTENSION_SUFFIX, - dependency.length - Constants.EXTENSION_SUFFIX.length - ); - - return index !== -1; - } - - // Examine a group of resolved dependencies to determine - // which extension categories still need to be satisfied. - function findEmptyExtensionDependencies(extGroup) { - var needed = {}, - categories = Object.keys(extGroup), - allExtensions = []; - - // Build up an array of all extensions - categories.forEach(function (category) { - allExtensions = - allExtensions.concat(extGroup[category]); - }); - - // Track all extension dependencies exposed therefrom - allExtensions.forEach(function (extension) { - (extension.depends || []).filter( - isExtensionDependency - ).forEach(function (dependency) { - needed[dependency] = true; - }); - }); - - // Remove categories which have been provided - categories.forEach(function (category) { - var dependency = category + Constants.EXTENSION_SUFFIX; - delete needed[dependency]; - }); - - return Object.keys(needed); - } - - // Register any extension categories that are depended-upon but - // have not been declared anywhere; such dependencies are then - // satisfied by an empty array, instead of not at all. - function registerEmptyDependencies(extGroup) { - findEmptyExtensionDependencies(extGroup) - .forEach(function (name) { - $log.info("Registering empty extension category " + name); - app.factory(name, [staticFunction([])]); - }); - } - - // Announce we're entering a new phase - $log.info("Registering extensions..."); - - // Register all declared extensions by category - Object.keys(extensionGroup).forEach(function (category) { - registerExtensionsForCategory( - category, - sorter.sort(extensionGroup[category]) - ); - }); - - // Also handle categories which are needed but not declared - registerEmptyDependencies(extensionGroup); - - // Return the application to which these extensions - // have been registered - return app; - }; - - return ExtensionRegistrar; - } -); diff --git a/platform/framework/src/register/ExtensionSorter.js b/platform/framework/src/register/ExtensionSorter.js deleted file mode 100644 index a6ddad6be1..0000000000 --- a/platform/framework/src/register/ExtensionSorter.js +++ /dev/null @@ -1,118 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../Constants"], - function (Constants) { - - /** - * Responsible for applying priority order to extensions in a - * given category. This will sort in reverse order of the numeric - * priority given for extensions in the `priority` priority (such - * that large values are registered first.) Extensions may also - * specify symbolic properties as strings (instead of numbers), - * which will be looked up from the table `Constants.PRIORITY_LEVELS`. - * @param $log Angular's logging service - * @memberof platform/framework - * @constructor - */ - function ExtensionSorter($log) { - this.$log = $log; - } - - /** - * Sort extensions according to priority. - * - * @param {object[]} extensions array of resolved extensions - * @returns {object[]} the same extensions, in priority order - */ - ExtensionSorter.prototype.sort = function (extensions) { - var $log = this.$log; - - // Handle unknown or malformed priorities specified by extensions - function unrecognizedPriority(extension) { - // Issue a warning - $log.warn([ - "Unrecognized priority '", - (extension || {}).priority, - "' specified for extension from ", - ((extension || {}).bundle || {}).path, - "; defaulting to ", - Constants.DEFAULT_PRIORITY - ].join('')); - - // Provide a return value (default priority) to make this - // useful in an expression. - return Constants.DEFAULT_PRIORITY; - } - - function getPriority(extension) { - var priority = - (extension || {}).priority || Constants.DEFAULT_PRIORITY; - - // If it's a symbolic priority, look it up - if (typeof priority === 'string') { - priority = Constants.PRIORITY_LEVELS[priority]; - } - - // Should be a number; otherwise, issue a warning and - // fall back to default priority level. - return (typeof priority === 'number') - ? priority : unrecognizedPriority(extension); - } - - // Attach a numeric priority to an extension; this is done in - // one pass outside of the comparator, mainly because getPriority - // may log warnings, and we only want this to happen once - // (instead of the many times that might occur during a sort.) - function prioritize(extension, index) { - return { - // The extension itself, for later unwrapping - extension: extension, - // The index, to provide a stable sort (see compare) - index: index, - // The numeric priority of the extension - priority: getPriority(extension) - }; - } - - // Unwrap the original extension - // (for use after ordering has been applied) - function deprioritize(prioritized) { - return prioritized.extension; - } - - // Compare two prioritized extensions - function compare(a, b) { - // Reverse order by numeric priority; or, original order. - return (b.priority - a.priority) || (a.index - b.index); - } - - return (extensions || []) - .map(prioritize) - .sort(compare) - .map(deprioritize); - }; - - return ExtensionSorter; - } -); diff --git a/platform/framework/src/register/PartialConstructor.js b/platform/framework/src/register/PartialConstructor.js deleted file mode 100644 index e6fca5af84..0000000000 --- a/platform/framework/src/register/PartialConstructor.js +++ /dev/null @@ -1,78 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * Module defining PartialConstructor. Created by vwoeltje on 11/3/14. - */ -define( - [], - function () { - - /** - * A partial constructor is used to instantiate objects in two - * stages: - * - * * First, dependencies injected from Angular - * * Second, arguments passed at run-time - * - * This allows extensions to accept both their Angular-injected - * dependencies and their per-instance attributes all in one - * constructor invocation. User code for these extensions then - * does not see the Angular dependency arguments; they may - * instantiate instances of these extensions by passing only - * those per-instance arguments. - * - * @memberof platform/framework - * @constructor - */ - function PartialConstructor(Constructor) { - - function OuterConstructor() { // Bind services - var dependencies = Array.prototype.slice.call(arguments); - - function InnerConstructor() { // Bind everything else - var other = Array.prototype.slice.call(arguments), - instance = Object.create(Constructor.prototype); - - // Mimic "new" behavior with apply. - instance = Constructor.apply( - instance, - dependencies.concat(other) - ) || instance; - - return instance; - } - - // Copy properties from original constructor - Object.keys(Constructor).forEach(function (k) { - InnerConstructor[k] = Constructor[k]; - }); - - return InnerConstructor; - } - - return OuterConstructor; - } - - return PartialConstructor; - } -); diff --git a/platform/framework/src/register/ServiceCompositor.js b/platform/framework/src/register/ServiceCompositor.js deleted file mode 100644 index c634a85679..0000000000 --- a/platform/framework/src/register/ServiceCompositor.js +++ /dev/null @@ -1,257 +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. - *****************************************************************************/ - -/** - * Module defining ServiceCompositor. Created by vwoeltje on 11/5/14. - */ -define( - [], - function () { - - /** - * Handles service compositing; that is, building up services - * from provider, aggregator, and decorator components. - * - * @memberof platform/framework - * @constructor - */ - function ServiceCompositor(app, $log) { - this.latest = {}; - this.providerLists = {}; // Track latest services registered - this.app = app; - this.$log = $log; - } - - /** - * Register composite services with Angular. This will build - * up a dependency hierarchy between providers, aggregators, - * and/or decorators, such that a dependency upon the service - * type they expose shall be satisfied by their fully-wired - * whole. - * - * Note that this method assumes that a complete set of - * components shall be provided. Multiple calls to this - * method may not behave as expected. - * - * @param {Array} components extensions of category component - */ - ServiceCompositor.prototype.registerCompositeServices = function (components) { - var latest = this.latest, - providerLists = this.providerLists, - app = this.app, - $log = this.$log; - - // Log a warning; defaults to "no service provided by" - function warn(extension, category, message) { - var msg = message || "No service provided by"; - $log.warn([ - msg, - " ", - category, - " ", - extension.key, - " from bundle ", - (extension.bundle || { path: "unknown bundle" }).path, - "; skipping." - ].join("")); - } - - //Log an info: defaults to "no service provide by" - function info(extension, category, message) { - var msg = message || "No service provided by"; - $log.info([ - msg, - " ", - category, - " ", - extension.key, - " from bundle ", - (extension.bundle || { path: "unknown bundle" }).path, - "; skipping." - ].join("")); - } - - // Echo arguments; used to represent groups of non-built-in - // extensions as a single dependency. - function echoMany() { - return Array.prototype.slice.call(arguments); - } - - // Echo arguments; used to represent groups of non-built-in - // extensions as a single dependency. - function echoSingle(value) { - return value; - } - - // Generates utility functions to match types (one of - // provider, aggregator, or decorator) of component. Used - // to filter down to specific types, which are handled - // in order. - function hasType(type) { - return function (extension) { - return extension.type === type; - }; - } - - // Make a unique name for a service component. - function makeName(category, service, index) { - return [ - service, - "[", - category, - "#", - index, - "]" - ].join(""); - } - - // Register a specific provider instance with Angular, and - // record its name for subsequent stages. - function registerProvider(provider, index) { - var service = provider.provides, - dependencies = provider.depends || [], - name = makeName("provider", service, index); - - if (!service) { - return warn(provider, "provider"); - } - - providerLists[service] = providerLists[service] || []; - providerLists[service].push(name); - - // This provider is the latest candidate for resolving - // the composite service. - latest[service] = name; - - app.service(name, dependencies.concat([provider])); - - $log.info("Registering provider for " + service); - } - - // Register an array of providers as a single dependency; - // aggregators will then depend upon this to consume all - // aggregated providers as a single dependency. - function registerProviderSets() { - Object.keys(providerLists).forEach(function (service) { - var name = makeName("provider", service, "*"), - list = providerLists[service]; - - $log.info([ - "Compositing", - list.length, - "providers for", - service - ].join(" ")); - - app.service(name, list.concat([echoMany])); - }); - } - - // Registers an aggregator via Angular, including both - // its declared dependencies and the additional, implicit - // dependency upon the array of all providers. - function registerAggregator(aggregator, index) { - var service = aggregator.provides, - dependencies = aggregator.depends || [], - providerSetName = makeName("provider", service, "*"), - name = makeName("aggregator", service, index); - - if (!service) { - return info(aggregator, "aggregator"); - } - - // Aggregators need other services to aggregate, otherwise they - // do nothing. - if (!latest[service]) { - return info( - aggregator, - "aggregator", - "No services to aggregate for" - ); - } - - dependencies = dependencies.concat([providerSetName]); - latest[service] = name; - - app.service(name, dependencies.concat([aggregator])); - } - - // Registers a decorator via Angular, including its implicit - // dependency on the latest service component which has come - // before it. - function registerDecorator(decorator, index) { - var service = decorator.provides, - dependencies = decorator.depends || [], - name = makeName("decorator", service, index); - - if (!service) { - return warn(decorator, "decorator"); - } - - // Decorators need other services to decorate, otherwise they - // do nothing. - if (!latest[service]) { - return warn( - decorator, - "decorator", - "No services to decorate for" - ); - } - - dependencies = dependencies.concat([latest[service]]); - latest[service] = name; - - app.service(name, dependencies.concat([decorator])); - } - - // Alias the latest services of various types back to the - // more general service declaration. - function registerLatest() { - Object.keys(latest).forEach(function (service) { - app.service(service, [latest[service], echoSingle]); - }); - } - - // Register composite services in phases: - // * Register providers - // * Register aggregators (which use providers) - // * Register decorators (which use anything) - // Then, register the latest candidate as a plain service. - function registerComposites(providers, aggregators, decorators) { - providers.forEach(registerProvider); - registerProviderSets(); - aggregators.forEach(registerAggregator); - decorators.forEach(registerDecorator); - registerLatest(); - } - - // Initial point of entry; split into three component types. - registerComposites( - components.filter(hasType("provider")), - components.filter(hasType("aggregator")), - components.filter(hasType("decorator")) - ); - }; - - return ServiceCompositor; - } -); diff --git a/platform/framework/src/resolve/BundleResolver.js b/platform/framework/src/resolve/BundleResolver.js deleted file mode 100644 index f04297baab..0000000000 --- a/platform/framework/src/resolve/BundleResolver.js +++ /dev/null @@ -1,125 +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. - *****************************************************************************/ - -/** - * Module defining BundleResolver. Created by vwoeltje on 11/4/14. - */ -define( - [], - function () { - - /** - * Responsible for the extension resolution phase of framework - * initialization. During this phase, any scripts implementing - * extensions provided by bundles are loaded. - * - * @memberof platform/framework - * @constructor - */ - function BundleResolver(extensionResolver, $log) { - this.extensionResolver = extensionResolver; - this.$log = $log; - } - - /** - * Resolve all extensions exposed by these bundles. - * - * @param {Bundle[]} bundles the bundles to resolve - * @returns {Promise.>} an promise - * for an object containing - * key-value pairs, where keys are extension - * categories and values are arrays of resolved - * extensions belonging to those categories - */ - BundleResolver.prototype.resolveBundles = function (bundles) { - var extensionResolver = this.extensionResolver, - $log = this.$log; - - /* - * Merge resolved bundles (where each is expressed as an - * object containing key-value pairs, where keys are extension - * categories and values are arrays of resolved extensions) - * into one large object containing resolved extensions from - * all bundles (in the same form.) - * - * @param {Object.|Array} resolvedBundles - * @returns {Object.} - * @memberof platform/framework.BundleResolver# - */ - function mergeResolvedBundles(resolvedBundles) { - var result = {}; - - resolvedBundles.forEach(function (resolved) { - Object.keys(resolved).forEach(function (k) { - result[k] = (result[k] || []).concat(resolved[k]); - }); - }); - - return result; - } - - // Resolve a bundle; resolve all extensions, and return - // the resolved extensions in an object in the format described - // for mergeResolvedBundles above - function resolveBundle(bundle) { - var categories = bundle.getExtensionCategories(), - result = {}; - - function resolveExtension(extension) { - var category = extension.getCategory(); - - function push(resolved) { - result[category].push(resolved); - } - - return extensionResolver.resolve(extension).then(push); - } - - function resolveCategory(category) { - result[category] = []; - - return Promise.all( - bundle.getExtensions(category).map(resolveExtension) - ); - } - - function giveResult() { - return result; - } - - // Log the large-scale task - $log.info( - "Resolving extensions for bundle " + bundle.getLogName() - ); - - return Promise.all(categories.map(resolveCategory)) - .then(giveResult); - } - - // Then, resolve all extension implementations. - return Promise.all(bundles.map(resolveBundle)) - .then(mergeResolvedBundles); - }; - - return BundleResolver; - } -); diff --git a/platform/framework/src/resolve/ExtensionResolver.js b/platform/framework/src/resolve/ExtensionResolver.js deleted file mode 100644 index 208f518685..0000000000 --- a/platform/framework/src/resolve/ExtensionResolver.js +++ /dev/null @@ -1,147 +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. - *****************************************************************************/ - -/** - * Module defining ExtensionResolver. Created by vwoeltje on 11/3/14. - */ -define( - [], - function () { - - /** - * An ExtensionResolver is responsible for loading any implementation - * modules associated with specific extensions. - * - * @param {ImplementationLoader} loader used to load implementations - * @param {*} $log Angular's logging service - * @memberof platform/framework - * @constructor - */ - function ExtensionResolver(loader, $log) { - this.loader = loader; - this.$log = $log; - } - - /** - * Resolve the provided extension; this will give a promise - * for the extension's implementation, if one has been - * specified, or for the plain definition of the extension - * otherwise. The plain definition will also be given - * if the implementation fails to load for some reason. - * - * All key-value pairs from the extension definition - * will additionally be attached to any loaded implementation. - * - * @param {Extension} extension the extension to resolve - * @returns {Promise} a promise for the resolved extension - */ - ExtensionResolver.prototype.resolve = function (extension) { - var loader = this.loader, - $log = this.$log; - - function loadImplementation(ext) { - var implPromise = ext.hasImplementationValue() - ? Promise.resolve(ext.getImplementationValue()) - : loader.load(ext.getImplementationPath()), - definition = ext.getDefinition(); - - // Wrap a constructor function (to avoid modifying the original) - function constructorFor(impl) { - function Constructor() { - return impl.apply(this, arguments); - } - - Constructor.prototype = impl.prototype; - - return Constructor; - } - - // Attach values from the object definition to the - // loaded implementation. - function attachDefinition(impl) { - var result = (typeof impl === 'function') - ? constructorFor(impl) - : Object.create(impl); - - // Copy over static properties - Object.keys(impl).forEach(function (k) { - result[k] = impl[k]; - }); - - // Copy over definition - Object.keys(definition).forEach(function (k) { - if (result[k] === undefined) { - result[k] = definition[k]; - } - }); - result.definition = definition; - - // Log that this load was successful - $log.info("Resolved " + ext.getLogName()); - - return result; - } - - // Log any errors in loading the implementation, and - // return the plain extension definition instead. - function handleError(err) { - // Build up a log message from parts - var message = [ - "Could not load implementation for extension ", - ext.getLogName(), - " due to ", - err.message - ].join(""); - - // Log that the extension was not loaded - $log.warn(message); - - return ext.getDefinition(); - } - - if (!ext.hasImplementationValue()) { - // Log that loading has begun - $log.info([ - "Loading implementation ", - ext.getImplementationPath(), - " for extension ", - ext.getLogName() - ].join("")); - } - - return implPromise.then(attachDefinition, handleError); - } - - // Log that loading has begun - $log.info([ - "Resolving extension ", - extension.getLogName() - ].join("")); - - return extension.hasImplementation() - ? loadImplementation(extension) - : Promise.resolve(extension.getDefinition()); - }; - - return ExtensionResolver; - } -); diff --git a/platform/framework/src/resolve/ImplementationLoader.js b/platform/framework/src/resolve/ImplementationLoader.js deleted file mode 100644 index a4362634c8..0000000000 --- a/platform/framework/src/resolve/ImplementationLoader.js +++ /dev/null @@ -1,64 +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. - *****************************************************************************/ - -/** - * Module defining ImplementationLoader. Created by vwoeltje on 11/3/14. - */ -define( - [], - function () { - - /** - * Responsible for loading extension implementations - * (AMD modules.) Acts as a wrapper around RequireJS to - * provide a promise-like API. - * @memberof platform/framework - * @constructor - * @param {*} require RequireJS, or an object with similar API - * @param {*} $log Angular's logging service - */ - function ImplementationLoader(require) { - this.require = require; - } - - /** - * Load an extension's implementation; or, equivalently, - * load an AMD module. This is fundamentally similar - * to a call to RequireJS, except that the result is - * wrapped in a promise. The promise will be fulfilled - * with the loaded module, or rejected with the error - * reported by Require. - * - * @param {string} path the path to the module to load - * @returns {Promise} a promise for the specified module. - */ - ImplementationLoader.prototype.load = function loadModule(path) { - var require = this.require; - - return new Promise(function (fulfill, reject) { - require([path], fulfill, reject); - }); - }; - - return ImplementationLoader; - } -); diff --git a/platform/framework/test/FrameworkInitializerSpec.js b/platform/framework/test/FrameworkInitializerSpec.js deleted file mode 100644 index 3f4b6a06c5..0000000000 --- a/platform/framework/test/FrameworkInitializerSpec.js +++ /dev/null @@ -1,60 +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. - *****************************************************************************/ - -/** - * FrameworkInitializerSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../src/FrameworkInitializer"], - function (FrameworkInitializer) { - - describe("The framework initializer", function () { - var initializer; - - function appender(name) { - return function (value) { - return Promise.resolve(value.concat([name])); - }; - } - - beforeEach(function () { - initializer = new FrameworkInitializer( - { loadBundles: appender("loader") }, - { resolveBundles: appender("resolver") }, - { registerExtensions: appender("registrar") }, - { bootstrap: appender("bootstrapper")} - ); - }); - - // Really just delegates work, can only verify the - // order of calls. - it("calls injected stages in order", function () { - return initializer.runApplication([]).then(function (result) { - expect(result).toEqual( - ["loader", "resolver", "registrar", "bootstrapper"] - ); - }); - }); - - }); - } -); diff --git a/platform/framework/test/LogLevelSpec.js b/platform/framework/test/LogLevelSpec.js deleted file mode 100644 index ef0086233e..0000000000 --- a/platform/framework/test/LogLevelSpec.js +++ /dev/null @@ -1,98 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['../src/LogLevel'], - function (LogLevel) { - - var LOG_METHODS = [ - 'error', - 'warn', - 'info', - 'log', - 'debug' - ]; - - describe("The logging level handler", function () { - var mockLog, - mockApp, - mockDelegate, - mockMethods; - - function logAll(v) { - LOG_METHODS.forEach(function (m) { - mockLog[m](v); - mockDelegate[m](v); - }); - } - - function expectCalls(calls, v) { - LOG_METHODS.forEach(function (m) { - if (calls.indexOf(m) > -1) { - expect(mockMethods[m]).toHaveBeenCalledWith(v); - } else { - expect(mockMethods[m]).not.toHaveBeenCalledWith(v); - } - }); - } - - beforeEach(function () { - mockMethods = jasmine.createSpyObj("levels", LOG_METHODS); - mockLog = jasmine.createSpyObj('$log', LOG_METHODS); - mockApp = jasmine.createSpyObj('app', ['config', 'decorator']); - mockDelegate = jasmine.createSpyObj('$delegate', LOG_METHODS); - - LOG_METHODS.forEach(function (m) { - mockLog[m].and.callFake(mockMethods[m]); - mockDelegate[m].and.callFake(mockMethods[m]); - }); - - mockApp.decorator.and.callFake(function (key, decoration) { - // We only expect $log to be decorated - if (key === '$log' && decoration[0] === '$delegate') { - decoration[1](mockDelegate); - } - }); - }); - - it("defaults to 'warn' level", function () { - new LogLevel("garbage").configure(mockApp, mockLog); - logAll("test"); - expectCalls(['error', 'warn'], 'test'); - }); - - LOG_METHODS.forEach(function (m, i) { - it("supports log level '" + m + "'", function () { - // Note: This is sensitive to ordering of LOG_METHODS, - // which needs to be highest-level-first above. - var expected = LOG_METHODS.slice(0, i + 1), - message = "test " + m; - - new LogLevel(m).configure(mockApp, mockLog); - logAll(message); - expectCalls(expected, message); - }); - }); - - }); - } -); diff --git a/platform/framework/test/bootstrap/ApplicationBootstrapperSpec.js b/platform/framework/test/bootstrap/ApplicationBootstrapperSpec.js deleted file mode 100644 index 482d57fab0..0000000000 --- a/platform/framework/test/bootstrap/ApplicationBootstrapperSpec.js +++ /dev/null @@ -1,108 +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. - *****************************************************************************/ - -/** - * ApplicationBootstrapperSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/bootstrap/ApplicationBootstrapper"], - function (ApplicationBootstrapper) { - - describe("The application bootstrapper", function () { - // Test support variables - var bootstrapper, - captured, - mockAngular, - mockDocument, - mockLog, - mockApp; - - // Used to capture arguments to mocks - function capture() { - var names = Array.prototype.slice.apply(arguments, []); - - return function () { - var values = arguments; - names.forEach(function (name, index) { - captured[name] = values[index]; - }); - }; - } - - // Set up the mocks before each run - beforeEach(function () { - captured = {}; - - mockAngular = { - element: function (selector) { - captured.selector = selector; - - return { ready: capture("callback") }; - }, - bootstrap: capture("element", "appNames") - }; - - mockDocument = "I am just a value."; - - mockLog = { info: capture("info") }; - - mockApp = { name: "MockApp" }; - - bootstrapper = new ApplicationBootstrapper( - mockAngular, - mockDocument, - mockLog - ); - - bootstrapper.bootstrap(mockApp); - }); - - // The tests. - - it("waits for the provided document element to be ready", function () { - // Should have provided Angular a selector... - expect(captured.selector).toBe(mockDocument); - // ...and called ready on the response. - expect(captured.callback).toBeDefined(); - }); - - it("issues a bootstrap call once ready", function () { - // Verify precondition; bootstrap not called - expect(captured.element).toBeUndefined(); - expect(captured.appNames).toBeUndefined(); - - // Call the "ready" function - captured.callback(); - - // Verify that bootstrap was called - expect(captured.element).toBe(mockDocument); - expect(captured.appNames).toEqual([mockApp.name]); - }); - - it("logs that the bootstrap phase has been reached", function () { - expect(captured.info).toBeDefined(); - expect(typeof captured.info).toEqual('string'); - }); - - }); - } -); diff --git a/platform/framework/test/load/BundleLoaderSpec.js b/platform/framework/test/load/BundleLoaderSpec.js deleted file mode 100644 index a85974b35f..0000000000 --- a/platform/framework/test/load/BundleLoaderSpec.js +++ /dev/null @@ -1,122 +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. - *****************************************************************************/ - -/** - * BundleLoaderSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/load/BundleLoader"], - function (BundleLoader) { - - describe("The bundle loader", function () { - var loader, - mockHttp, - mockLog, - mockRegistry, - testBundles; - - beforeEach(function () { - testBundles = { - "bundles.json": ["bundle/a", "bundle/b"], - "bundle/a/bundle.json": {"someValue": 6}, - "bundle/b/bundle.json": {"someValue": 7} - }; - - mockHttp = jasmine.createSpyObj("$http", ["get"]); - mockLog = jasmine.createSpyObj("$log", ["error", "warn", "info", "debug"]); - mockRegistry = jasmine.createSpyObj( - 'legacyRegistry', - ['list', 'contains', 'get'] - ); - mockRegistry.list.and.returnValue([]); - mockRegistry.contains.and.returnValue(false); - loader = new BundleLoader(mockHttp, mockLog, mockRegistry); - }); - - it("accepts a JSON file name and loads all bundles", function () { - // Set up; return named bundles - mockHttp.get.and.returnValue(Promise.resolve({ data: [] })); - - // Call load bundles - return loader.loadBundles("test-filename.json").then(function (bundles) { - // Should have loaded the file via $http - expect(mockHttp.get).toHaveBeenCalledWith("test-filename.json"); - - // Should have gotten an empty bundle list - expect(bundles).toEqual([]); - }); - }); - - it("accepts a list of bundle paths", function () { - // Set up; return named bundles - mockHttp.get.and.callFake(function (name) { - return Promise.resolve({data: testBundles[name]}); - }); - - // Call load bundles - return loader.loadBundles(["bundle/a", "bundle/b"]).then(function (bundles) { - // Should have gotten back two bundles - expect(bundles.length).toEqual(2); - - // They should have the values we expect; don't care about order, - // some map/reduce the summation. - expect(bundles.map(function (call) { - return call.getDefinition().someValue; - }).reduce(function (a, b) { - return a + b; - }, 0)).toEqual(13); - }); - }); - - it("warns about, then ignores, missing bundle declarations", function () { - // File-not-found - mockHttp.get.and.returnValue(Promise.reject(new Error("test error"))); - - // Try and load - return loader.loadBundles(["some/bundle"]).then(function (bundles) { - // Should have gotten zero bundles - expect(bundles.length).toEqual(0); - - // They should have the values we expect; don't care about order, - // some map/reduce the summation. - expect(mockLog.warn).toHaveBeenCalled(); - }); - }); - - it("warns about, then ignores, malformed bundle declarations", function () { - // File-not-found - mockHttp.get.and.returnValue(Promise.resolve(["I am not a valid bundle."])); - - // Try and load - return loader.loadBundles(["some/bundle"]).then(function (bundles) { - // Should have gotten zero bundle - expect(bundles.length).toEqual(0); - - // They should have the values we expect; don't care about order, - // some map/reduce the summation. - expect(mockLog.warn).toHaveBeenCalled(); - }); - }); - - }); - } -); diff --git a/platform/framework/test/load/BundleSpec.js b/platform/framework/test/load/BundleSpec.js deleted file mode 100644 index f87bfe54f6..0000000000 --- a/platform/framework/test/load/BundleSpec.js +++ /dev/null @@ -1,98 +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. - *****************************************************************************/ - -/** - * BundleSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/load/Bundle", "../../src/Constants"], - function (Bundle, Constants) { // Verify against constants, too - - describe("A bundle", function () { - var PATH = "some/path", - KEY = "someKey"; - - it("reports its path", function () { - expect(new Bundle(PATH, {}).getPath()).toEqual(PATH); - }); - - it("reports its source path", function () { - expect(new Bundle(PATH, { "sources": "test-src" }).getSourcePath()).toEqual( - PATH + Constants.SEPARATOR + "test-src" - ); - }); - - it("reports the default source path if none is configured", function () { - expect(new Bundle(PATH, {}).getSourcePath()).toEqual( - PATH + Constants.SEPARATOR + Constants.DEFAULT_BUNDLE.sources - ); - }); - - it("reports its resource path", function () { - expect(new Bundle(PATH, { "resources": "test-res" }).getResourcePath()).toEqual( - PATH + Constants.SEPARATOR + "test-res" - ); - }); - - it("reports the default resource path if none is configured", function () { - expect(new Bundle(PATH, {}).getResourcePath()).toEqual( - PATH + Constants.SEPARATOR + Constants.DEFAULT_BUNDLE.resources - ); - }); - - it("has a log-friendly name for the bundle which includes its key and path", function () { - // Use indexof to look for the bundle's key - var logName = new Bundle(PATH, { key: KEY }).getLogName(); - expect(logName.indexOf(KEY)).not.toEqual(-1); - expect(logName.indexOf(PATH)).not.toEqual(-1); - }); - - it("reports all declared extension categories", function () { - var bundle = new Bundle(PATH, { - extensions: { - things: [], - tests: [], - foos: [] - } - }); - - expect(bundle.getExtensionCategories().sort()).toEqual( - ["foos", "tests", "things"] - ); - }); - - it("reports all extensions that have been declared", function () { - var bundle = new Bundle(PATH, { - extensions: { things: [{}, {}, {}] } - }); - expect(bundle.getExtensions("things").length).toEqual(3); - }); - - it("reports an empty list for extensions that have not been declared", function () { - var bundle = new Bundle(PATH, { - extensions: { things: [{}, {}, {}] } - }); - expect(bundle.getExtensions("stuffs").length).toEqual(0); - }); - }); - } -); diff --git a/platform/framework/test/load/ExtensionSpec.js b/platform/framework/test/load/ExtensionSpec.js deleted file mode 100644 index 04e500d42f..0000000000 --- a/platform/framework/test/load/ExtensionSpec.js +++ /dev/null @@ -1,107 +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. - *****************************************************************************/ - -/** - * ExtensionSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/load/Extension", "../../src/load/Bundle"], - function (Extension, Bundle) { - - describe("An extension", function () { - var bundle; - - beforeEach(function () { - bundle = new Bundle("test/bundle", {}); - }); - - it("reports its key", function () { - expect(new Extension(bundle, "tests", { key: "testKey"}).getKey()) - .toEqual("testKey"); - }); - - it("reports some key, even if non is defined", function () { - expect(new Extension(bundle, "tests", {}).getKey()).toBeDefined(); - }); - - it("allows retrieval of its declaring bundle", function () { - expect(new Extension(bundle, "tests", {}).getBundle()).toBe(bundle); - }); - - it("reports its category", function () { - expect(new Extension(bundle, "tests", {}).getCategory()) - .toEqual("tests"); - expect(new Extension(bundle, "otherThings", {}).getCategory()) - .toEqual("otherThings"); - }); - - it("provides a check to see if an implementation is associated with the extension", function () { - expect(new Extension(bundle, "tests", {}).hasImplementation()) - .toBeFalsy(); - expect(new Extension(bundle, "tests", { implementation: "Something.js" }).hasImplementation()) - .toBeTruthy(); - }); - - it("provides a full path to an implementation, if present", function () { - expect(new Extension(bundle, "tests", { implementation: "Something.js" }).getImplementationPath()) - .toEqual("test/bundle/src/Something.js"); - }); - - it("does not define an implementation path if there is no implementation", function () { - expect(new Extension(bundle, "tests", {}).getImplementationPath()) - .toBeUndefined(); - }); - - it("provides a log-friendly name which contains the extension key", function () { - var logName = - new Extension(bundle, "tests", { key: "testKey"}).getLogName(); - expect(logName.indexOf("testKey")).not.toEqual(-1); - }); - - it("allows its definition to be retrieved", function () { - var definition = { - key: "someKey", - implementation: "SomeImplementation.js" - }, - reported = new Extension(bundle, "tests", definition).getDefinition(); - - // Bundle is added, so we can't do a normal toEqual; check that - // all keys are still there, though. - Object.keys(definition).forEach(function (k) { - expect(reported[k]).toEqual(definition[k]); - }); - }); - - it("includes the bundle in its definition", function () { - // Bundle is needed by some registrars in the the registration phase, - // so make sure it gets attached to the definition. - var definition = { - key: "someKey", - implementation: "SomeImplementation.js" - }, - reported = new Extension(bundle, "tests", definition).getDefinition(); - - expect(reported.bundle).toEqual(bundle.getDefinition()); - }); - }); - } -); diff --git a/platform/framework/test/register/CustomRegistrarsSpec.js b/platform/framework/test/register/CustomRegistrarsSpec.js deleted file mode 100644 index ef3341a6d0..0000000000 --- a/platform/framework/test/register/CustomRegistrarsSpec.js +++ /dev/null @@ -1,198 +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. - *****************************************************************************/ - -/** - * CustomRegistrarsSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/register/CustomRegistrars"], - function (CustomRegistrars) { - - describe("Custom registrars", function () { - var mockLog, - mockApp, - customRegistrars; - - // Set up mock test dependencies - beforeEach(function () { - mockApp = jasmine.createSpyObj("app", [ - "controller", - "directive", - "service", - "constant", - "config", - "run" - ]); - - mockLog = jasmine.createSpyObj("$log", [ - "error", - "warn", - "info", - "debug" - ]); - - customRegistrars = new CustomRegistrars(mockApp, mockLog); - }); - - it("has custom registrars for Angular built-ins", function () { - expect(customRegistrars.directives).toBeTruthy(); - expect(customRegistrars.controllers).toBeTruthy(); - expect(customRegistrars.services).toBeTruthy(); - expect(customRegistrars.routes).toBeTruthy(); - expect(customRegistrars.constants).toBeTruthy(); - expect(customRegistrars.runs).toBeTruthy(); - }); - - it("invokes built-in functions on the app", function () { - // Verify preconditions, invoke, expect to have been called - expect(mockApp.directive.calls.count()).toEqual(0); - customRegistrars.directives([{ key: "a" }, { key: "b" }, { key: "c" }]); - expect(mockApp.directive.calls.count()).toEqual(3); - - expect(mockApp.controller.calls.count()).toEqual(0); - customRegistrars.controllers([{ key: "a" }, { key: "b" }, { key: "c" }]); - expect(mockApp.controller.calls.count()).toEqual(3); - - expect(mockApp.service.calls.count()).toEqual(0); - customRegistrars.services([{ key: "a" }, { key: "b" }, { key: "c" }]); - expect(mockApp.service.calls.count()).toEqual(3); - - expect(mockApp.constant.calls.count()).toEqual(0); - customRegistrars.constants([{ - key: "a", - value: "b" - }, { - key: "b", - value: "c" - }, { - key: "c", - value: "d" - }]); - expect(mockApp.constant.calls.count()).toEqual(3); - - expect(mockApp.run.calls.count()).toEqual(0); - customRegistrars.runs([jasmine.createSpy("a"), jasmine.createSpy("a"), jasmine.createSpy("a")]); - expect(mockApp.run.calls.count()).toEqual(3); - - }); - - it("warns when keys are not defined, then skips", function () { - // Verify preconditions, invoke, expect to have been called - expect(mockApp.directive.calls.count()).toEqual(0); - customRegistrars.directives([{ key: "a" }, { }, { key: "c" }]); - expect(mockApp.directive.calls.count()).toEqual(2); - expect(mockLog.warn.calls.count()).toEqual(1); - - expect(mockApp.controller.calls.count()).toEqual(0); - customRegistrars.controllers([{ }, { }, { key: "c" }]); - expect(mockApp.controller.calls.count()).toEqual(1); - expect(mockLog.warn.calls.count()).toEqual(3); - - expect(mockApp.service.calls.count()).toEqual(0); - customRegistrars.services([{ }, { }, { }]); - expect(mockApp.service.calls.count()).toEqual(0); - expect(mockLog.warn.calls.count()).toEqual(6); - - expect(mockApp.constant.calls.count()).toEqual(0); - customRegistrars.constants([{ }, { }, { }]); - expect(mockApp.constant.calls.count()).toEqual(0); - expect(mockLog.warn.calls.count()).toEqual(9); - - // Notably, keys are not needed for run calls - }); - - it("does not re-register duplicate keys", function () { - // Verify preconditions, invoke, expect to have been called - expect(mockApp.directive.calls.count()).toEqual(0); - customRegistrars.directives([{ key: "a" }, { key: "a" }]); - expect(mockApp.directive.calls.count()).toEqual(1); - - expect(mockApp.controller.calls.count()).toEqual(0); - customRegistrars.controllers([{ key: "c" }, { key: "c" }, { key: "c" }]); - expect(mockApp.controller.calls.count()).toEqual(1); - - expect(mockApp.service.calls.count()).toEqual(0); - customRegistrars.services([{ key: "b" }, { key: "b" }]); - expect(mockApp.service.calls.count()).toEqual(1); - - // None of this should have warned, this is all - // nominal behavior - expect(mockLog.warn.calls.count()).toEqual(0); - }); - - it("allows routes to be registered", function () { - var mockRouteProvider = jasmine.createSpyObj( - "$routeProvider", - ["when", "otherwise"] - ), - bundle = { - path: "test/bundle", - resources: "res" - }, - routes = [ - { - when: "foo", - templateUrl: "templates/test.html", - bundle: bundle - }, - { - templateUrl: "templates/default.html", - bundle: bundle - } - ]; - - customRegistrars.routes(routes); - - // Give it the route provider based on its config call - mockApp.config.calls.all().forEach(function (call) { - // Invoke the provided callback - call.args[0][1](mockRouteProvider); - }); - - // The "when" clause should have been mapped to the when method... - expect(mockRouteProvider.when).toHaveBeenCalled(); - expect(mockRouteProvider.when.calls.mostRecent().args[0]).toEqual("foo"); - expect(mockRouteProvider.when.calls.mostRecent().args[1].templateUrl) - .toEqual("test/bundle/res/templates/test.html"); - - // ...while the other should have been treated as a default route - expect(mockRouteProvider.otherwise).toHaveBeenCalled(); - expect(mockRouteProvider.otherwise.calls.mostRecent().args[0].templateUrl) - .toEqual("test/bundle/res/templates/default.html"); - }); - - it("accepts components for service compositing", function () { - // Most relevant code will be exercised in service compositor spec - expect(customRegistrars.components).toBeTruthy(); - customRegistrars.components([]); - }); - - it("warns if no implementation is provided for runs", function () { - // Verify precondition - expect(mockLog.warn).not.toHaveBeenCalled(); - customRegistrars.runs([{ something: "that is not a function"}]); - expect(mockLog.warn).toHaveBeenCalledWith(jasmine.any(String)); - expect(mockApp.run).not.toHaveBeenCalled(); - }); - }); - } -); diff --git a/platform/framework/test/register/ExtensionRegistrarSpec.js b/platform/framework/test/register/ExtensionRegistrarSpec.js deleted file mode 100644 index 68d8b9ccdb..0000000000 --- a/platform/framework/test/register/ExtensionRegistrarSpec.js +++ /dev/null @@ -1,119 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * ExtensionRegistrarSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/register/ExtensionRegistrar"], - function (ExtensionRegistrar) { - - describe("The extension registrar", function () { - var mockApp, - mockLog, - mockSorter, - customRegistrars, - registrar; - - beforeEach(function () { - mockApp = jasmine.createSpyObj("app", ["factory"]); - mockLog = jasmine.createSpyObj("$log", ["error", "warn", "debug", "info"]); - mockSorter = jasmine.createSpyObj("sorter", ["sort"]); - customRegistrars = {}; - - mockSorter.sort.and.callFake(function (v) { - return v; - }); - - registrar = new ExtensionRegistrar( - mockApp, - customRegistrars, - mockSorter, - mockLog - ); - }); - - it("registers extensions using the factory", function () { - registrar.registerExtensions({ things: [{}] }); - expect(mockApp.factory).toHaveBeenCalled(); - }); - - it("registers extensions with square brackets, as arrays", function () { - var callbacks = {}; - mockApp.factory.and.callFake(function (name, value) { - callbacks[name] = value[value.length - 1]; - }); - registrar.registerExtensions({ things: [{}] }); - expect(callbacks["things[]"]).toBeDefined(); - - // Verify dependency echo behavior - expect(callbacks["things[]"]("a", "b", "c")).toEqual(["a", "b", "c"]); - }); - - it("warns if multiple registrations are made for the same category of extension", function () { - registrar.registerExtensions({ things: [{}] }); - expect(mockLog.warn).not.toHaveBeenCalled(); - registrar.registerExtensions({ things: [{}] }); - expect(mockLog.warn).toHaveBeenCalled(); - }); - - it("registers empty extension categories when they are needed", function () { - var lengths = {}; - mockApp.factory.and.callFake(function (name, value) { - lengths[name] = value.length; - }); - // Nobody has registered tests[], but it looks like an extension dependency, - // so register it as an empty array. - registrar.registerExtensions({ things: [{ depends: ["tests[]", "other"] }] }); - expect(lengths["tests[]"]).toEqual(1); - expect(lengths.other).toBeUndefined(); - }); - - it("invokes custom registrars (not app.factory) when available", function () { - customRegistrars.things = jasmine.createSpy("things"); - registrar.registerExtensions({ things: [{}] }); - expect(mockApp.factory).not.toHaveBeenCalled(); - expect(customRegistrars.things).toHaveBeenCalled(); - }); - - it("sorts extensions before registering", function () { - // Some extension definitions to sort - var a = { a: 'a' }, b = { b: 'b' }, c = { c: 'c' }; - - // Fake sorting; just reverse the array - mockSorter.sort.and.callFake(function (v) { - return v.reverse(); - }); - - // Register the extensions - registrar.registerExtensions({ things: [a, b, c] }); - - // Verify registration interactions occurred in reverse-order - [c, b, a].forEach(function (extension, index) { - expect(mockApp.factory.calls.all()[index].args[1][0]()) - .toEqual(extension); - }); - }); - - }); - } -); diff --git a/platform/framework/test/register/ExtensionSorterSpec.js b/platform/framework/test/register/ExtensionSorterSpec.js deleted file mode 100644 index a6de3ebec6..0000000000 --- a/platform/framework/test/register/ExtensionSorterSpec.js +++ /dev/null @@ -1,69 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../../src/register/ExtensionSorter"], - function (ExtensionSorter) { - - describe("The extension sorter", function () { - var mockLog, - sorter; - - beforeEach(function () { - mockLog = jasmine.createSpyObj( - "$log", - ["error", "warn", "debug", "info"] - ); - - sorter = new ExtensionSorter(mockLog); - }); - - it("sorts extensions in priority order", function () { - var a = { priority: 10 }, - b = {}, - c = { priority: 'mandatory' }; // Should be +Inf - expect(sorter.sort([a, b, c])).toEqual([c, a, b]); - }); - - it("warns about unrecognized priorities", function () { - var a = { priority: 10 }, - b = {}, - c = { priority: 'mandatory' }, // Should be +Inf - d = { priority: 'GARBAGE-TEXT' }, - e = { priority: { mal: "formed"} }, - f = { priority: 3 }; - - // Sorting should use default order (note we assume - // a stable sort here as well) - expect(sorter.sort( - [a, b, c, d, e, f] - )).toEqual( - [c, a, f, b, d, e] - ); - - // Should have been warned exactly twice (for d & e) - expect(mockLog.warn.calls.count()).toEqual(2); - }); - - }); - } -); diff --git a/platform/framework/test/register/PartialConstructorSpec.js b/platform/framework/test/register/PartialConstructorSpec.js deleted file mode 100644 index 4871aefcd9..0000000000 --- a/platform/framework/test/register/PartialConstructorSpec.js +++ /dev/null @@ -1,97 +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. - *****************************************************************************/ - -/** - * PartialConstructorSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/register/PartialConstructor"], - function (PartialConstructor) { - - describe("A partial constructor", function () { - var result, - PartializedConstructor; - - function RegularConstructor(a, b, c, d) { - result = { - a: a, - b: b, - c: c, - d: d - }; - - return result; - } - - function ThisStyleConstructor(x, y, z) { - this.message = [x, y, z].join(" "); - } - - RegularConstructor.someProperty = "test property"; - - beforeEach(function () { - result = undefined; - PartializedConstructor = new PartialConstructor(RegularConstructor); - }); - - it("splits a constructor call into two stages", function () { - var RemainingConstructor = new PartializedConstructor("test"), - instance; - // first call should not have hit constructor - expect(result).toBeUndefined(); - - // Call again - instance = new RemainingConstructor(1, 2, 3); - expect(result).toEqual({ - a: "test", - b: 1, - c: 2, - d: 3 - }); - - // Should have returned the constructor's value - expect(instance).toEqual(result); - }); - - it("handles this-style constructors", function () { - var Partialized = new PartialConstructor(ThisStyleConstructor), - Remaining = new Partialized("this"), - instance = new Remaining("is", "correct"); - - // We should have everything we put in "this", and we - // should still pass an instanceof test.g - expect(instance.message).toEqual("this is correct"); - expect(instance).toEqual(new ThisStyleConstructor("this", "is", "correct")); - expect(instance instanceof ThisStyleConstructor).toBeTruthy(); - }); - - it("retains static properties after partialization", function () { - // This string should appear after invoking the partialized - // constructor, such that the resulting inner constructor - // exposes these as if we were looking at the original - // RegularConstructor. - expect(new PartializedConstructor().someProperty).toEqual("test property"); - }); - - }); - } -); diff --git a/platform/framework/test/register/ServiceCompositorSpec.js b/platform/framework/test/register/ServiceCompositorSpec.js deleted file mode 100644 index 0e78721e66..0000000000 --- a/platform/framework/test/register/ServiceCompositorSpec.js +++ /dev/null @@ -1,258 +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. - *****************************************************************************/ - -/** - * ServiceCompositorSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/register/ServiceCompositor"], - function (ServiceCompositor) { - - describe("The service compositor", function () { - var registered, - mockApp, - mockLog, - compositor; - - beforeEach(function () { - registered = {}; - - mockApp = jasmine.createSpyObj("app", ["service"]); - mockLog = jasmine.createSpyObj("$log", ["error", "warn", "info", "debug"]); - - mockApp.service.and.callFake(function (name, value) { - var factory = value[value.length - 1]; - - registered[name] = { - depends: value.slice(0, value.length - 1), - callback: value[value.length - 1] - }; - - // Track what name it was registered under - factory.registeredName = name; - }); - - compositor = new ServiceCompositor(mockApp, mockLog); - }); - - it("allows composite services to be registered", function () { - compositor.registerCompositeServices([ - { - type: "provider", - provides: "testService" - } - ]); - - expect(mockApp.service).toHaveBeenCalled(); - }); - - it("allows composite services to be registered", function () { - // Prepare components that look like resolved extensions - var components, name; - function MyDecorator() { - return {}; - } - - function MyOtherDecorator() { - return {}; - } - - function MyProvider() { - return {}; - } - - function MyOtherProvider() { - return {}; - } - - function MyAggregator() { - return {}; - } - - components = [ - MyDecorator, - MyProvider, - MyAggregator, - MyOtherDecorator, - MyOtherProvider - ]; - - MyDecorator.type = "decorator"; - MyOtherDecorator.type = "decorator"; - MyProvider.type = "provider"; - MyOtherProvider.type = "provider"; - MyAggregator.type = "aggregator"; - components.forEach(function (c) { - c.provides = "testService"; - }); - - // Add some test dependencies, to check prepending - MyOtherDecorator.depends = ["someOtherService"]; - MyAggregator.depends = ["tests[]"]; - - // Register! - compositor.registerCompositeServices(components); - - expect(mockApp.service).toHaveBeenCalled(); - - // Verify some interesting spots on dependency graph - expect(registered.testService.depends).toEqual([ - MyOtherDecorator.registeredName - ]); - expect(registered[MyOtherDecorator.registeredName].depends).toEqual([ - "someOtherService", - MyDecorator.registeredName - ]); - expect(registered[MyAggregator.registeredName].depends.length).toEqual(2); - // Get the name of the group of providers - name = registered[MyAggregator.registeredName].depends[1]; - // ...it should depend on both providers - expect(registered[name].depends).toEqual([ - MyProvider.registeredName, - MyOtherProvider.registeredName - ]); - }); - - it("allows registered composite services to be instantiated", function () { - // Prepare components that look like resolved extensions - var components, name; - function MyProvider() { - return {}; - } - - function MyOtherProvider() { - return {}; - } - - function MyAggregator() { - return {}; - } - - components = [MyProvider, MyAggregator, MyOtherProvider]; - - MyProvider.type = "provider"; - MyOtherProvider.type = "provider"; - MyAggregator.type = "aggregator"; - components.forEach(function (c) { - c.provides = "testService"; - }); - - // Register! - compositor.registerCompositeServices(components); - - expect(mockApp.service).toHaveBeenCalled(); - - // Test service should just be a reflecting dependency; - // it will depend upon (then return) the aggregator. - expect(registered.testService.callback("hello")).toEqual("hello"); - - // The aggregated provider dependencies should be similar, - // except they should reflect back the array of arguments. - // Get the name of the group of providers - name = registered[MyAggregator.registeredName].depends[0]; - // ...it should depend on both providers - expect(registered[name].callback(1, 2, "hello")).toEqual([1, 2, "hello"]); - - }); - - it("warns and skips components with no service type", function () { - // Prepare components that look like resolved extensions - var components; - function MyProvider() { - return {}; - } - - function MyDecorator() { - return {}; - } - - function MyAggregator() { - return {}; - } - - components = [MyProvider, MyAggregator, MyDecorator]; - - MyProvider.type = "provider"; - MyDecorator.type = "decorator"; - MyAggregator.type = "aggregator"; - - // Notably, we don't do - // components.forEach(function (c) { c.provides = "testService"; }); - - // Try to register... - compositor.registerCompositeServices(components); - - // Nothing should have gotten registered - expect(mockApp.service).not.toHaveBeenCalled(); - - // Should have gotten one warning for each skipped component - expect(mockLog.warn.calls.count()).toEqual(2); - expect(mockLog.info.calls.count()).toEqual(1); - }); - - it("warns about and skips aggregators with zero providers", function () { - // Prepare components that look like resolved extensions - var components; - function MyAggregator() { - return {}; - } - - components = [MyAggregator]; - - MyAggregator.type = "aggregator"; - MyAggregator.provides = "testService"; - - // Try to register... - compositor.registerCompositeServices(components); - - // Nothing should have gotten registered - expect(mockApp.service).not.toHaveBeenCalled(); - - // Should have gotten a warning - expect(mockLog.info).toHaveBeenCalled(); - }); - - it("warns about and skips decorators with nothing to decorate", function () { - // Prepare components that look like resolved extensions - var components; - function MyDecorator() { - return {}; - } - - components = [MyDecorator]; - - MyDecorator.type = "decorator"; - MyDecorator.provides = "testService"; - - // Try to register... - compositor.registerCompositeServices(components); - - // Nothing should have gotten registered - expect(mockApp.service).not.toHaveBeenCalled(); - - // Should have gotten a warning - expect(mockLog.warn).toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/framework/test/resolve/BundleResolverSpec.js b/platform/framework/test/resolve/BundleResolverSpec.js deleted file mode 100644 index 61defd28eb..0000000000 --- a/platform/framework/test/resolve/BundleResolverSpec.js +++ /dev/null @@ -1,71 +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. - *****************************************************************************/ - -/** - * BundleResolverSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/resolve/BundleResolver", "../../src/load/Bundle"], - function (BundleResolver, Bundle) { - - describe("The bundle resolver", function () { - var mockExtensionResolver, - mockLog, - resolver; - - beforeEach(function () { - mockExtensionResolver = jasmine.createSpyObj( - "extensionResolver", - ["resolve"] - ); - mockLog = jasmine.createSpyObj( - "$log", - ["error", "warn", "info", "debug"] - ); - - mockExtensionResolver.resolve.and.returnValue(Promise.resolve("a")); - - resolver = new BundleResolver( - mockExtensionResolver, - mockLog - ); - }); - - it("invokes the extension resolver for all bundle extensions", function () { - return resolver.resolveBundles([ - new Bundle("x", { extensions: { tests: [{}, {}, {}] } }), - new Bundle("y", { - extensions: { - tests: [{}, {}], - others: [{}, {}] - } - }), - new Bundle("z", { extensions: { others: [{}] } }) - ]).then(function (result) { - expect(result.tests).toEqual(["a", "a", "a", "a", "a"]); - expect(result.others).toEqual(["a", "a", "a"]); - }); - }); - - }); - } -); diff --git a/platform/framework/test/resolve/ExtensionResolverSpec.js b/platform/framework/test/resolve/ExtensionResolverSpec.js deleted file mode 100644 index 4e0b591c0f..0000000000 --- a/platform/framework/test/resolve/ExtensionResolverSpec.js +++ /dev/null @@ -1,114 +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. - *****************************************************************************/ - -/** - * ExtensionResolverSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/resolve/ExtensionResolver", "../../src/load/Bundle"], - function (ExtensionResolver, Bundle) { - - describe("", function () { - var mockLoader, - mockLog, - resolver; - - // Test implementation, to load from the mock loader - function Constructor() { - return { someKey: "some value" }; - } - - Constructor.someProperty = "some static value"; - - beforeEach(function () { - mockLoader = jasmine.createSpyObj("loader", ["load"]); - - mockLog = jasmine.createSpyObj( - "$log", - ["error", "warn", "info", "debug"] - ); - - mockLoader.load.and.returnValue(Promise.resolve(Constructor)); - - resolver = new ExtensionResolver(mockLoader, mockLog); - }); - - it("requests implementations from the implementation loader", function () { - var bundle = new Bundle("w", { - sources: "x", - extensions: { tests: [{ implementation: "y/z.js" }] } - }), - extension = bundle.getExtensions("tests")[0]; - - return resolver.resolve(extension).then(function (result) { - // Verify that the right file was requested - expect(mockLoader.load).toHaveBeenCalledWith("w/x/y/z.js"); - - // We should have resolved to the constructor from above - expect(typeof result).toEqual('function'); - expect(result().someKey).toEqual("some value"); - }); - }); - - it("issues a warning and defaults to plain definition if load fails", function () { - var bundle = new Bundle("w", { - sources: "x", - extensions: { - tests: [{ - someOtherKey: "some other value", - implementation: "y/z.js" - }] - } - }), - extension = bundle.getExtensions("tests")[0]; - - mockLoader.load.and.returnValue(Promise.reject(new Error("test error"))); - - return resolver.resolve(extension).then(function (result) { - // Should have gotten a warning - expect(mockLog.warn).toHaveBeenCalled(); - // We should have resolved to the plain definition from above - expect(typeof result).not.toEqual('function'); - expect(result.someOtherKey).toEqual("some other value"); - }); - }); - - it("ensures implementation properties are exposed", function () { - var bundle = new Bundle("w", { - sources: "x", - extensions: { tests: [{ implementation: "y/z.js" }] } - }), - extension = bundle.getExtensions("tests")[0]; - - return resolver.resolve(extension).then(function (result) { - // Verify that the right file was requested - expect(mockLoader.load).toHaveBeenCalledWith("w/x/y/z.js"); - // We should have resolved to the constructor from above - expect(typeof result).toEqual('function'); - expect(result().someKey).toEqual("some value"); - expect(result.someProperty).toEqual("some static value"); - }); - }); - - }); - } -); diff --git a/platform/framework/test/resolve/ImplementationLoaderSpec.js b/platform/framework/test/resolve/ImplementationLoaderSpec.js deleted file mode 100644 index 3ec3c8f2bd..0000000000 --- a/platform/framework/test/resolve/ImplementationLoaderSpec.js +++ /dev/null @@ -1,88 +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. - *****************************************************************************/ - -/** - * ImplementationLoaderSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../../src/resolve/ImplementationLoader"], - function (ImplementationLoader) { - - describe("The implementation loader", function () { - var required, - loader; - - function mockRequire(names, fulfill, reject) { - required = { - names: names, - fulfill: fulfill, - reject: reject - }; - } - - beforeEach(function () { - required = undefined; - loader = new ImplementationLoader(mockRequire); - }); - - it("passes script names to require", function () { - loader.load("xyz.js"); - expect(required.names).toEqual(["xyz.js"]); - }); - - it("wraps require results in a Promise that can resolve", function () { - // Load and get the result - var promise = loader.load("xyz.js").then(function (result) { - expect(result).toEqual("test result"); - }); - - required.fulfill("test result"); - - return promise; - }); - - it("wraps require results in a Promise that can reject", function () { - var result, - rejection; - - // Load and get the result - var promise = loader.load("xyz.js").then( - function (v) { - result = v; - }, - function (v) { - rejection = v; - }); - - expect(result).toBeUndefined(); - - required.reject("test result"); - - return promise.then(function () { - expect(result).toBeUndefined(); - expect(rejection).toEqual("test result"); - }); - }); - - }); - } -); diff --git a/platform/identity/README.md b/platform/identity/README.md deleted file mode 100644 index 4df89f7368..0000000000 --- a/platform/identity/README.md +++ /dev/null @@ -1,2 +0,0 @@ -This bundle is responsible for introducing identity management features -and API to Open MCT. diff --git a/platform/identity/bundle.js b/platform/identity/bundle.js deleted file mode 100644 index fc077b96f0..0000000000 --- a/platform/identity/bundle.js +++ /dev/null @@ -1,87 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/IdentityAggregator", - "./src/IdentityProvider", - "./src/IdentityCreationDecorator", - "./src/IdentityIndicator" -], function ( - IdentityAggregator, - IdentityProvider, - IdentityCreationDecorator, - IdentityIndicator -) { - - return { - name: "platform/identity", - definition: { - "extensions": { - "components": [ - { - "implementation": IdentityAggregator, - "type": "aggregator", - "provides": "identityService", - "depends": [ - "$q" - ] - }, - { - "implementation": IdentityProvider, - "type": "provider", - "provides": "identityService", - "depends": [ - "$q" - ], - "priority": "fallback" - }, - { - "type": "decorator", - "provides": "creationService", - "implementation": IdentityCreationDecorator, - "depends": [ - "identityService" - ] - } - ], - "indicators": [ - { - "implementation": IdentityIndicator, - "depends": [ - "identityService" - ] - } - ], - "types": [ - { - "properties": [ - { - "key": "creator", - "name": "Creator" - } - ] - } - ] - } - } - }; -}); diff --git a/platform/identity/src/IdentityAggregator.js b/platform/identity/src/IdentityAggregator.js deleted file mode 100644 index 79c0ec7b74..0000000000 --- a/platform/identity/src/IdentityAggregator.js +++ /dev/null @@ -1,91 +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. - *****************************************************************************/ - -/** - * Defines interfaces and common infrastructure for establishing - * a user's identity. - * @namespace platform/identity - */ -define( - function () { - - /** - * Provides information about the currently logged-in - * user, if available. - * - * @interface IdentityService - */ - - /** - * Get information about the current user. This returns a promise - * which will resolve to metadata about the user, or undefined if - * no information about the user is available. - * - * @method IdentityService#getUser - * @returns {Promise.} a promise for metadata about - * the current user - */ - - /** - * Metadata about a user. - * - * @typedef IdentityMetadata - * @property {string} name the user's human-readable name - * @property {string} key the user's machine-readable name - */ - - /** - * Aggregator for multiple identity services. Exposes the first - * defined identity provided by any provider, according to - * priority order. - * - * @constructor - * @implements {IdentityService} - * @memberof platform/identity - */ - function IdentityAggregator($q, providers) { - this.providers = providers; - this.$q = $q; - } - - function delegateGetUser(provider) { - return provider.getUser(); - } - - function identity(value) { - return value; - } - - function giveFirst(results) { - return results.filter(identity)[0]; - } - - IdentityAggregator.prototype.getUser = function () { - var $q = this.$q, - promises = this.providers.map(delegateGetUser); - - return $q.all(promises).then(giveFirst); - }; - - return IdentityAggregator; - } -); diff --git a/platform/identity/src/IdentityProvider.js b/platform/identity/src/IdentityProvider.js deleted file mode 100644 index bfa030f56f..0000000000 --- a/platform/identity/src/IdentityProvider.js +++ /dev/null @@ -1,51 +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. - *****************************************************************************/ - -/** - * Defines interfaces and common infrastructure for establishing - * a user's identity. - * @namespace platform/identity - */ -define( - function () { - - /** - * Default implementation of an identity service. Provides an - * unknown user as an `undefined` value; this is present simply - * to ensure that there is always an `identityService` available - * for platform components to use. - * - * @constructor - * @implements {IdentityService} - * @memberof platform/identity - */ - function IdentityProvider($q) { - this.userPromise = $q.when(undefined); - } - - IdentityProvider.prototype.getUser = function () { - return this.userPromise; - }; - - return IdentityProvider; - } -); diff --git a/platform/identity/test/IdentityAggregatorSpec.js b/platform/identity/test/IdentityAggregatorSpec.js deleted file mode 100644 index ea9f3fc24d..0000000000 --- a/platform/identity/test/IdentityAggregatorSpec.js +++ /dev/null @@ -1,139 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/IdentityAggregator"], - function (IdentityAggregator) { - - describe("The identity aggregator", function () { - var mockProviders, - mockQ, - resolves, - testUsers, - aggregator; - - function resolveProviderPromises() { - ['a', 'b', 'c'].forEach(function (id, i) { - resolves[id](testUsers[i]); - }); - } - - beforeEach(function () { - testUsers = [ - { - key: "user0", - name: "User Zero" - }, - { - key: "user1", - name: "User One" - }, - { - key: "user2", - name: "User Two" - } - ]; - - resolves = {}; - - mockProviders = ['a', 'b', 'c'].map(function (id) { - var mockProvider = jasmine.createSpyObj( - 'provider-' + id, - ['getUser'] - ); - - mockProvider.getUser.and.returnValue(new Promise(function (r) { - resolves[id] = r; - })); - - return mockProvider; - }); - - mockQ = jasmine.createSpyObj('$q', ['all']); - mockQ.all.and.callFake(function (promises) { - return Promise.all(promises); - }); - - aggregator = new IdentityAggregator( - mockQ, - mockProviders - ); - }); - - it("delegates to the aggregated providers", function () { - // Verify precondition - mockProviders.forEach(function (p) { - expect(p.getUser).not.toHaveBeenCalled(); - }); - - aggregator.getUser(); - - mockProviders.forEach(function (p) { - expect(p.getUser).toHaveBeenCalled(); - }); - }); - - it("returns the first result when it is defined", function () { - var promise = aggregator.getUser(); - - resolveProviderPromises(); - - return promise.then(function (user) { - expect(user).toEqual(testUsers[0]); - }); - }); - - it("returns a later result when earlier results are undefined", function () { - testUsers[0] = undefined; - - var promise = aggregator.getUser(); - - resolveProviderPromises(); - - return promise.then(function (user) { - expect(user).toEqual(testUsers[1]); - }); - }); - - it("returns undefined when no providers expose users", function () { - testUsers = [undefined, undefined, undefined]; - - var promise = aggregator.getUser(); - - resolveProviderPromises(); - - return promise.then(function (user) { - expect(user).toBe(undefined); - }); - }); - - it("returns undefined when there are no providers", function () { - var promise = new IdentityAggregator(mockQ, []).getUser(); - - return promise.then(function (user) { - expect(user).toBe(undefined); - }); - }); - - }); - } -); diff --git a/platform/identity/test/IdentityCreationDecoratorSpec.js b/platform/identity/test/IdentityCreationDecoratorSpec.js deleted file mode 100644 index b35de02b37..0000000000 --- a/platform/identity/test/IdentityCreationDecoratorSpec.js +++ /dev/null @@ -1,95 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - '../src/IdentityCreationDecorator' - ], - function (IdentityCreationDecorator) { - - describe("IdentityCreationDecorator", function () { - var mockIdentityService, - mockCreationService, - mockParent, - mockCreatedObject, - decorator; - - beforeEach(function () { - mockIdentityService = jasmine.createSpyObj( - 'identityService', - ['getUser'] - ); - mockCreationService = jasmine.createSpyObj( - 'creationService', - ['createObject'] - ); - mockParent = jasmine.createSpyObj( - 'parentObject', - ['getCapability', 'getId', 'getModel', 'hasCapability', 'useCapability'] - ); - mockCreatedObject = jasmine.createSpyObj( - 'domainObject', - ['getCapability', 'getId', 'getModel', 'hasCapability', 'useCapability'] - ); - mockCreationService.createObject - .and.returnValue(Promise.resolve(mockCreatedObject)); - mockIdentityService.getUser - .and.returnValue(Promise.resolve({ key: "test-user-id" })); - mockParent.getId.and.returnValue('test-id'); - decorator = new IdentityCreationDecorator( - mockIdentityService, - mockCreationService - ); - }); - - it("delegates to its decorated service when identity is available", function () { - var testModel = { someKey: "some value" }; - - return decorator.createObject(testModel, mockParent) - .then(function (object) { - expect(object).toEqual(mockCreatedObject); - }); - }); - - it("adds a creator property", function () { - var testModel = { someKey: "some value" }; - - return decorator.createObject(testModel, mockParent) - .then(function (object) { - expect(object) - .toEqual(mockCreatedObject); - - // Make sure arguments were delegated appropriately - expect(mockCreationService.createObject) - .toHaveBeenCalledWith( - { - someKey: "some value", - creator: "test-user-id" - }, - mockParent - ); - }); - }); - - }); - } -); diff --git a/platform/identity/test/IdentityIndicatorSpec.js b/platform/identity/test/IdentityIndicatorSpec.js deleted file mode 100644 index 705089e06e..0000000000 --- a/platform/identity/test/IdentityIndicatorSpec.js +++ /dev/null @@ -1,70 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/IdentityIndicator"], - function (IdentityIndicator) { - - describe("The identity indicator", function () { - var mockPromise, - mockIdentityService, - indicator; - - beforeEach(function () { - mockPromise = jasmine.createSpyObj('promise', ['then']); - mockIdentityService = jasmine.createSpyObj( - 'identityService', - ['getUser'] - ); - - mockIdentityService.getUser.and.returnValue(mockPromise); - - indicator = new IdentityIndicator(mockIdentityService); - }); - - it("shows information about the current user", function () { - mockPromise.then.calls.mostRecent().args[0]({ - key: "testuserid", - name: "A User" - }); - expect(indicator.getCssClass()).toEqual("icon-person"); - expect(indicator.getText()).toEqual("A User"); - expect(indicator.getDescription().indexOf("testuserid")) - .not.toEqual(-1); - }); - - it("shows nothing while no user information is available", function () { - expect(indicator.getCssClass()).toBeUndefined(); - expect(indicator.getText()).toBeUndefined(); - expect(indicator.getDescription()).toBeUndefined(); - }); - - it("shows nothing when there is no identity information", function () { - mockPromise.then.calls.mostRecent().args[0](undefined); - expect(indicator.getCssClass()).toBeUndefined(); - expect(indicator.getText()).toBeUndefined(); - expect(indicator.getDescription()).toBeUndefined(); - }); - - }); - } -); diff --git a/platform/persistence/README.md b/platform/persistence/README.md deleted file mode 100644 index e5ffbd156d..0000000000 --- a/platform/persistence/README.md +++ /dev/null @@ -1 +0,0 @@ -This directory contains bundles relating to the persistence of domain objects. diff --git a/platform/persistence/aggregator/bundle.js b/platform/persistence/aggregator/bundle.js deleted file mode 100644 index 142cfc2a58..0000000000 --- a/platform/persistence/aggregator/bundle.js +++ /dev/null @@ -1,46 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/PersistenceAggregator" -], function ( - PersistenceAggregator -) { - - return { - name: "platform/persistence/aggregator", - definition: { - "extensions": { - "components": [ - { - "provides": "persistenceService", - "type": "aggregator", - "depends": [ - "$q" - ], - "implementation": PersistenceAggregator - } - ] - } - } - }; -}); diff --git a/platform/persistence/aggregator/src/PersistenceAggregator.js b/platform/persistence/aggregator/src/PersistenceAggregator.js deleted file mode 100644 index 712511df8b..0000000000 --- a/platform/persistence/aggregator/src/PersistenceAggregator.js +++ /dev/null @@ -1,90 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - // Return values to use when a persistence space is unknown, - // and there is no appropriate provider to route to. - var METHOD_DEFAULTS = { - createObject: false, - readObject: undefined, - listObjects: [], - updateObject: false, - deleteObject: false - }; - - /** - * Aggregates multiple persistence providers, such that they can be - * utilized as if they were a single object. This is achieved by - * routing persistence calls to an appropriate provider; the space - * specified at call time is matched with the first provider (per - * priority order) which reports that it provides persistence for - * this space. - * - * @memberof platform/persistence/aggregator - * @constructor - * @implements {PersistenceService} - * @param $q Angular's $q, for promises - * @param {PersistenceService[]} providers the providers to aggregate - */ - function PersistenceAggregator($q, providers) { - var providerMap = {}; - - function addToMap(provider) { - return provider.listSpaces().then(function (spaces) { - spaces.forEach(function (space) { - providerMap[space] = providerMap[space] || provider; - }); - }); - } - - this.providerMapPromise = $q.all(providers.map(addToMap)) - .then(function () { - return providerMap; - }); - } - - PersistenceAggregator.prototype.listSpaces = function () { - return this.providerMapPromise.then(function (map) { - return Object.keys(map); - }); - }; - - Object.keys(METHOD_DEFAULTS).forEach(function (method) { - PersistenceAggregator.prototype[method] = function (space) { - var delegateArgs = Array.prototype.slice.apply(arguments, []); - - return this.providerMapPromise.then(function (map) { - var provider = map[space]; - - return provider - ? provider[method].apply(provider, delegateArgs) - : METHOD_DEFAULTS[method]; - }); - }; - }); - - return PersistenceAggregator; - } -); diff --git a/platform/persistence/aggregator/test/PersistenceAggregatorSpec.js b/platform/persistence/aggregator/test/PersistenceAggregatorSpec.js deleted file mode 100644 index 62b9e35403..0000000000 --- a/platform/persistence/aggregator/test/PersistenceAggregatorSpec.js +++ /dev/null @@ -1,105 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['../src/PersistenceAggregator'], - function (PersistenceAggregator) { - - var PERSISTENCE_SERVICE_METHODS = [ - 'listSpaces', - 'listObjects', - 'createObject', - 'readObject', - 'updateObject', - 'deleteObject' - ], - WRAPPED_METHODS = PERSISTENCE_SERVICE_METHODS.filter(function (m) { - return m !== 'listSpaces'; - }); - - function fakePromise(value) { - return (value || {}).then ? value : { - then: function (callback) { - return fakePromise(callback(value)); - } - }; - } - - describe("PersistenceAggregator", function () { - var mockQ, - mockProviders, - mockCallback, - testSpaces, - aggregator; - - beforeEach(function () { - testSpaces = ['a', 'b', 'c']; - mockQ = jasmine.createSpyObj("$q", ['all']); - mockProviders = testSpaces.map(function (space) { - var mockProvider = jasmine.createSpyObj( - 'provider-' + space, - PERSISTENCE_SERVICE_METHODS - ); - PERSISTENCE_SERVICE_METHODS.forEach(function (m) { - mockProvider[m].and.returnValue(fakePromise(true)); - }); - mockProvider.listSpaces.and.returnValue(fakePromise([space])); - - return mockProvider; - }); - mockCallback = jasmine.createSpy(); - - mockQ.all.and.callFake(function (fakePromises) { - var result = []; - fakePromises.forEach(function (p) { - p.then(function (v) { - result.push(v); - }); - }); - - return fakePromise(result); - }); - - aggregator = new PersistenceAggregator(mockQ, mockProviders); - }); - - it("exposes spaces for all providers", function () { - aggregator.listSpaces().then(mockCallback); - expect(mockCallback).toHaveBeenCalledWith(testSpaces); - }); - - WRAPPED_METHODS.forEach(function (m) { - it("redirects " + m + " calls to an appropriate provider", function () { - testSpaces.forEach(function (space, index) { - var key = 'key-' + space, - value = 'val-' + space; - expect(aggregator[m](space, key, value)) - .toEqual(mockProviders[index][m]()); - expect(mockProviders[index][m]) - .toHaveBeenCalledWith(space, key, value); - }); - }); - }); - - }); - } -); diff --git a/platform/persistence/elastic/README.md b/platform/persistence/elastic/README.md deleted file mode 100644 index 413501d9ca..0000000000 --- a/platform/persistence/elastic/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Elasticsearch Persistence Provider -An adapter for using Elastic for persistence of user-created objects. The installation function takes the URL for an -Elasticsearch server as a parameter. - -## Installation -```js -openmct.install(openmct.plugins.Elasticsearch('http://localhost:9200')) -``` \ No newline at end of file diff --git a/platform/persistence/elastic/bundle.js b/platform/persistence/elastic/bundle.js deleted file mode 100644 index 7fedf6e51d..0000000000 --- a/platform/persistence/elastic/bundle.js +++ /dev/null @@ -1,97 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/ElasticPersistenceProvider", - "./src/ElasticSearchProvider", - "./src/ElasticIndicator" -], function ( - ElasticPersistenceProvider, - ElasticSearchProvider, - ElasticIndicator -) { - - return { - name: "platform/persistence/elastic", - definition: { - "name": "ElasticSearch Persistence", - "description": "Adapter to read and write objects using an ElasticSearch instance.", - "extensions": { - "components": [ - { - "provides": "persistenceService", - "type": "provider", - "implementation": ElasticPersistenceProvider, - "depends": [ - "$http", - "$q", - "PERSISTENCE_SPACE", - "ELASTIC_ROOT", - "ELASTIC_PATH" - ] - }, - { - "provides": "searchService", - "type": "provider", - "implementation": ElasticSearchProvider, - "depends": [ - "$http", - "ELASTIC_ROOT" - ] - } - ], - "constants": [ - { - "key": "PERSISTENCE_SPACE", - "value": "mct" - }, - { - "key": "ELASTIC_ROOT", - "value": "http://localhost:9200", - "priority": "fallback" - }, - { - "key": "ELASTIC_PATH", - "value": "mct/_doc", - "priority": "fallback" - }, - { - "key": "ELASTIC_INDICATOR_INTERVAL", - "value": 15000, - "priority": "fallback" - } - ], - "indicators": [ - { - "implementation": ElasticIndicator, - "depends": [ - "$http", - "$interval", - "ELASTIC_ROOT", - "ELASTIC_INDICATOR_INTERVAL" - ] - } - ] - } - } - }; -}); diff --git a/platform/persistence/elastic/src/ElasticIndicator.js b/platform/persistence/elastic/src/ElasticIndicator.js deleted file mode 100644 index dcb9e86eb5..0000000000 --- a/platform/persistence/elastic/src/ElasticIndicator.js +++ /dev/null @@ -1,104 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - // Set of connection states; changing among these states will be - // reflected in the indicator's appearance. - // CONNECTED: Everything nominal, expect to be able to read/write. - // DISCONNECTED: HTTP failed; maybe misconfigured, disconnected. - // PENDING: Still trying to connect, and haven't failed yet. - var CONNECTED = { - text: "Connected", - glyphClass: "ok", - statusClass: "s-status-on", - description: "Connected to the domain object database." - }, - DISCONNECTED = { - text: "Disconnected", - glyphClass: "err", - statusClass: "s-status-caution", - description: "Unable to connect to the domain object database." - }, - PENDING = { - text: "Checking connection..." - }; - - /** - * Indicator for the current ElasticSearch connection. Polls - * ElasticSearch at a regular interval (defined by bundle constants) - * to ensure that the database is available. - * @constructor - * @memberof platform/persistence/elastic - * @implements {Indicator} - * @param $http Angular's $http service - * @param $interval Angular's $interval service - * @param {string} path the URL to poll for elasticsearch availability - * @param {number} interval the interval, in milliseconds, to poll at - */ - function ElasticIndicator($http, $interval, path, interval) { - // Track the current connection state - var self = this; - - this.state = PENDING; - - // Callback if the HTTP request to ElasticSearch fails - function handleError() { - self.state = DISCONNECTED; - } - - // Callback if the HTTP request succeeds. - function handleResponse() { - self.state = CONNECTED; - } - - // Try to connect to ElasticSearch, and update the indicator. - function updateIndicator() { - $http.get(path).then(handleResponse, handleError); - } - - // Update the indicator initially, and start polling. - updateIndicator(); - $interval(updateIndicator, interval, 0, false); - } - - ElasticIndicator.prototype.getCssClass = function () { - return "c-indicator--clickable icon-suitcase"; - }; - - ElasticIndicator.prototype.getGlyphClass = function () { - return this.state.glyphClass; - }; - - ElasticIndicator.prototype.getText = function () { - return this.state.text; - }; - - ElasticIndicator.prototype.getDescription = function () { - return this.state.description; - }; - - return ElasticIndicator; - } -); diff --git a/platform/persistence/elastic/src/ElasticPersistenceProvider.js b/platform/persistence/elastic/src/ElasticPersistenceProvider.js deleted file mode 100644 index 2b5c801d4a..0000000000 --- a/platform/persistence/elastic/src/ElasticPersistenceProvider.js +++ /dev/null @@ -1,168 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * This bundle implements a persistence service which uses ElasticSearch to - * store documents. - * @namespace platform/persistence/elastic - */ -define( - [], - function () { - - // JSLint doesn't like underscore-prefixed properties, - // so hide them here. - var SRC = "_source", - CONFLICT = 409, - SEQ_NO = "_seq_no", - PRIMARY_TERM = "_primary_term"; - - /** - * The ElasticPersistenceProvider reads and writes JSON documents - * (more specifically, domain object models) to/from an ElasticSearch - * instance. - * @memberof platform/persistence/elastic - * @constructor - * @implements {PersistenceService} - * @param $http Angular's $http service - * @param $interval Angular's $interval service - * @param {string} space the name of the persistence space being served - * @param {string} root the root of the path to ElasticSearch - * @param {string} path the path to domain objects within ElasticSearch - */ - function ElasticPersistenceProvider($http, $q, space, root, path) { - this.spaces = [space]; - this.revs = {}; - this.$http = $http; - this.$q = $q; - this.root = root; - this.path = path; - } - - // Issue a request using $http; get back the plain JS object - // from the expected JSON response - ElasticPersistenceProvider.prototype.request = function (subpath, method, value, params) { - return this.$http({ - method: method, - url: this.root + '/' + this.path + '/' + subpath, - params: params, - data: value - }).then(function (response) { - return response.data; - }, function (response) { - return (response || {}).data; - }); - }; - - // Shorthand methods for GET/PUT methods - ElasticPersistenceProvider.prototype.get = function (subpath) { - return this.request(subpath, "GET"); - }; - - ElasticPersistenceProvider.prototype.put = function (subpath, value, params) { - return this.request(subpath, "PUT", value, params); - }; - - ElasticPersistenceProvider.prototype.del = function (subpath) { - return this.request(subpath, "DELETE"); - }; - - // Handle an update error - ElasticPersistenceProvider.prototype.handleError = function (response, key) { - var error = new Error("Persistence error."), - $q = this.$q; - if ((response || {}).status === CONFLICT) { - error.key = "revision"; - - // Load the updated model, then reject the promise - return this.get(key).then(function (res) { - error.model = res[SRC]; - - return $q.reject(error); - }); - } - - // Reject the promise - return this.$q.reject(error); - }; - - // Get a domain object model out of ElasticSearch's response - ElasticPersistenceProvider.prototype.getModel = function (response) { - if (response && response[SRC]) { - this.revs[response[SEQ_NO]] = response[SEQ_NO]; - this.revs[response[PRIMARY_TERM]] = response[PRIMARY_TERM]; - - return response[SRC]; - } else { - return undefined; - } - }; - - // Check the response to a create/update/delete request; - // track the rev if it's valid, otherwise return false to - // indicate that the request failed. - ElasticPersistenceProvider.prototype.checkResponse = function (response, key) { - if (response && !response.error) { - this.revs[SEQ_NO] = response[SEQ_NO]; - this.revs[PRIMARY_TERM] = response[PRIMARY_TERM]; - - return response; - } else { - return this.handleError(response, key); - } - }; - - // Public API - ElasticPersistenceProvider.prototype.listSpaces = function () { - return this.$q.when(this.spaces); - }; - - ElasticPersistenceProvider.prototype.listObjects = function () { - // Not yet implemented - return this.$q.when([]); - }; - - ElasticPersistenceProvider.prototype.createObject = function (space, key, value) { - return this.put(key, value).then(this.checkResponse.bind(this)); - }; - - ElasticPersistenceProvider.prototype.readObject = function (space, key) { - return this.get(key).then(this.getModel.bind(this)); - }; - - ElasticPersistenceProvider.prototype.updateObject = function (space, key, value) { - var self = this; - function checkUpdate(response) { - return self.checkResponse(response, key); - } - - return this.put(key, value) - .then(checkUpdate); - }; - - ElasticPersistenceProvider.prototype.deleteObject = function (space, key) { - return this.del(key).then(this.checkResponse.bind(this)); - }; - - return ElasticPersistenceProvider; - } -); diff --git a/platform/persistence/elastic/src/ElasticSearchProvider.js b/platform/persistence/elastic/src/ElasticSearchProvider.js deleted file mode 100644 index 28ce84362d..0000000000 --- a/platform/persistence/elastic/src/ElasticSearchProvider.js +++ /dev/null @@ -1,142 +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. - *****************************************************************************/ - -/** - * Module defining ElasticSearchProvider. Created by shale on 07/16/2015. - */ -define([ - -], function ( - -) { - - var ID_PROPERTY = '_id', - SOURCE_PROPERTY = '_source', - SCORE_PROPERTY = '_score'; - - /** - * A search service which searches through domain objects in - * the filetree using ElasticSearch. - * - * @constructor - * @param $http Angular's $http service, for working with urls. - * @param ROOT the constant `ELASTIC_ROOT` which allows us to - * interact with ElasticSearch. - */ - function ElasticSearchProvider($http, ROOT) { - this.$http = $http; - this.root = ROOT; - } - - /** - * Search for domain objects using elasticsearch as a search provider. - * - * @param {String} searchTerm the term to search by. - * @param {Number} [maxResults] the max number of results to return. - * @returns {Promise} promise for a modelResults object. - */ - ElasticSearchProvider.prototype.query = function (searchTerm, maxResults) { - var searchUrl = this.root + '/_search/', - params = {}, - provider = this; - - searchTerm = this.cleanTerm(searchTerm); - searchTerm = this.fuzzyMatchUnquotedTerms(searchTerm); - - params.q = searchTerm; - params.size = maxResults; - - return this - .$http({ - method: "GET", - url: searchUrl, - params: params - }) - .then(function success(succesResponse) { - return provider.parseResponse(succesResponse); - }, function error() { - // Gracefully fail. - return { - hits: [], - total: 0 - }; - }); - }; - - /** - * Clean excess whitespace from a search term and return the cleaned - * version. - * - * @private - * @param {string} the search term to clean. - * @returns {string} search terms cleaned of excess whitespace. - */ - ElasticSearchProvider.prototype.cleanTerm = function (term) { - return term.trim().replace(/ +/g, ' '); - }; - - /** - * Add fuzzy matching markup to search terms that are not quoted. - * - * The following: - * hello welcome "to quoted village" have fun - * will become - * hello~ welcome~ "to quoted village" have~ fun~ - * - * @private - */ - ElasticSearchProvider.prototype.fuzzyMatchUnquotedTerms = function (query) { - var matchUnquotedSpaces = '\\s+(?=([^"]*"[^"]*")*[^"]*$)', - matcher = new RegExp(matchUnquotedSpaces, 'g'); - - return query - .replace(matcher, '~ ') - .replace(/$/, '~') - .replace(/"~+/, '"'); - }; - - /** - * Parse the response from ElasticSearch and convert it to a - * modelResults object. - * - * @private - * @param response a ES response object from $http - * @returns modelResults - */ - ElasticSearchProvider.prototype.parseResponse = function (response) { - var results = response.data.hits.hits, - searchResults = results.map(function (result) { - return { - id: result[ID_PROPERTY], - model: result[SOURCE_PROPERTY], - score: result[SCORE_PROPERTY] - }; - }); - - return { - hits: searchResults, - total: response.data.hits.total - }; - }; - - return ElasticSearchProvider; -}); diff --git a/platform/persistence/elastic/test/ElasticIndicatorSpec.js b/platform/persistence/elastic/test/ElasticIndicatorSpec.js deleted file mode 100644 index ea4bbd1751..0000000000 --- a/platform/persistence/elastic/test/ElasticIndicatorSpec.js +++ /dev/null @@ -1,109 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/ElasticIndicator"], - function (ElasticIndicator) { - - xdescribe("The ElasticSearch status indicator", function () { - var mockHttp, - mockInterval, - testPath, - testInterval, - mockPromise, - indicator; - - beforeEach(function () { - mockHttp = jasmine.createSpyObj("$http", ["get"]); - mockInterval = jasmine.createSpy("$interval"); - mockPromise = jasmine.createSpyObj("promise", ["then"]); - testPath = "/test/path"; - testInterval = 12321; // Some number - - mockHttp.get.and.returnValue(mockPromise); - - indicator = new ElasticIndicator( - mockHttp, - mockInterval, - testPath, - testInterval - ); - }); - - it("polls for changes", function () { - expect(mockInterval).toHaveBeenCalledWith( - jasmine.any(Function), - testInterval, - 0, - false - ); - }); - - it("has a database icon", function () { - expect(indicator.getCssClass()).toEqual("icon-suitcase"); - }); - - it("consults the database at the configured path", function () { - expect(mockHttp.get).toHaveBeenCalledWith(testPath); - }); - - it("changes when the database connection is nominal", function () { - var initialText = indicator.getText(), - initialDescrption = indicator.getDescription(), - initialGlyphClass = indicator.getGlyphClass(); - - // Nominal just means getting back an object, without - // an error field. - mockPromise.then.calls.mostRecent().args[0]({ data: {} }); - - // Verify that these values changed; - // don't test for specific text. - expect(indicator.getText()).not.toEqual(initialText); - expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass); - expect(indicator.getDescription()).not.toEqual(initialDescrption); - - // Do check for specific class - expect(indicator.getGlyphClass()).toEqual("ok"); - }); - - it("changes when the server cannot be reached", function () { - var initialText = indicator.getText(), - initialDescrption = indicator.getDescription(), - initialGlyphClass = indicator.getGlyphClass(); - - // Nominal just means getting back an object, without - // an error field. - mockPromise.then.calls.mostRecent().args[1]({ data: {} }); - - // Verify that these values changed; - // don't test for specific text. - expect(indicator.getText()).not.toEqual(initialText); - expect(indicator.getGlyphClass()).not.toEqual(initialGlyphClass); - expect(indicator.getDescription()).not.toEqual(initialDescrption); - - // Do check for specific class - expect(indicator.getGlyphClass()).toEqual("err"); - }); - - }); - } -); diff --git a/platform/persistence/elastic/test/ElasticPersistenceProviderSpec.js b/platform/persistence/elastic/test/ElasticPersistenceProviderSpec.js deleted file mode 100644 index 18a3836bd4..0000000000 --- a/platform/persistence/elastic/test/ElasticPersistenceProviderSpec.js +++ /dev/null @@ -1,253 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/ElasticPersistenceProvider"], - function (ElasticPersistenceProvider) { - - describe("The ElasticSearch persistence provider", function () { - var mockHttp, - mockQ, - testSpace = "testSpace", - testRoot = "/test", - testPath = "db", - capture, - provider; - - function mockPromise(value) { - return (value || {}).then ? value : { - then: function (callback) { - return mockPromise(callback(value)); - } - }; - } - - beforeEach(function () { - mockHttp = jasmine.createSpy("$http"); - mockQ = jasmine.createSpyObj("$q", ["when", "reject"]); - - mockQ.when.and.callFake(mockPromise); - mockQ.reject.and.callFake(function (value) { - return { - then: function (ignored, callback) { - return mockPromise(callback(value)); - } - }; - }); - - // Capture promise results - capture = jasmine.createSpy("capture"); - - provider = new ElasticPersistenceProvider( - mockHttp, - mockQ, - testSpace, - testRoot, - testPath - ); - }); - - it("reports available spaces", function () { - provider.listSpaces().then(capture); - expect(capture).toHaveBeenCalledWith([testSpace]); - }); - - // General pattern of tests below is to simulate ElasticSearch's - // response, verify that request looks like what ElasticSearch - // would expect, and finally verify that ElasticPersistenceProvider's - // return values match what is expected. - it("lists all available documents", function () { - // Not implemented yet - provider.listObjects().then(capture); - expect(capture).toHaveBeenCalledWith([]); - }); - - it("allows object creation", function () { - var model = { someKey: "some value" }; - mockHttp.and.returnValue(mockPromise({ - data: { - "_id": "abc", - "_seq_no": 1, - "_primary_term": 1 - } - })); - provider.createObject("testSpace", "abc", model).then(capture); - expect(mockHttp).toHaveBeenCalledWith({ - url: "/test/db/abc", - method: "PUT", - data: model, - params: undefined - }); - expect(capture.calls.mostRecent().args[0]).toBeTruthy(); - }); - - it("allows object models to be read back", function () { - var model = { someKey: "some value" }; - mockHttp.and.returnValue(mockPromise({ - data: { - "_id": "abc", - "_seq_no": 1, - "_primary_term": 1, - "_source": model - } - })); - provider.readObject("testSpace", "abc").then(capture); - expect(mockHttp).toHaveBeenCalledWith({ - url: "/test/db/abc", - method: "GET", - params: undefined, - data: undefined - }); - expect(capture).toHaveBeenCalledWith(model); - }); - - it("allows object update", function () { - var model = { someKey: "some value" }; - - // First do a read to populate rev tags... - mockHttp.and.returnValue(mockPromise({ - data: { - "_id": "abc", - "_source": {} - } - })); - provider.readObject("testSpace", "abc"); - - // Now perform an update - mockHttp.and.returnValue(mockPromise({ - data: { - "_id": "abc", - "_seq_no": 1, - "_source": {} - } - })); - provider.updateObject("testSpace", "abc", model).then(capture); - expect(mockHttp).toHaveBeenCalledWith({ - url: "/test/db/abc", - method: "PUT", - params: undefined, - data: model - }); - expect(capture.calls.mostRecent().args[0]).toBeTruthy(); - }); - - it("allows object deletion", function () { - // First do a read to populate rev tags... - mockHttp.and.returnValue(mockPromise({ - data: { - "_id": "abc", - "_source": {} - } - })); - provider.readObject("testSpace", "abc"); - - // Now perform an update - mockHttp.and.returnValue(mockPromise({ - data: { - "_id": "abc", - "_source": {} - } - })); - provider.deleteObject("testSpace", "abc", {}).then(capture); - expect(mockHttp).toHaveBeenCalledWith({ - url: "/test/db/abc", - method: "DELETE", - params: undefined, - data: undefined - }); - expect(capture.calls.mostRecent().args[0]).toBeTruthy(); - }); - - it("returns undefined when objects are not found", function () { - // Act like a 404 - mockHttp.and.returnValue({ - then: function (success, fail) { - return mockPromise(fail()); - } - }); - provider.readObject("testSpace", "abc").then(capture); - expect(capture).toHaveBeenCalledWith(undefined); - }); - - it("handles rejection due to _seq_no", function () { - var model = { someKey: "some value" }, - mockErrorCallback = jasmine.createSpy('error'); - - // First do a read to populate rev tags... - mockHttp.and.returnValue(mockPromise({ - data: { - "_id": "abc", - "_seq_no": 1, - "_source": {} - } - })); - provider.readObject("testSpace", "abc"); - - // Now perform an update - mockHttp.and.returnValue(mockPromise({ - data: { - "status": 409, - "error": "Revision error..." - } - })); - provider.updateObject("testSpace", "abc", model).then( - capture, - mockErrorCallback - ); - - expect(capture).not.toHaveBeenCalled(); - expect(mockErrorCallback).toHaveBeenCalled(); - }); - - it("handles rejection due to unknown reasons", function () { - var model = { someKey: "some value" }, - mockErrorCallback = jasmine.createSpy('error'); - - // First do a read to populate rev tags... - mockHttp.and.returnValue(mockPromise({ - data: { - "_id": "abc", - "_seq_no": 1, - "_source": {} - } - })); - provider.readObject("testSpace", "abc"); - - // Now perform an update - mockHttp.and.returnValue(mockPromise({ - data: { - "status": 410, - "error": "Revision error..." - } - })); - provider.updateObject("testSpace", "abc", model).then( - capture, - mockErrorCallback - ); - - expect(capture).not.toHaveBeenCalled(); - expect(mockErrorCallback).toHaveBeenCalled(); - }); - - }); - } -); diff --git a/platform/persistence/elastic/test/ElasticSearchProviderSpec.js b/platform/persistence/elastic/test/ElasticSearchProviderSpec.js deleted file mode 100644 index 0c751ed3db..0000000000 --- a/platform/persistence/elastic/test/ElasticSearchProviderSpec.js +++ /dev/null @@ -1,164 +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. - *****************************************************************************/ - -/** - * SearchSpec. Created by shale on 07/31/2015. - */ -define([ - '../src/ElasticSearchProvider' -], function ( - ElasticSearchProvider -) { - - describe('ElasticSearchProvider', function () { - var $http, - ROOT, - provider; - - beforeEach(function () { - $http = jasmine.createSpy('$http'); - ROOT = 'http://localhost:9200'; - - provider = new ElasticSearchProvider($http, ROOT); - }); - - describe('query', function () { - beforeEach(function () { - spyOn(provider, 'cleanTerm').and.returnValue('cleanedTerm'); - spyOn(provider, 'fuzzyMatchUnquotedTerms').and.returnValue('fuzzy'); - spyOn(provider, 'parseResponse').and.returnValue('parsedResponse'); - $http.and.returnValue(Promise.resolve({ - data: { - hits: { - hits: [] - } - } - })); - }); - - it('cleans terms and adds fuzzyness', function () { - return provider.query('hello', 10) - .then(() => { - expect(provider.cleanTerm).toHaveBeenCalledWith('hello'); - expect(provider.fuzzyMatchUnquotedTerms) - .toHaveBeenCalledWith('cleanedTerm'); - }); - }); - - it('calls through to $http', function () { - return provider.query('hello', 10).then(() => { - expect($http).toHaveBeenCalledWith({ - method: 'GET', - params: { - q: 'fuzzy', - size: 10 - }, - url: 'http://localhost:9200/_search/' - }); - }); - }); - - it('gracefully fails when http fails', function () { - $http.and.returnValue(Promise.reject()); - - return provider - .query('hello', 10) - .then(function (results) { - expect(results).toEqual({ - hits: [], - total: 0 - }); - }); - }); - - it('parses and returns when http succeeds', function () { - $http.and.returnValue(Promise.resolve('successResponse')); - - return provider - .query('hello', 10) - .then(function (results) { - expect(provider.parseResponse) - .toHaveBeenCalledWith('successResponse'); - expect(results).toBe('parsedResponse'); - }); - }); - }); - - it('can clean terms', function () { - expect(provider.cleanTerm(' asdhs ')).toBe('asdhs'); - expect(provider.cleanTerm(' and some words')) - .toBe('and some words'); - expect(provider.cleanTerm('Nice input')).toBe('Nice input'); - }); - - it('can create fuzzy term matchers', function () { - expect(provider.fuzzyMatchUnquotedTerms('pwr dvc 43')) - .toBe('pwr~ dvc~ 43~'); - - expect(provider.fuzzyMatchUnquotedTerms( - 'hello welcome "to quoted village" have fun' - )).toBe( - 'hello~ welcome~ "to quoted village" have~ fun~' - ); - }); - - it('can parse responses', function () { - var elasticSearchResponse = { - data: { - hits: { - total: 2, - hits: [ - { - '_id': 'hit1Id', - '_source': 'hit1Model', - '_score': 0.56 - }, - { - '_id': 'hit2Id', - '_source': 'hit2Model', - '_score': 0.34 - } - ] - } - } - }; - - expect(provider.parseResponse(elasticSearchResponse)) - .toEqual({ - hits: [ - { - id: 'hit1Id', - model: 'hit1Model', - score: 0.56 - }, - { - id: 'hit2Id', - model: 'hit2Model', - score: 0.34 - } - ], - total: 2 - }); - }); - }); - -}); diff --git a/platform/persistence/queue/README.md b/platform/persistence/queue/README.md deleted file mode 100644 index b64bba3c34..0000000000 --- a/platform/persistence/queue/README.md +++ /dev/null @@ -1,5 +0,0 @@ -This bundle provides an Overwrite/Cancel dialog when persisting -domain objects, if persistence fails. It is meant to be paired -with a persistence adapter which performs revision-checking -on update calls, in order to provide the user interface for -choosing between Overwrite and Cancel in that situation. diff --git a/platform/persistence/queue/bundle.js b/platform/persistence/queue/bundle.js deleted file mode 100644 index 0aa0c79e31..0000000000 --- a/platform/persistence/queue/bundle.js +++ /dev/null @@ -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. - *****************************************************************************/ - -define([ - "./src/QueuingPersistenceCapabilityDecorator", - "./src/PersistenceQueue", - "./src/PersistenceFailureController", - "./res/templates/persistence-failure-dialog.html" -], function ( - QueuingPersistenceCapabilityDecorator, - PersistenceQueue, - PersistenceFailureController, - persistenceFailureDialogTemplate -) { - - return { - name: "platform/persistence/queue", - definition: { - "extensions": { - "components": [ - { - "type": "decorator", - "provides": "capabilityService", - "implementation": QueuingPersistenceCapabilityDecorator, - "depends": [ - "persistenceQueue" - ] - } - ], - "services": [ - { - "key": "persistenceQueue", - "implementation": PersistenceQueue, - "depends": [ - "$q", - "$timeout", - "dialogService", - "PERSISTENCE_QUEUE_DELAY" - ] - } - ], - "constants": [ - { - "key": "PERSISTENCE_QUEUE_DELAY", - "value": 5 - } - ], - "templates": [ - { - "key": "persistence-failure-dialog", - "template": persistenceFailureDialogTemplate - } - ], - "controllers": [ - { - "key": "PersistenceFailureController", - "implementation": PersistenceFailureController - } - ] - } - } - }; -}); diff --git a/platform/persistence/queue/res/templates/persistence-failure-dialog.html b/platform/persistence/queue/res/templates/persistence-failure-dialog.html deleted file mode 100644 index d477b31f9e..0000000000 --- a/platform/persistence/queue/res/templates/persistence-failure-dialog.html +++ /dev/null @@ -1,52 +0,0 @@ - - - -
      - External changes have been made to the following objects: -
        -
      • - - - was modified at - {{controller.formatTimestamp(failure.error.model.modified)}} - by - {{controller.formatUsername(failure.error.model.modifier)}} -
      • -
      - You may overwrite these objects, or discard your changes to keep - the updates that were made externally. -
      - -
      - Changes to these objects could not be saved for unknown reasons: -
        -
      • - - -
      • -
      -
      - -
      diff --git a/platform/persistence/queue/src/PersistenceFailureController.js b/platform/persistence/queue/src/PersistenceFailureController.js deleted file mode 100644 index b4b33d1cc1..0000000000 --- a/platform/persistence/queue/src/PersistenceFailureController.js +++ /dev/null @@ -1,55 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['moment', './PersistenceFailureConstants'], - function (moment, Constants) { - - /** - * Controller to support the template to be shown in the - * dialog shown for persistence failures. - * @constructor - * @memberof platform/persistence/queue - */ - function PersistenceFailureController() { - } - - /** - * Format a timestamp for display in the dialog. - * @memberof platform/persistence/queue.PersistenceFailureController# - */ - PersistenceFailureController.prototype.formatTimestamp = function (timestamp) { - return moment.utc(timestamp) - .format(Constants.TIMESTAMP_FORMAT); - }; - - /** - * Format a user name for display in the dialog. - * @memberof platform/persistence/queue.PersistenceFailureController# - */ - PersistenceFailureController.prototype.formatUsername = function (username) { - return username || Constants.UNKNOWN_USER; - }; - - return PersistenceFailureController; - } -); diff --git a/platform/persistence/queue/src/PersistenceFailureDialog.js b/platform/persistence/queue/src/PersistenceFailureDialog.js deleted file mode 100644 index 9e2d187c30..0000000000 --- a/platform/persistence/queue/src/PersistenceFailureDialog.js +++ /dev/null @@ -1,78 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['./PersistenceFailureConstants'], - function (PersistenceFailureConstants) { - - var OVERWRITE_CANCEL_OPTIONS = [ - { - name: "Overwrite", - key: PersistenceFailureConstants.OVERWRITE_KEY - }, - { - name: "Discard", - key: "cancel" - } - ], - OK_OPTIONS = [{ - name: "OK", - key: "ok" - }]; - - /** - * Populates a `dialogModel` to pass to `dialogService.getUserChoise` - * in order to choose between Overwrite and Cancel. - * @constructor - * @memberof platform/persistence/queue - */ - function PersistenceFailureDialog(failures) { - var revisionErrors = [], - otherErrors = []; - - // Place this failure into an appropriate group - function categorizeFailure(failure) { - // Check if the error is due to object revision - var isRevisionError = ((failure || {}).error || {}).key - === PersistenceFailureConstants.REVISION_ERROR_KEY; - // Push the failure into the appropriate group - (isRevisionError ? revisionErrors : otherErrors).push(failure); - } - - // Separate into revision errors, and other errors - failures.forEach(categorizeFailure); - - return { - title: "Save Error", - template: "persistence-failure-dialog", - model: { - revised: revisionErrors, - unrecoverable: otherErrors - }, - options: revisionErrors.length > 0 - ? OVERWRITE_CANCEL_OPTIONS : OK_OPTIONS - }; - } - - return PersistenceFailureDialog; - } -); diff --git a/platform/persistence/queue/src/PersistenceFailureHandler.js b/platform/persistence/queue/src/PersistenceFailureHandler.js deleted file mode 100644 index b7a9543d49..0000000000 --- a/platform/persistence/queue/src/PersistenceFailureHandler.js +++ /dev/null @@ -1,69 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ['./PersistenceFailureDialog', './PersistenceFailureConstants'], - function (PersistenceFailureDialog, PersistenceFailureConstants) { - - /** - * Handle failures to persist domain object models. - * @param $q Angular's `$q` - * @param {DialogService} dialogService the dialog service - * @constructor - * @memberof platform/persistence/queue - */ - function PersistenceFailureHandler($q, dialogService) { - this.$q = $q; - this.dialogService = dialogService; - } - - /** - * Discard failures - * @param {Array} failures persistence failures, as prepared - * by PersistenceQueueHandler - * @memberof platform/persistence/queue.PersistenceFailureHandler# - */ - PersistenceFailureHandler.prototype.handle = function handleFailures(failures) { - - var dialogModel = new PersistenceFailureDialog(failures), - revisionErrors = dialogModel.model.revised, - $q = this.$q; - - // Discard changes for a failed refresh - function discard(failure) { - var persistence = - failure.domainObject.getCapability('persistence'); - - return persistence.refresh(); - } - - // Discard changes associated with a failed save - function discardAll(failuresToDiscard) { - return $q.all(failuresToDiscard.map(discard)); - } - - return discardAll(revisionErrors); - }; - - return PersistenceFailureHandler; - } -); diff --git a/platform/persistence/queue/src/PersistenceQueue.js b/platform/persistence/queue/src/PersistenceQueue.js deleted file mode 100644 index d3b5fea85b..0000000000 --- a/platform/persistence/queue/src/PersistenceQueue.js +++ /dev/null @@ -1,76 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [ - './PersistenceQueueImpl', - './PersistenceQueueHandler', - './PersistenceFailureHandler' - ], - function ( - PersistenceQueueImpl, - PersistenceQueueHandler, - PersistenceFailureHandler - ) { - - /** - * The PersistenceQueue is used by the QueuingPersistenceCapability - * to aggregate calls for object persistence. These are then issued - * in a group, such that if some or all are rejected, this result can - * be shown to the user (again, in a group.) - * - * This constructor is exposed as a service, but handles only the - * wiring of injected dependencies; behavior is implemented in the - * various component parts. - * - * @param $timeout Angular's $timeout - * @param {PersistenceQueueHandler} handler handles actual - * persistence when the queue is flushed - * @param {number} [DELAY] optional; delay in milliseconds between - * attempts to flush the queue - * @constructor - * @memberof platform/persistence/queue - */ - function PersistenceQueue( - $q, - $timeout, - dialogService, - PERSISTENCE_QUEUE_DELAY - ) { - // Wire up injected dependencies - return new PersistenceQueueImpl( - $q, - $timeout, - new PersistenceQueueHandler( - $q, - new PersistenceFailureHandler( - $q, - dialogService - ) - ), - PERSISTENCE_QUEUE_DELAY - ); - } - - return PersistenceQueue; - } -); diff --git a/platform/persistence/queue/src/PersistenceQueueHandler.js b/platform/persistence/queue/src/PersistenceQueueHandler.js deleted file mode 100644 index dd7caa6e20..0000000000 --- a/platform/persistence/queue/src/PersistenceQueueHandler.js +++ /dev/null @@ -1,115 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * Handles actual persistence invocations for queued persistence - * attempts, in a group. Handling in a group in this manner - * also allows failure to be handled in a group (e.g. in a single - * summary dialog.) - * @param $q Angular's $q, for promises - * @param {PersistenceFailureHandler} handler to invoke in the event - * that a persistence attempt fails. - * @constructor - * @memberof platform/persistence/queue - */ - function PersistenceQueueHandler($q, failureHandler) { - this.$q = $q; - this.failureHandler = failureHandler; - } - - /** - * Invoke the persist method on the provided persistence - * capabilities. - * @param {Object.} persistences - * capabilities to invoke, in id->capability pairs. - * @param {Object.} domainObjects - * associated domain objects, in id->object pairs. - * @param {PersistenceQueue} queue the persistence queue, - * to requeue as necessary - * @memberof platform/persistence/queue.PersistenceQueueHandler# - */ - PersistenceQueueHandler.prototype.persist = function (persistences, domainObjects, queue) { - var ids = Object.keys(persistences), - $q = this.$q, - failureHandler = this.failureHandler; - - // Handle a group of persistence invocations - function persistGroup(groupIds, persistenceCaps, domainObjs, pQueue) { - var failures = []; - - // Try to persist a specific domain object - function tryPersist(id) { - // Look up its persistence capability from the provided - // id->persistence object. - var persistence = persistenceCaps[id], - domainObject = domainObjs[id]; - - // Put a domain object back in the queue - // (e.g. after Overwrite) - function requeue() { - return pQueue.put(domainObject, persistence); - } - - // Handle success - function succeed(value) { - return value; - } - - // Handle failure (build up a list of failures) - function fail(error) { - failures.push({ - id: id, - persistence: persistence, - domainObject: domainObject, - requeue: requeue, - error: error - }); - - return false; - } - - // Invoke the actual persistence capability, then - // note success or failures - return persistence.persist().then(succeed, fail); - } - - // Handle any failures from the full operation - function handleFailure(value) { - return failures.length > 0 - ? failureHandler.handle(failures) - : value; - } - - // Try to persist everything, then handle any failures - return $q.all(groupIds.map(tryPersist)).then(handleFailure); - } - - return persistGroup(ids, persistences, domainObjects, queue); - }; - - return PersistenceQueueHandler; - } -); diff --git a/platform/persistence/queue/src/PersistenceQueueImpl.js b/platform/persistence/queue/src/PersistenceQueueImpl.js deleted file mode 100644 index 3c2a040512..0000000000 --- a/platform/persistence/queue/src/PersistenceQueueImpl.js +++ /dev/null @@ -1,149 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * The PersistenceQueue is used by the QueuingPersistenceCapability - * to aggregrate calls for object persistence. These are then issued - * in a group, such that if some or all are rejected, this result can - * be shown to the user (again, in a group.) - * - * This implementation is separate out from PersistenceQueue, which - * handles the wiring of injected dependencies into an instance of - * this class. - * - * @param $timeout Angular's $timeout - * @param {PersistenceQueueHandler} handler handles actual - * persistence when the queue is flushed - * @param {number} [DELAY] optional; delay in milliseconds between - * attempts to flush the queue - * @constructor - * @memberof platform/persistence/queue - */ - function PersistenceQueueImpl($q, $timeout, handler, delay) { - - this.persistences = {}; - this.objects = {}; - this.lastObservedSize = 0; - this.activeDefer = $q.defer(); - - // If no delay is provided, use a default - this.delay = delay || 0; - this.handler = handler; - this.$timeout = $timeout; - this.$q = $q; - } - - // Schedule a flushing of the queue (that is, plan to flush - // all objects in the queue) - PersistenceQueueImpl.prototype.scheduleFlush = function () { - var self = this, - $timeout = this.$timeout, - $q = this.$q, - handler = this.handler; - - // Check if the queue's size has stopped increasing) - function quiescent() { - return Object.keys(self.persistences).length - === self.lastObservedSize; - } - - // Persist all queued objects - function flush() { - // Get a local reference to the active promise; - // this will be replaced with a promise for the next round - // of persistence calls, so we want to make sure we clear - // the correct one when this flush completes. - var flushingDefer = self.activeDefer; - - // Clear the active promise for a queue flush - function clearFlushPromise(value) { - self.flushPromise = undefined; - flushingDefer.resolve(value); - - return value; - } - - // Persist all queued objects - self.flushPromise = handler.persist( - self.persistences, - self.objects, - self - ).then(clearFlushPromise, clearFlushPromise); - - // Reset queue, etc. - self.persistences = {}; - self.objects = {}; - self.lastObservedSize = 0; - self.pendingTimeout = undefined; - self.activeDefer = $q.defer(); - } - - function maybeFlush() { - // Timeout fired, so clear it - self.pendingTimeout = undefined; - // Only flush when we've stopped receiving updates - if (quiescent()) { - flush(); - } else { - self.scheduleFlush(); - } - - // Update lastObservedSize to detect quiescence - self.lastObservedSize = Object.keys(self.persistences).length; - } - - // If we are already flushing the queue... - if (self.flushPromise) { - // Wait until that's over before considering a flush - self.flushPromise.then(maybeFlush); - } else { - // Otherwise, schedule a flush on a timeout (to give - // a window for other updates to get aggregated) - self.pendingTimeout = self.pendingTimeout - || $timeout(maybeFlush, self.delay, false); - } - - return self.activeDefer.promise; - }; - - /** - * Queue persistence of a domain object. - * @param {DomainObject} domainObject the domain object - * @param {PersistenceCapability} persistence the object's - * undecorated persistence capability - * @returns {Promise} a promise which will resolve upon persistence - */ - PersistenceQueueImpl.prototype.put = function (domainObject, persistence) { - var id = domainObject.getId(); - this.persistences[id] = persistence; - this.objects[id] = domainObject; - - return this.scheduleFlush(); - }; - - return PersistenceQueueImpl; - } -); diff --git a/platform/persistence/queue/src/QueuingPersistenceCapability.js b/platform/persistence/queue/src/QueuingPersistenceCapability.js deleted file mode 100644 index 30beb0ce04..0000000000 --- a/platform/persistence/queue/src/QueuingPersistenceCapability.js +++ /dev/null @@ -1,51 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * The QueuingPersistenceCapability places `persist` calls in a queue - * to be handled in batches. - * @param {PersistenceQueue} queue of persistence calls - * @param {PersistenceCapability} persistence the wrapped persistence - * capability - * @param {DomainObject} domainObject the domain object which exposes - * the capability - * @constructor - * @memberof platform/persistence/queue - */ - function QueuingPersistenceCapability(queue, persistence, domainObject) { - var queuingPersistence = Object.create(persistence); - - // Override persist calls to queue them instead - queuingPersistence.persist = function () { - return queue.put(domainObject, persistence); - }; - - return queuingPersistence; - } - - return QueuingPersistenceCapability; - } -); diff --git a/platform/persistence/queue/src/QueuingPersistenceCapabilityDecorator.js b/platform/persistence/queue/src/QueuingPersistenceCapabilityDecorator.js deleted file mode 100644 index 59a32311e9..0000000000 --- a/platform/persistence/queue/src/QueuingPersistenceCapabilityDecorator.js +++ /dev/null @@ -1,86 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * This bundle decorates the persistence service to handle persistence - * in batches, and to provide notification of persistence errors in batches - * as well. - * @namespace platform/persistence/queue - */ -define( - ['./QueuingPersistenceCapability'], - function (QueuingPersistenceCapability) { - - /** - * Capability decorator. Adds queueing support to persistence - * capabilities for domain objects, such that persistence attempts - * will be handled in batches (allowing failure notification to - * also be presented in batches.) - * - * @memberof platform/persistence/queue - * @constructor - * @implements {CapabilityService} - * @param {platform/persistence/queue.PersistenceQueue} persistenceQueue - * @param {CapabilityService} the decorated capability service - */ - function QueuingPersistenceCapabilityDecorator( - persistenceQueue, - capabilityService - ) { - this.persistenceQueue = persistenceQueue; - this.capabilityService = capabilityService; - } - - QueuingPersistenceCapabilityDecorator.prototype.getCapabilities = function (model, id) { - var capabilityService = this.capabilityService, - persistenceQueue = this.persistenceQueue; - - function decoratePersistence(capabilities) { - var originalPersistence = capabilities.persistence; - if (originalPersistence) { - capabilities.persistence = function (domainObject) { - // Get/instantiate the original - var original = - (typeof originalPersistence === 'function') - ? originalPersistence(domainObject) - : originalPersistence; - - // Provide a decorated version - return new QueuingPersistenceCapability( - persistenceQueue, - original, - domainObject - ); - }; - } - - return capabilities; - } - - return decoratePersistence( - capabilityService.getCapabilities(model, id) - ); - }; - - return QueuingPersistenceCapabilityDecorator; - } -); diff --git a/platform/persistence/queue/test/PersistenceFailureControllerSpec.js b/platform/persistence/queue/test/PersistenceFailureControllerSpec.js deleted file mode 100644 index 9e1e84df33..0000000000 --- a/platform/persistence/queue/test/PersistenceFailureControllerSpec.js +++ /dev/null @@ -1,45 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/PersistenceFailureController"], - function (PersistenceFailureController) { - - describe("The persistence failure controller", function () { - var controller; - - beforeEach(function () { - controller = new PersistenceFailureController(); - }); - - it("converts timestamps to human-readable dates", function () { - expect(controller.formatTimestamp(402514331000)) - .toEqual("1982-10-03 17:32:11Z"); - }); - - it("provides default user names", function () { - expect(controller.formatUsername(undefined)) - .toEqual(jasmine.any(String)); - }); - }); - } -); diff --git a/platform/persistence/queue/test/PersistenceFailureDialogSpec.js b/platform/persistence/queue/test/PersistenceFailureDialogSpec.js deleted file mode 100644 index 8cd5f48c9c..0000000000 --- a/platform/persistence/queue/test/PersistenceFailureDialogSpec.js +++ /dev/null @@ -1,71 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/PersistenceFailureDialog", "../src/PersistenceFailureConstants"], - function (PersistenceFailureDialog, Constants) { - - describe("The persistence failure dialog", function () { - var testFailures, - dialog; - - beforeEach(function () { - testFailures = [ - { - error: { key: Constants.REVISION_ERROR_KEY }, - someKey: "abc" - }, - { - error: { key: "..." }, - someKey: "def" - }, - { - error: { key: Constants.REVISION_ERROR_KEY }, - someKey: "ghi" - }, - { - error: { key: Constants.REVISION_ERROR_KEY }, - someKey: "jkl" - }, - { - error: { key: "..." }, - someKey: "mno" - } - ]; - dialog = new PersistenceFailureDialog(testFailures); - }); - - it("categorizes failures", function () { - expect(dialog.model.revised).toEqual([ - testFailures[0], testFailures[2], testFailures[3] - ]); - expect(dialog.model.unrecoverable).toEqual([ - testFailures[1], testFailures[4] - ]); - }); - - it("provides an overwrite option", function () { - expect(dialog.options[0].key).toEqual(Constants.OVERWRITE_KEY); - }); - }); - } -); diff --git a/platform/persistence/queue/test/PersistenceFailureHandlerSpec.js b/platform/persistence/queue/test/PersistenceFailureHandlerSpec.js deleted file mode 100644 index 2bcb9bf646..0000000000 --- a/platform/persistence/queue/test/PersistenceFailureHandlerSpec.js +++ /dev/null @@ -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. - *****************************************************************************/ - -define( - ["../src/PersistenceFailureHandler", "../src/PersistenceFailureConstants"], - function (PersistenceFailureHandler, Constants) { - - describe("The persistence failure handler", function () { - var mockQ, - mockDialogService, - mockFailures, - mockPromise, - handler; - - function makeMockFailure(id, index) { - var mockFailure = jasmine.createSpyObj( - 'failure-' + id, - ['requeue'] - ), - mockPersistence = jasmine.createSpyObj( - 'persistence-' + id, - ['refresh', 'persist'] - ); - mockFailure.domainObject = jasmine.createSpyObj( - 'domainObject', - ['getCapability', 'useCapability', 'getModel'] - ); - mockFailure.domainObject.getCapability.and.callFake(function (c) { - return (c === 'persistence') && mockPersistence; - }); - mockFailure.domainObject.getModel.and.returnValue({ - id: id, - modified: index - }); - mockFailure.persistence = mockPersistence; - mockFailure.id = id; - mockFailure.error = { key: Constants.REVISION_ERROR_KEY }; - - return mockFailure; - } - - beforeEach(function () { - mockQ = jasmine.createSpyObj('$q', ['all', 'when']); - mockDialogService = jasmine.createSpyObj('dialogService', ['getUserChoice']); - mockFailures = ['a', 'b', 'c'].map(makeMockFailure); - mockPromise = jasmine.createSpyObj('promise', ['then']); - mockDialogService.getUserChoice.and.returnValue(mockPromise); - mockQ.all.and.returnValue(mockPromise); - mockPromise.then.and.returnValue(mockPromise); - handler = new PersistenceFailureHandler(mockQ, mockDialogService); - }); - - it("discards on handle", function () { - handler.handle(mockFailures); - mockFailures.forEach(function (mockFailure) { - expect(mockFailure.persistence.refresh).toHaveBeenCalled(); - expect(mockFailure.requeue).not.toHaveBeenCalled(); - expect(mockFailure.domainObject.useCapability).not.toHaveBeenCalled(); - }); - }); - }); - } -); diff --git a/platform/persistence/queue/test/PersistenceQueueHandlerSpec.js b/platform/persistence/queue/test/PersistenceQueueHandlerSpec.js deleted file mode 100644 index b78d1ef6a9..0000000000 --- a/platform/persistence/queue/test/PersistenceQueueHandlerSpec.js +++ /dev/null @@ -1,136 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/PersistenceQueueHandler"], - function (PersistenceQueueHandler) { - - var TEST_ERROR = { someKey: "some value" }; - - describe("The persistence queue handler", function () { - var mockQ, - mockFailureHandler, - mockPersistences, - mockDomainObjects, - mockQueue, - mockRejection, - handler; - - function asPromise(value) { - return (value || {}).then ? value : { - then: function (callback) { - return asPromise(callback(value)); - } - }; - } - - function makeMockPersistence(id) { - var mockPersistence = jasmine.createSpyObj( - 'persistence-' + id, - ['persist', 'refresh'] - ); - mockPersistence.persist.and.returnValue(asPromise(true)); - - return mockPersistence; - } - - function makeMockDomainObject(id) { - var mockDomainObject = jasmine.createSpyObj( - 'domainObject-' + id, - ['getId'] - ); - mockDomainObject.getId.and.returnValue(id); - - return mockDomainObject; - } - - beforeEach(function () { - mockQ = jasmine.createSpyObj('$q', ['all']); - mockFailureHandler = jasmine.createSpyObj('handler', ['handle']); - mockQueue = jasmine.createSpyObj('queue', ['put']); - mockPersistences = {}; - mockDomainObjects = {}; - ['a', 'b', 'c'].forEach(function (id) { - mockPersistences[id] = makeMockPersistence(id); - mockDomainObjects[id] = makeMockDomainObject(id); - }); - mockRejection = jasmine.createSpyObj('rejection', ['then']); - mockQ.all.and.returnValue(asPromise([])); - mockRejection.then.and.callFake(function (callback, fallback) { - return asPromise(fallback({ someKey: "some value" })); - }); - handler = new PersistenceQueueHandler(mockQ, mockFailureHandler); - }); - - it("invokes persistence on all members in the group", function () { - handler.persist(mockPersistences, mockDomainObjects, mockQueue); - expect(mockPersistences.a.persist).toHaveBeenCalled(); - expect(mockPersistences.b.persist).toHaveBeenCalled(); - expect(mockPersistences.c.persist).toHaveBeenCalled(); - // No failures in this group - expect(mockFailureHandler.handle).not.toHaveBeenCalled(); - }); - - it("handles failures that occur", function () { - mockPersistences.b.persist.and.returnValue(mockRejection); - mockPersistences.c.persist.and.returnValue(mockRejection); - handler.persist(mockPersistences, mockDomainObjects, mockQueue); - expect(mockFailureHandler.handle).toHaveBeenCalledWith([ - { - id: 'b', - persistence: mockPersistences.b, - domainObject: mockDomainObjects.b, - requeue: jasmine.any(Function), - error: TEST_ERROR - }, - { - id: 'c', - persistence: mockPersistences.c, - domainObject: mockDomainObjects.c, - requeue: jasmine.any(Function), - error: TEST_ERROR - } - ]); - }); - - it("provides a requeue method for failures", function () { - // This method is needed by PersistenceFailureHandler - // to allow requeuing of objects for persistence when - // Overwrite is chosen. - mockPersistences.b.persist.and.returnValue(mockRejection); - handler.persist(mockPersistences, mockDomainObjects, mockQueue); - - // Verify precondition - expect(mockQueue.put).not.toHaveBeenCalled(); - - // Invoke requeue - mockFailureHandler.handle.calls.mostRecent().args[0][0].requeue(); - - // Should have returned the object to the queue - expect(mockQueue.put).toHaveBeenCalledWith( - mockDomainObjects.b, - mockPersistences.b - ); - }); - }); - } -); diff --git a/platform/persistence/queue/test/PersistenceQueueImplSpec.js b/platform/persistence/queue/test/PersistenceQueueImplSpec.js deleted file mode 100644 index 42f55beab0..0000000000 --- a/platform/persistence/queue/test/PersistenceQueueImplSpec.js +++ /dev/null @@ -1,153 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/PersistenceQueueImpl"], - function (PersistenceQueueImpl) { - - var TEST_DELAY = 42; - - describe("The implemented persistence queue", function () { - var mockQ, - mockTimeout, - mockHandler, - mockDeferred, - mockPromise, - queue; - - function makeMockDomainObject(id) { - var mockDomainObject = jasmine.createSpyObj( - 'domainObject-' + id, - ['getId'] - ); - mockDomainObject.getId.and.returnValue(id); - - return mockDomainObject; - } - - function makeMockPersistence(id) { - var mockPersistence = jasmine.createSpyObj( - 'persistence-' + id, - ['persist'] - ); - - return mockPersistence; - } - - beforeEach(function () { - mockQ = jasmine.createSpyObj('$q', ['when', 'defer']); - mockTimeout = jasmine.createSpy('$timeout'); - mockHandler = jasmine.createSpyObj('handler', ['persist']); - mockDeferred = jasmine.createSpyObj('deferred', ['resolve']); - mockDeferred.promise = jasmine.createSpyObj('promise', ['then']); - mockPromise = jasmine.createSpyObj('promise', ['then']); - mockQ.defer.and.returnValue(mockDeferred); - mockTimeout.and.returnValue({}); - mockHandler.persist.and.returnValue(mockPromise); - mockPromise.then.and.returnValue(mockPromise); - queue = new PersistenceQueueImpl( - mockQ, - mockTimeout, - mockHandler, - TEST_DELAY - ); - }); - - it("schedules a timeout to persist objects", function () { - expect(mockTimeout).not.toHaveBeenCalled(); - queue.put(makeMockDomainObject('a'), makeMockPersistence('a')); - expect(mockTimeout).toHaveBeenCalledWith( - jasmine.any(Function), - TEST_DELAY, - false - ); - }); - - it("does not schedule multiple timeouts for multiple objects", function () { - // Put three objects in without triggering the timeout; - // shouldn't schedule multiple timeouts - queue.put(makeMockDomainObject('a'), makeMockPersistence('a')); - queue.put(makeMockDomainObject('b'), makeMockPersistence('b')); - queue.put(makeMockDomainObject('c'), makeMockPersistence('c')); - expect(mockTimeout.calls.count()).toEqual(1); - }); - - it("returns a promise", function () { - expect(queue.put(makeMockDomainObject('a'), makeMockPersistence('a'))) - .toEqual(mockDeferred.promise); - }); - - it("waits for quiescence to proceed", function () { - // Keep adding objects to the queue between timeouts. - // Should keep scheduling timeouts instead of resolving. - queue.put(makeMockDomainObject('a'), makeMockPersistence('a')); - expect(mockTimeout.calls.count()).toEqual(1); - mockTimeout.calls.mostRecent().args[0](); - queue.put(makeMockDomainObject('b'), makeMockPersistence('b')); - expect(mockTimeout.calls.count()).toEqual(2); - mockTimeout.calls.mostRecent().args[0](); - queue.put(makeMockDomainObject('c'), makeMockPersistence('c')); - expect(mockTimeout.calls.count()).toEqual(3); - mockTimeout.calls.mostRecent().args[0](); - expect(mockHandler.persist).not.toHaveBeenCalled(); - }); - - it("persists upon quiescence", function () { - // Add objects to the queue, but fire two timeouts afterward - queue.put(makeMockDomainObject('a'), makeMockPersistence('a')); - queue.put(makeMockDomainObject('b'), makeMockPersistence('b')); - queue.put(makeMockDomainObject('c'), makeMockPersistence('c')); - mockTimeout.calls.mostRecent().args[0](); - mockTimeout.calls.mostRecent().args[0](); - expect(mockHandler.persist).toHaveBeenCalled(); - }); - - it("waits on an active flush, while flushing", function () { - // Persist some objects - queue.put(makeMockDomainObject('a'), makeMockPersistence('a')); - queue.put(makeMockDomainObject('b'), makeMockPersistence('b')); - mockTimeout.calls.mostRecent().args[0](); - mockTimeout.calls.mostRecent().args[0](); - expect(mockTimeout.calls.count()).toEqual(2); - // Adding a new object should not trigger a new timeout, - // because we haven't completed the previous flush - queue.put(makeMockDomainObject('c'), makeMockPersistence('c')); - expect(mockTimeout.calls.count()).toEqual(2); - }); - - it("clears the active flush after it has completed", function () { - // Persist some objects - queue.put(makeMockDomainObject('a'), makeMockPersistence('a')); - queue.put(makeMockDomainObject('b'), makeMockPersistence('b')); - mockTimeout.calls.mostRecent().args[0](); - mockTimeout.calls.mostRecent().args[0](); - expect(mockTimeout.calls.count()).toEqual(2); - // Resolve the promise from handler.persist - mockPromise.then.calls.all()[0].args[0](true); - // Adding a new object should now trigger a new timeout, - // because we have completed the previous flush - queue.put(makeMockDomainObject('c'), makeMockPersistence('c')); - expect(mockTimeout.calls.count()).toEqual(3); - }); - }); - } -); diff --git a/platform/persistence/queue/test/PersistenceQueueSpec.js b/platform/persistence/queue/test/PersistenceQueueSpec.js deleted file mode 100644 index d8171c7b02..0000000000 --- a/platform/persistence/queue/test/PersistenceQueueSpec.js +++ /dev/null @@ -1,53 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/PersistenceQueue"], - function (PersistenceQueue) { - - describe("The persistence queue", function () { - var mockQ, - mockTimeout, - mockDialogService, - queue; - - beforeEach(function () { - mockQ = jasmine.createSpyObj("$q", ['defer']); - mockTimeout = jasmine.createSpy("$timeout"); - mockDialogService = jasmine.createSpyObj( - 'dialogService', - ['getUserChoice'] - ); - queue = new PersistenceQueue(mockQ, mockTimeout, mockDialogService); - }); - - // PersistenceQueue is just responsible for handling injected - // dependencies and wiring the PersistenceQueueImpl and its - // handlers. Functionality is tested there, so our test here is - // minimal (get back expected interface, no exceptions) - it("provides a queue with a put method", function () { - expect(queue.put).toEqual(jasmine.any(Function)); - }); - - }); - } -); diff --git a/platform/persistence/queue/test/QueuingPersistenceCapabilityDecoratorSpec.js b/platform/persistence/queue/test/QueuingPersistenceCapabilityDecoratorSpec.js deleted file mode 100644 index 24dd750250..0000000000 --- a/platform/persistence/queue/test/QueuingPersistenceCapabilityDecoratorSpec.js +++ /dev/null @@ -1,83 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/QueuingPersistenceCapabilityDecorator"], - function (QueuingPersistenceCapabilityDecorator) { - - describe("A queuing persistence capability decorator", function () { - var mockQueue, - mockCapabilityService, - mockPersistenceConstructor, - mockPersistence, - mockDomainObject, - testModel, - testId, - decorator; - - beforeEach(function () { - mockQueue = jasmine.createSpyObj('queue', ['put']); - mockCapabilityService = jasmine.createSpyObj( - 'capabilityService', - ['getCapabilities'] - ); - testModel = { someKey: "some value" }; - testId = 'someId'; - mockPersistence = jasmine.createSpyObj( - 'persistence', - ['persist', 'refresh'] - ); - mockPersistenceConstructor = jasmine.createSpy(); - mockDomainObject = jasmine.createSpyObj( - 'domainObject', - ['getId'] - ); - - mockCapabilityService.getCapabilities.and.returnValue({ - persistence: mockPersistenceConstructor - }); - mockPersistenceConstructor.and.returnValue(mockPersistence); - - decorator = new QueuingPersistenceCapabilityDecorator( - mockQueue, - mockCapabilityService - ); - }); - - // Here, we verify that the decorator wraps the calls it is expected - // to wrap; remaining responsibility belongs to - // QueuingPersistenceCapability itself, which has its own tests. - - it("delegates to its wrapped service", function () { - decorator.getCapabilities(testModel, testId); - expect(mockCapabilityService.getCapabilities) - .toHaveBeenCalledWith(testModel, testId); - }); - - it("wraps its persistence capability's constructor", function () { - decorator.getCapabilities(testModel).persistence(mockDomainObject); - expect(mockPersistenceConstructor).toHaveBeenCalledWith(mockDomainObject); - }); - - }); - } -); diff --git a/platform/persistence/queue/test/QueuingPersistenceCapabilitySpec.js b/platform/persistence/queue/test/QueuingPersistenceCapabilitySpec.js deleted file mode 100644 index ddbba8b669..0000000000 --- a/platform/persistence/queue/test/QueuingPersistenceCapabilitySpec.js +++ /dev/null @@ -1,64 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/QueuingPersistenceCapability"], - function (QueuingPersistenceCapability) { - - describe("A queuing persistence capability", function () { - var mockQueue, - mockPersistence, - mockDomainObject, - persistence; - - beforeEach(function () { - mockQueue = jasmine.createSpyObj('queue', ['put']); - mockPersistence = jasmine.createSpyObj( - 'persistence', - ['persist', 'refresh'] - ); - mockDomainObject = {}; - persistence = new QueuingPersistenceCapability( - mockQueue, - mockPersistence, - mockDomainObject - ); - }); - - it("puts a request for persistence into the queue on persist", function () { - // Verify precondition - expect(mockQueue.put).not.toHaveBeenCalled(); - // Invoke persistence - persistence.persist(); - // Should have queued - expect(mockQueue.put).toHaveBeenCalledWith( - mockDomainObject, - mockPersistence - ); - }); - - it("exposes other methods from the wrapped persistence capability", function () { - expect(persistence.refresh).toBe(mockPersistence.refresh); - }); - }); - } -); diff --git a/platform/policy/README.md b/platform/policy/README.md deleted file mode 100644 index 571782c174..0000000000 --- a/platform/policy/README.md +++ /dev/null @@ -1,93 +0,0 @@ -# Overview - -This bundle provides support for policy in Open MCT. Policy can be -used to limit the applicability of certain actions, or more broadly, -to provide an extension point for arbitrary decisions. - -# Services - -This bundle introduces the `policyService`, which may be consulted for -various decisions which are intended to be open for extension. - -The `policyService` has a single method, `allow`, which takes three -arguments and returns a boolean value (true if policy says this decision -should be allowed, false if not): - -* `category`: A string identifying which kind of decision is being made. - Typically, this will be a non-plural form of an extension type that is - being filtered down; for instance, to check whether or not a given - action should be returned by an `actionService`, one would use the - `action` category of extension. -* `candidate`: An object representing the thing which shall or shall not - be allowed. Usually, this will be an instance of an extension of the - category defined above. - * This does need to be the case; additional - policies which are not specific to any extension may also be defined - and consulted using unique `category` identifiers. In this case, the - type of the object delivered for the candidate may be unique to the - policy type. -* `context`: An object representing the context in which the decision is - occurring. Its contents are specific to each policy category. -* `callback`: Optional; a function to call if the policy decision is - rejected. This function will be called with the `message` string - (which may be undefined) of whichever individual policy caused the - operation to fail. - -_Design rationale_: Returning a boolean here limits the amount of -information that can be conveyed by a policy decision, but has the -benefit of simplicity. In MCT on the desktop, the policy service -returned a more complex object with both a boolean status and a string -message; the string message was used rarely (by only around 15% of -policy user code) and as such is made optional in the call itself here. - -_Design rationale_: Returning a boolean instead of a promise here implies -that policy decisions must occur synchronously. This limits the logic -which can be involved in a policy decision, but broadens its applicability; -policy is meant to be used by a variety of other services to separate out -a certain category of business logic, and a synchronous response means -that this capability may be utilized by both synchronous and asynchronous -services. Additionally, policies will often be used in loops (e.g. to filter -down a set of applicable actions) where latency will have the result of -harming the user experience (e.g. the user right-clicks and gets stuck -waiting for a bunch of policy decisions to complete before a menu showing -available actions can appear.) - -The `policyService` is a composite service; it may be modified by adding -decorators, aggregators, etc. - -## Service Components - -The policy service is most often used by decorators for other composite -services. For instance, this bundle contains a decorator for `actionService` -which filters down the applicable actions exposed by that service based -on policy. - -# Policy Categories - -This bundle introduces `action` as a policy category. Policies of this -category shall take action instances as their candidate argument, and -action contexts as their context argument. - -# Extensions - -This bundle introduces the `policies` category of extension. An extension -of this category should have both an implementation, as well as the following -metadata: - -* `category`: A string identifying which kind of policy decision this - effects. -* `message`: Optional; a human-readable string describing the policy - decision when it fails. - -An extension of this category must also have an implementation which -takes no arguments to its constructor and provides a single method, -`allow`, which takes two arguments, `candidate` and `context` (see -descriptions above under documentation for `actionService`) and returns -a boolean indicating whether or not it allows the policy decision. - -Policy decisions require consensus among all policies; that is, if a -single policy returns false, then the policy decision as a whole returns -false. As a consequence, policies should be written in a permissive -manner; that is, they should be designed to prohibit behavior under a -specific set of conditions (by returning false), and allow any behavior -which does not match those conditions (by returning true.) diff --git a/platform/policy/bundle.js b/platform/policy/bundle.js deleted file mode 100644 index 0f9ae86d0d..0000000000 --- a/platform/policy/bundle.js +++ /dev/null @@ -1,69 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/PolicyActionDecorator", - "./src/PolicyViewDecorator", - "./src/PolicyProvider" -], function ( - PolicyActionDecorator, - PolicyViewDecorator, - PolicyProvider -) { - - return { - name: "platform/policy", - definition: { - "name": "Policy Service", - "description": "Provides support for extension-driven decisions.", - "sources": "src", - "extensions": { - "components": [ - { - "type": "decorator", - "provides": "actionService", - "implementation": PolicyActionDecorator, - "depends": [ - "policyService" - ] - }, - { - "type": "decorator", - "provides": "viewService", - "implementation": PolicyViewDecorator, - "depends": [ - "policyService" - ] - }, - { - "type": "provider", - "provides": "policyService", - "implementation": PolicyProvider, - "depends": [ - "policies[]" - ] - } - ] - } - } - }; -}); diff --git a/platform/policy/src/PolicyActionDecorator.js b/platform/policy/src/PolicyActionDecorator.js deleted file mode 100644 index 6416d56e7d..0000000000 --- a/platform/policy/src/PolicyActionDecorator.js +++ /dev/null @@ -1,55 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * Filters out actions based on policy. - * @param {PolicyService} policyService the service which provides - * policy decisions - * @param {ActionService} actionService the service to decorate - * @constructor - * @memberof platform/policy - * @implements {ActionService} - */ - function PolicyActionDecorator(policyService, actionService) { - this.policyService = policyService; - this.actionService = actionService; - } - - PolicyActionDecorator.prototype.getActions = function (context) { - var policyService = this.policyService; - - // Check if an action is allowed by policy. - function allow(action) { - return policyService.allow('action', action, context); - } - - // Look up actions, filter out the disallowed ones. - return this.actionService.getActions(context).filter(allow); - }; - - return PolicyActionDecorator; - } -); diff --git a/platform/policy/src/PolicyProvider.js b/platform/policy/src/PolicyProvider.js deleted file mode 100644 index 179b6b42a8..0000000000 --- a/platform/policy/src/PolicyProvider.js +++ /dev/null @@ -1,143 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * This bundle implements the policy service. - * @namespace platform/policy - */ -define( - [], - function () { - - /** - * A policy is a participant in decision-making policies. Policies - * are divided into categories (identified symbolically by strings); - * within a given category, every given policy-driven decision will - * occur by consulting all available policies and requiring their - * collective consent (that is, every individual policy has the - * power to reject the decision entirely.) - * - * @interface Policy - * @template C, X - */ - - /** - * Check if this policy allows the described decision. The types - * of the arguments expected here vary depending on policy category. - * - * @method Policy#allow - * @template C, X - * @param {C} candidate the thing to allow or disallow - * @param {X} context the context in which the decision occurs - * @returns {boolean} false if disallowed; otherwise, true - */ - - /** - * The `policyService` handles decisions about what things - * are and are not allowed in certain contexts. - * @interface PolicyService - */ - - /** - * Check whether or not a certain decision is allowed by - * policy. - * @param {string} category a machine-readable identifier - * for the kind of decision being made - * @param candidate the object about which the decision is - * being made - * @param context the context in which the decision occurs - * @param {Function} [callback] callback to invoke with a - * string message describing the reason a decision - * was disallowed (if its disallowed) - * @returns {boolean} true if the decision is allowed, - * otherwise false. - * @method PolicyService#allow - */ - - /** - * Provides an implementation of `policyService` which consults - * various policy extensions to determine whether or not a specific - * decision should be allowed. - * @memberof platform/policy - * @constructor - * @implements {PolicyService} - * @param {Policy[]} policies the policies to enforce - */ - function PolicyProvider(policies) { - var policyMap = {}; - - // Instantiate a policy. Mostly just a constructor call, but - // we also track the message (which was provided as metadata - // along with the constructor) so that we can expose this later. - function instantiate(Policy) { - var policy = Object.create(new Policy()); - policy.message = Policy.message; - - return policy; - } - - // Add a specific policy to the map for later lookup, - // according to its category. Note that policy extensions are - // provided as constructors, so they are instantiated here. - function addToMap(Policy) { - var category = (Policy || {}).category; - if (category) { - // Create a new list for that category if needed... - policyMap[category] = policyMap[category] || []; - // ...and put an instance of this policy in that list. - policyMap[category].push(instantiate(Policy)); - } - } - - // Populate the map for subsequent lookup - policies.forEach(addToMap); - this.policyMap = policyMap; - } - - PolicyProvider.prototype.allow = function (category, candidate, context, callback) { - var policyList = this.policyMap[category] || [], - i; - - // Iterate through policies. We do this instead of map or - // forEach so that we can return immediately if a policy - // chooses to disallow this decision. - for (i = 0; i < policyList.length; i += 1) { - // Consult the policy... - if (!policyList[i].allow(candidate, context)) { - // ...it disallowed, so pass its message to - // the callback (if any) - if (callback) { - callback(policyList[i].message); - } - - // And return the failed result. - return false; - } - } - - // No policy disallowed this decision. - return true; - }; - - return PolicyProvider; - } -); diff --git a/platform/policy/src/PolicyViewDecorator.js b/platform/policy/src/PolicyViewDecorator.js deleted file mode 100644 index 7a9b114ff4..0000000000 --- a/platform/policy/src/PolicyViewDecorator.js +++ /dev/null @@ -1,55 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * Filters out views based on policy. - * @param {PolicyService} policyService the service which provides - * policy decisions - * @param {ViewService} viewService the service to decorate - * @constructor - * @memberof platform/policy - * @implements {ViewService} - */ - function PolicyViewDecorator(policyService, viewService) { - this.policyService = policyService; - this.viewService = viewService; - } - - PolicyViewDecorator.prototype.getViews = function (domainObject) { - var policyService = this.policyService; - - // Check if an action is allowed by policy. - function allow(view) { - return policyService.allow('view', view, domainObject); - } - - // Look up actions, filter out the disallowed ones. - return this.viewService.getViews(domainObject).filter(allow); - }; - - return PolicyViewDecorator; - } -); diff --git a/platform/policy/test/PolicyActionDecoratorSpec.js b/platform/policy/test/PolicyActionDecoratorSpec.js deleted file mode 100644 index 8ddc8b699a..0000000000 --- a/platform/policy/test/PolicyActionDecoratorSpec.js +++ /dev/null @@ -1,98 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/PolicyActionDecorator"], - function (PolicyActionDecorator) { - - describe("The policy action decorator", function () { - var mockPolicyService, - mockActionService, - testContext, - testActions, - decorator; - - beforeEach(function () { - mockPolicyService = jasmine.createSpyObj( - 'policyService', - ['allow'] - ); - mockActionService = jasmine.createSpyObj( - 'actionService', - ['getActions'] - ); - - // Content of actions should be irrelevant to this - // decorator, so just give it some objects to pass - // around. - testActions = [ - { someKey: "a" }, - { someKey: "b" }, - { someKey: "c" } - ]; - testContext = { someKey: "some value" }; - - mockActionService.getActions.and.returnValue(testActions); - mockPolicyService.allow.and.returnValue(true); - - decorator = new PolicyActionDecorator( - mockPolicyService, - mockActionService - ); - }); - - it("delegates to its decorated action service", function () { - decorator.getActions(testContext); - expect(mockActionService.getActions) - .toHaveBeenCalledWith(testContext); - }); - - it("provides actions from its decorated action service", function () { - // Mock policy service allows everything by default, - // so everything should be returned - expect(decorator.getActions(testContext)) - .toEqual(testActions); - }); - - it("consults the policy service for each candidate action", function () { - decorator.getActions(testContext); - testActions.forEach(function (testAction) { - expect(mockPolicyService.allow).toHaveBeenCalledWith( - 'action', - testAction, - testContext - ); - }); - }); - - it("filters out policy-disallowed actions", function () { - // Disallow the second action - mockPolicyService.allow.and.callFake(function (cat, candidate) { - return candidate.someKey !== 'b'; - }); - expect(decorator.getActions(testContext)) - .toEqual([testActions[0], testActions[2]]); - }); - - }); - } -); diff --git a/platform/policy/test/PolicyProviderSpec.js b/platform/policy/test/PolicyProviderSpec.js deleted file mode 100644 index 7304030c83..0000000000 --- a/platform/policy/test/PolicyProviderSpec.js +++ /dev/null @@ -1,128 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/PolicyProvider"], - function (PolicyProvider) { - - describe("The policy provider", function () { - var testPolicies, - mockPolicies, - mockPolicyConstructors, - testCandidate, - testContext, - provider; - - beforeEach(function () { - testPolicies = [ - { - category: "a", - message: "some message", - result: true - }, - { - category: "a", - result: true - }, - { - category: "a", - result: true - }, - { - category: "b", - message: "some message", - result: true - }, - { - category: "b", - result: true - }, - { - category: "b", - result: true - } - ]; - mockPolicies = testPolicies.map(function (p) { - var mockPolicy = jasmine.createSpyObj("policy", ['allow']); - mockPolicy.allow.and.callFake(function () { - return p.result; - }); - - return mockPolicy; - }); - mockPolicyConstructors = testPolicies.map(function (p, i) { - var mockPolicyConstructor = jasmine.createSpy(); - mockPolicyConstructor.and.returnValue(mockPolicies[i]); - mockPolicyConstructor.message = p.message; - mockPolicyConstructor.category = p.category; - - return mockPolicyConstructor; - }); - - testCandidate = { someKey: "some value" }; - testContext = { someOtherKey: "some other value" }; - - provider = new PolicyProvider(mockPolicyConstructors); - }); - - it("has an allow method", function () { - expect(provider.allow).toEqual(jasmine.any(Function)); - }); - - it("consults all relevant policies", function () { - provider.allow("a", testCandidate, testContext); - expect(mockPolicies[0].allow) - .toHaveBeenCalledWith(testCandidate, testContext); - expect(mockPolicies[1].allow) - .toHaveBeenCalledWith(testCandidate, testContext); - expect(mockPolicies[2].allow) - .toHaveBeenCalledWith(testCandidate, testContext); - expect(mockPolicies[3].allow) - .not.toHaveBeenCalled(); - expect(mockPolicies[4].allow) - .not.toHaveBeenCalled(); - expect(mockPolicies[5].allow) - .not.toHaveBeenCalled(); - }); - - it("allows what all policies allow", function () { - expect(provider.allow("a", testCandidate, testContext)) - .toBeTruthy(); - }); - - it("disallows what any one policy disallows", function () { - testPolicies[1].result = false; - expect(provider.allow("a", testCandidate, testContext)) - .toBeFalsy(); - }); - - it("provides a message for policy failure, when available", function () { - var mockCallback = jasmine.createSpy(); - testPolicies[0].result = false; - expect(provider.allow("a", testCandidate, testContext, mockCallback)) - .toBeFalsy(); - expect(mockCallback).toHaveBeenCalledWith(testPolicies[0].message); - }); - - }); - } -); diff --git a/platform/policy/test/PolicyViewDecoratorSpec.js b/platform/policy/test/PolicyViewDecoratorSpec.js deleted file mode 100644 index 2ed7951441..0000000000 --- a/platform/policy/test/PolicyViewDecoratorSpec.js +++ /dev/null @@ -1,102 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/PolicyViewDecorator"], - function (PolicyViewDecorator) { - - describe("The policy view decorator", function () { - var mockPolicyService, - mockViewService, - mockDomainObject, - testViews, - decorator; - - beforeEach(function () { - mockPolicyService = jasmine.createSpyObj( - 'policyService', - ['allow'] - ); - mockViewService = jasmine.createSpyObj( - 'viewService', - ['getViews'] - ); - mockDomainObject = jasmine.createSpyObj( - 'domainObject', - ['getId'] - ); - - // Content of actions should be irrelevant to this - // decorator, so just give it some objects to pass - // around. - testViews = [ - { someKey: "a" }, - { someKey: "b" }, - { someKey: "c" } - ]; - - mockDomainObject.getId.and.returnValue('xyz'); - mockViewService.getViews.and.returnValue(testViews); - mockPolicyService.allow.and.returnValue(true); - - decorator = new PolicyViewDecorator( - mockPolicyService, - mockViewService - ); - }); - - it("delegates to its decorated view service", function () { - decorator.getViews(mockDomainObject); - expect(mockViewService.getViews) - .toHaveBeenCalledWith(mockDomainObject); - }); - - it("provides views from its decorated view service", function () { - // Mock policy service allows everything by default, - // so everything should be returned - expect(decorator.getViews(mockDomainObject)) - .toEqual(testViews); - }); - - it("consults the policy service for each candidate view", function () { - decorator.getViews(mockDomainObject); - testViews.forEach(function (testView) { - expect(mockPolicyService.allow).toHaveBeenCalledWith( - 'view', - testView, - mockDomainObject - ); - }); - }); - - it("filters out policy-disallowed views", function () { - // Disallow the second action - mockPolicyService.allow.and.callFake(function (cat, candidate) { - return candidate.someKey !== 'b'; - }); - expect(decorator.getViews(mockDomainObject)) - .toEqual([testViews[0], testViews[2]]); - }); - - }); - } -); diff --git a/platform/representation/README.md b/platform/representation/README.md deleted file mode 100644 index 493a04bd0a..0000000000 --- a/platform/representation/README.md +++ /dev/null @@ -1,120 +0,0 @@ -This bundle introduces the notion of "representations" to Open MCT, -primarily via an Angular directive, `mct-representation`. - -A representation is used to display domain objects as Angular templates. - -# Extension Categories - -This bundle introduces four new categories of extension: - -* `templates`: Reusable Angular templates. This category of extension is - present to support the `mct-include` directive, which in turn is present - to allow templates to be loaded from across bundles, without knowing - their path ahead of time. A template has the following fields: - * `key`: The machine-readable name which identifies this template, - matched against the value given to the `key` attribute of the - `mct-include` directive. - * `templateUrl`: The path to the relevant Angular template. This - path is relative to the bundle's resources directory. -* `representations`: Ways of representing a domain object. A representation - is defined with the following fields: - * `key`: The machine-readable name which identifies the representation. - * `templateUrl`: The path to the representation's Angular template. This - path is relative to the bundle's resources directory. - * `uses`: An array of capability names. Indicates that this representation - intends to use those capabilities of a domain object (via a - `useCapability` call), and expects to find the latest results of - that `useCapability` call in the scope of the presented template (under - the same name as the capability itself.) - * `gestures`: An array of keys identifying gestures which should be - available upon this representation. Examples of gestures include - "drag" (for representations that should act as draggable sources - for drag-drop operations) and "menu" (for representations which - should show a domain-object-specific context menu on right-click.) -* `views`: A view is a representation with a visible identity to the user - (e.g. something they can switch among in the view menu.) A view - supports the same fields as a representation, and additionally: - * `name`: The human-readable name of the view. - * `glyph`: A character to display as an icon for this view. - * `description`: The human-readable description of the view. -* `gestures`: A gesture is a user action which can be taken upon a - representation of a domain object. Gestures are described by: - * `key`: The machine-readable name used to look up the gesture. - * `implementation`: The class (relative to the bundle's sources - directory) which implements the gesture. This is instantiated once - per representation that uses the gesture. This class will - receive the jqLite-wrapped `mct-representation` element and the - domain object being represented as arguments, and should do any - necessary "wiring" (e.g. listening for events) during its - constructor call. This class may also expose an optional `destroy()` - method which should be called when the gesture should be removed, - to avoid memory leaks by way of unremoved listeners. - - -# Extensions - -## Directives - -* `mct-include`: Includes a template by symbolic key; used to augment the - capability of Angular's `ng-include`, which loads templates by path. - Takes three attributes as Angular expressions: - * `key`: The symbolic identifier of the template to load, matched - against keys defined in extensions of category `templates`. - Note that this is an Angular expression, so internal quotes - may be necessary (see documentation of `ng-include`, which has the same - "gotcha" for URLs.) - * `ng-model`: Optional (and not often used); a model which should appear - in the included template's scope, for it to modify. The specific - interpretation of this attribute will vary depending on the included - template. - * `parameters`: Optional (and not often used); as `ng-model`, except the - intent is to provide information about how to display the included - template (e.g. "title", "color"). The specific interpretation of - this attribute will vary depending on the included template. -* `mct-representation`: Similar to `mct-include`, except the template to - include is specifically a representation of a domain object. - * `key`: As used in `mct-include`, except it will refer to an extension - or category `representations` or of `views`. - * `mct-object`: An Angular expression; the domain object to be - represented. - * `parameters`: As defined for `mct-include`. - -### Examples - - - - - - - - -## Components - -* `gestureService`: A provider of type `gestureService` is included to - remove the need to depend on `gestures[]` directly; instead, the - gesture service can be used to add/remove gestures in groups. This is - present primarily for bundle-internal use (it is used by the - `mct-representation` directive) but it is exposed as a service component - for convenience. - -## Gestures - -In addition to introducing `gestures` as a category of extension, this bundle -introduces three specific gestures as "built in" options, listed by key: - -* `drag`: Representations with this gesture can serve as drag sources for - drag-drop domain object composition. -* `drop`: Representations with this gesture can serve as drop targets for - drag-drop domain object composition. - * When a drop occurs, an `mctDrop` event will be broadcast with two - arguments (in addition to Angular's event object): The domain object - identifier for the dropped object, and the position (with `x` and `y` - properties in pixels) of the drop, relative to the top-left of the - representation which features the drop gesture. -* `menu`: Representations with this gesture will provide a custom context - menu (instead of the browser default). - * It should be noted that this gesture does _not_ define the appearance - or functionality of this menu; rather, it simply adds a - representation of key `context-menu` to the document at an appropriate - location. This representation will be supplied by the commonUI bundle. diff --git a/platform/representation/bundle.js b/platform/representation/bundle.js deleted file mode 100644 index 915eb306bc..0000000000 --- a/platform/representation/bundle.js +++ /dev/null @@ -1,144 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - "./src/MCTInclude", - "./src/MCTRepresentation", - "./src/gestures/DragGesture", - "./src/gestures/DropGesture", - "./src/gestures/GestureProvider", - "./src/gestures/GestureRepresenter", - "./src/services/DndService", - "./src/TemplateLinker", - "./src/TemplatePrefetcher" -], function ( - MCTInclude, - MCTRepresentation, - DragGesture, - DropGesture, - GestureProvider, - GestureRepresenter, - DndService, - TemplateLinker, - TemplatePrefetcher -) { - - return { - name: "platform/representation", - definition: { - "extensions": { - "directives": [ - { - "key": "mctInclude", - "implementation": MCTInclude, - "depends": [ - "templates[]", - "templateLinker" - ] - }, - { - "key": "mctRepresentation", - "implementation": MCTRepresentation, - "depends": [ - "representations[]", - "views[]", - "representers[]", - "$q", - "templateLinker", - "$log" - ] - } - ], - "gestures": [ - { - "key": "drag", - "implementation": DragGesture, - "depends": [ - "$log", - "dndService" - ] - }, - { - "key": "drop", - "implementation": DropGesture, - "depends": [ - "dndService", - "$q" - ] - } - ], - "components": [ - { - "provides": "gestureService", - "type": "provider", - "implementation": GestureProvider, - "depends": [ - "gestures[]" - ] - } - ], - "representers": [ - { - "implementation": GestureRepresenter, - "depends": [ - "gestureService" - ] - } - ], - "services": [ - { - "key": "dndService", - "implementation": DndService, - "depends": [ - "$log" - ] - }, - { - "key": "templateLinker", - "implementation": TemplateLinker, - "depends": [ - "$templateRequest", - "$sce", - "$compile", - "$log" - ], - "comment": "For internal use by mct-include and mct-representation." - } - ], - "runs": [ - { - "priority": "mandatory", - "implementation": TemplatePrefetcher, - "depends": [ - "templateLinker", - "templates[]", - "views[]", - "representations[]", - "controls[]", - "containers[]" - ] - } - ] - } - } - }; -}); diff --git a/platform/representation/src/MCTInclude.js b/platform/representation/src/MCTInclude.js deleted file mode 100644 index c2c308975a..0000000000 --- a/platform/representation/src/MCTInclude.js +++ /dev/null @@ -1,102 +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. - *****************************************************************************/ - -/** - * Module defining MCTInclude. Created by vwoeltje on 11/7/14. - */ -define( - [], - function () { - - /** - * Defines the mct-include directive. This acts like the - * ng-include directive, except it accepts a symbolic - * key which can be exposed by bundles, instead of requiring - * an explicit path. - * - * This directive uses two-way binding for three attributes: - * - * * `key`, matched against the key of a defined template extension - * in order to determine which actual template to include. - * * `ng-model`, populated as `ngModel` in the loaded template's - * scope; used for normal ng-model purposes (e.g. if the - * included template is meant to two-way bind to a data model.) - * * `parameters`, used to communicate display parameters to - * the included template (e.g. title.) The difference between - * `parameters` and `ngModel` is intent: Both are two-way - * bound, but `ngModel` is useful for data models (more like - * an output) and `parameters` is meant to be useful for - * display parameterization (more like an input.) - * - * @memberof platform/representation - * @constructor - * @param {TemplateDefinition[]} templates an array of - * template extensions - */ - function MCTInclude(templates, templateLinker) { - var templateMap = {}; - - function link(scope, element) { - var changeTemplate = templateLinker.link( - scope, - element, - scope.key && templateMap[scope.key] - ); - - scope.$watch('key', function (newKey, oldKey) { - if (newKey !== oldKey) { - changeTemplate(newKey && templateMap[newKey]); - } - }); - } - - // Prepopulate templateMap for easy look up by key - templates.forEach(function (template) { - var key = template.key; - // First found should win (priority ordering) - templateMap[key] = - templateMap[key] || template; - }); - - return { - // Only show at the element level - restrict: "E", - - // Use the included controller to populate scope - link: link, - - // May hide the element, so let other directives act first - priority: -1000, - - // Two-way bind key, ngModel, and parameters - scope: { - key: "=", - ngModel: "=", - parameters: "=" - } - }; - } - - return MCTInclude; - } -); - diff --git a/platform/representation/src/MCTRepresentation.js b/platform/representation/src/MCTRepresentation.js deleted file mode 100644 index 0bed902667..0000000000 --- a/platform/representation/src/MCTRepresentation.js +++ /dev/null @@ -1,308 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * This bundle implements the directives for representing domain objects - * as Angular-managed HTML. - * @namespace platform/representation - */ -define( - [], - function () { - - /** - * Defines the mct-representation directive. This may be used to - * present domain objects as HTML (with event wiring), with the - * specific representation being mapped to a defined extension - * (as defined in either the `representation` category-of-extension, - * or the `views` category-of-extension.) - * - * This directive uses two-way binding for three attributes: - * - * * `key`, matched against the key of a defined template extension - * in order to determine which actual template to include. - * * `mct-object`, populated as `domainObject` in the loaded - * template's scope. This is the domain object being - * represented as HTML by this directive. - * * `parameters`, used to communicate display parameters to - * the included template (e.g. title.) - * - * @memberof platform/representation - * @constructor - * @param {RepresentationDefinition[]} representations an array of - * representation extensions - * @param {ViewDefinition[]} views an array of view extensions - */ - function MCTRepresentation(representations, views, representers, $q, templateLinker, $log) { - var representationMap = {}; - - // Assemble all representations and views - // The distinction between views and representations is - // not important here (view is-a representation) - representations.concat(views).forEach(function (representation) { - var key = representation.key; - - // Store the representation - representationMap[key] = representationMap[key] || []; - representationMap[representation.key].push(representation); - }); - - // Look up a matching representation for this domain object - function lookup(key, domainObject) { - var candidates = representationMap[key] || [], - type, - i; - // Filter candidates by object type - for (i = 0; i < candidates.length; i += 1) { - type = candidates[i].type; - if (!type || !domainObject - || domainObject.getCapability('type').instanceOf(type)) { - return candidates[i]; - } - } - } - - function link($scope, element, attrs) { - var activeRepresenters = representers.map(function (Representer) { - return new Representer($scope, element, attrs); - }), - toClear = [], // Properties to clear out of scope on change - counter = 0, - couldRepresent = false, - lastIdPath = [], - lastKey, - mutationListener, - changeTemplate = templateLinker.link($scope, element); - - // Populate scope with any capabilities indicated by the - // representation's extension definition - function refreshCapabilities() { - var domainObject = $scope.domainObject, - representation = lookup($scope.key, domainObject), - uses = ((representation || {}).uses || []), - myCounter = counter; - - if (mutationListener) { - mutationListener(); - mutationListener = undefined; - } - - if (domainObject) { - mutationListener = domainObject - .getCapability('mutation') - .listen(refreshCapabilities); - - // Update model - $scope.model = domainObject.getModel(); - - // Provide any of the capabilities requested - uses.forEach(function (used) { - $log.debug([ - "Requesting capability ", - used, - " for representation ", - $scope.key - ].join("")); - - $q.when( - domainObject.useCapability(used) - ).then(function (c) { - // Avoid clobbering capabilities from - // subsequent representations; - // Angular reuses scopes. - if (counter === myCounter) { - $scope[used] = c; - } - }); - }); - } - } - - // Destroy (deallocate any resources associated with) any - // active representers. - function destroyRepresenters() { - activeRepresenters.forEach(function (activeRepresenter) { - activeRepresenter.destroy(); - }); - } - - function unchanged(canRepresent, idPath, key) { - return (canRepresent === couldRepresent) - && (key === lastKey) - && (idPath.length === lastIdPath.length) - && idPath.every(function (id, i) { - return id === lastIdPath[i]; - }); - } - - function getIdPath(domainObject) { - if (!domainObject) { - return []; - } - - if (!domainObject.hasCapability('context')) { - return [domainObject.getId()]; - } - - return domainObject.getCapability('context') - .getPath().map(function (pathObject) { - return pathObject.getId(); - }); - } - - // General-purpose refresh mechanism; should set up the scope - // as appropriate for current representation key and - // domain object. - function refresh() { - var domainObject = $scope.domainObject, - representation = lookup($scope.key, domainObject), - uses = ((representation || {}).uses || []), - canRepresent = Boolean(representation && domainObject), - idPath = getIdPath(domainObject), - key = $scope.key; - - if (unchanged(canRepresent, idPath, key)) { - return; - } - - // Create an empty object named "representation", for this - // representation to store local variables into. - $scope.representation = {}; - - // Change templates (passing in undefined to clear - // if we don't have enough info to show a template.) - changeTemplate(canRepresent ? representation : undefined); - - // Any existing representers are no longer valid; release them. - destroyRepresenters(); - - // Log if a key was given, but no matching representation - // was found. - if (!representation && $scope.key) { - $log.warn("No representation found for " + $scope.key); - } - - // Clear out the scope from the last representation - toClear.forEach(function (property) { - delete $scope[property]; - }); - - // To allow simplified change detection next time around - couldRepresent = canRepresent; - lastIdPath = idPath; - lastKey = key; - - // Populate scope with fields associated with the current - // domain object (if one has been passed in) - if (canRepresent) { - // Track how many representations we've made in this scope, - // to ensure that the correct representations are matched to - // the correct object/key pairs. - counter += 1; - - // Initialize any capabilities - refreshCapabilities(); - - // Also provide the view configuration, - // for the specific view - $scope.configuration = - ($scope.model.configuration || {})[$scope.key] || {}; - - // Finally, wire up any additional behavior (such as - // gestures) associated with this representation. - activeRepresenters.forEach(function (representer) { - representer.represent(representation, domainObject); - }); - - // Track which properties we want to clear from scope - // next change object/key pair changes - toClear = uses.concat(['model']); - } - } - - // Update the representation when the key changes (e.g. if a - // different representation has been selected) - $scope.$watch("key", refresh); - - // Also update when the represented domain object changes - // (to a different object) - $scope.$watch("domainObject", refresh); - - // Make sure any resources allocated by representers also get - // released. - $scope.$on("$destroy", destroyRepresenters); - $scope.$on("$destroy", function () { - if (mutationListener) { - mutationListener(); - } - }); - - // Do one initial refresh, so that we don't need another - // digest iteration just to populate the scope. Failure to - // do this can result in unstable digest cycles, which - // Angular will detect, and throw an Error about. - refresh(); - } - - return { - // Only applicable at the element level - restrict: "E", - - // Handle Angular's linking step - link: link, - - // May hide the element, so let other directives act first - priority: -1000, - - // Two-way bind key and parameters, get the represented domain - // object as "mct-object" - scope: { - key: "=", - domainObject: "=mctObject", - ngModel: "=", - parameters: "=" - } - }; - } - - /** - * A representer participates in the process of instantiating a - * representation of a domain object. - * - * @interface Representer - * @augments {Destroyable} - */ - /** - * Set the current representation in use, and the domain - * object being represented. - * - * @method Representer#represent - * @param {RepresentationDefinition} representation the - * definition of the representation in use - * @param {DomainObject} domainObject the domain object - * being represented - */ - - return MCTRepresentation; - } -); - diff --git a/platform/representation/src/TemplateLinker.js b/platform/representation/src/TemplateLinker.js deleted file mode 100644 index d42a9e8eeb..0000000000 --- a/platform/representation/src/TemplateLinker.js +++ /dev/null @@ -1,177 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * The `templateLinker` service is intended for internal use by - * the `mct-include` and `mct-representation` directives. It is - * used to support common behavior of directives; specifically, - * loading templates and inserting them into a specified element, - * and/or removing that element from the DOM when there is no - * template to populate it with. - * - * @param {Function} $templateRequest Angular's `$templateRequest` - * service - * @param $sce Angular's `$sce` service - * @param {Function} $compile Angular's `$compile` service - * @param $log Angular's `$log` service - * @private - */ - function TemplateLinker($templateRequest, $sce, $compile, $log) { - this.$templateRequest = $templateRequest; - this.$sce = $sce; - this.$compile = $compile; - this.$log = $log; - } - - /** - * Load a template from the given URL. This request will be handled - * via `$templateRequest` to ensure caching et cetera. - * @param {string} the URL for the template - * @returns {Promise.} a promise for the HTML content of - * the template - */ - TemplateLinker.prototype.load = function (templateUrl) { - return this.$templateRequest( - this.$sce.trustAsResourceUrl(templateUrl), - false - ); - }; - - /** - * Get a path to a template from an extension definition fo - * a template, representation, or view. - * @param {TemplateDefinition} extensionDefinition the definition - * of the template/representation/view to resolve - */ - TemplateLinker.prototype.getPath = function (extensionDefinition) { - return [ - extensionDefinition.bundle.path, - extensionDefinition.bundle.resources, - extensionDefinition.templateUrl - ].join('/'); - }; - - /** - * Populate the given element with templates, within the given scope; - * intended to support the `link` function of the supported directives. - * - * @param {Scope} scope the Angular scope to use when rendering - * templates - * @param element the jqLite-wrapped element into which templates - * should be inserted - * @param {TemplateDefinition} extensionDefinition the definition - * of the template/representation/view to display initially - * @returns {Function} a function which can be called with a template's - * extension definition to switch templates, or `undefined` - * to remove. - */ - TemplateLinker.prototype.link = function (scope, element, ext) { - var activeElement = element, - activeTemplateUrl, - comment = this.$compile('')(scope), - activeScope, - self = this; - - function destroyScope() { - if (activeScope) { - activeScope.$destroy(); - activeScope = undefined; - } - } - - function removeElement() { - if (activeElement !== comment) { - destroyScope(); - activeElement.replaceWith(comment); - activeElement = comment; - } - } - - function addElement() { - if (activeElement !== element) { - activeElement.replaceWith(element); - activeElement = element; - activeElement.empty(); - } - } - - function populateElement(template) { - destroyScope(); - activeScope = scope.$new(false); - element.html(template); - self.$compile(element.contents())(activeScope); - } - - function showTemplate(template) { - addElement(); - populateElement(template); - activeTemplateUrl = undefined; - } - - function badTemplateUrl(templateUrl) { - self.$log.warn("Couldn't load template at " + templateUrl); - removeElement(); - } - - function changeTemplateUrl(templateUrl) { - if (templateUrl) { - destroyScope(); - addElement(); - self.load(templateUrl).then(function (template) { - // Avoid race conditions - if (templateUrl === activeTemplateUrl) { - populateElement(template); - } - }, function () { - badTemplateUrl(templateUrl); - }); - } else { - removeElement(); - } - - activeTemplateUrl = templateUrl; - } - - function changeTemplate(templateExt) { - templateExt = templateExt || {}; - if (templateExt.templateUrl) { - changeTemplateUrl(self.getPath(templateExt)); - } else if (templateExt.template) { - showTemplate(templateExt.template); - } else { - removeElement(); - } - } - - changeTemplate(ext); - - return changeTemplate; - }; - - return TemplateLinker; - } -); - diff --git a/platform/representation/src/TemplatePrefetcher.js b/platform/representation/src/TemplatePrefetcher.js deleted file mode 100644 index 1509d2d0c1..0000000000 --- a/platform/representation/src/TemplatePrefetcher.js +++ /dev/null @@ -1,48 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - function () { - - /** - * Loads all templates when the application is started. - * @param {platform/representation.TemplateLinker} templateLinker - * the `templateLinker` service, used to load and cache - * template extensions - * @param {...Array.<{templateUrl: string}>} extensions arrays - * of template or template-like extensions - */ - function TemplatePrefetcher(templateLinker) { - Array.prototype.slice.apply(arguments, [1]) - .reduce(function (a, b) { - return a.concat(b); - }, []) - .forEach(function (ext) { - if (ext.templateUrl) { - templateLinker.load(templateLinker.getPath(ext)); - } - }); - } - - return TemplatePrefetcher; - } -); diff --git a/platform/representation/src/gestures/DragGesture.js b/platform/representation/src/gestures/DragGesture.js deleted file mode 100644 index 937b63f6ae..0000000000 --- a/platform/representation/src/gestures/DragGesture.js +++ /dev/null @@ -1,119 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * Module defining DragGesture. Created by vwoeltje on 11/17/14. - */ -define( - ['./GestureConstants'], - function (GestureConstants) { - - /** - * Add event handlers to a representation such that it may be - * dragged as the source for drag-drop composition. - * - * @memberof platform/representation - * @constructor - * @implements {Gesture} - * @param $log Angular's logging service - * @param element the jqLite-wrapped element which should become - * draggable - * @param {DomainObject} domainObject the domain object which - * is represented; this will be passed on drop. - */ - function DragGesture($log, dndService, element, domainObject) { - function startDrag(e) { - var event = (e || {}).originalEvent || e; - - $log.debug("Initiating drag"); - - try { - // Set the data associated with the drag-drop operation - event.dataTransfer.effectAllowed = 'move'; - - // Support drop as plain-text (JSON); not used internally - event.dataTransfer.setData( - 'text/plain', - JSON.stringify({ - id: domainObject.getId(), - model: domainObject.getModel() - }) - ); - - // For internal use, pass the object's identifier as - // part of the drag - event.dataTransfer.setData( - GestureConstants.MCT_DRAG_TYPE, - domainObject.getId() - ); - - // Finally, also pass the id object instance via the - // dndService, allowing inspection during drag as well - // as retrieval of the original domain object. - dndService.setData( - GestureConstants.MCT_EXTENDED_DRAG_TYPE, - domainObject - ); - dndService.setData( - GestureConstants.MCT_DRAG_TYPE, - domainObject.getId() - ); - - } catch (err) { - // Exceptions at this point indicate that the browser - // do not fully support drag-and-drop (e.g. if - // dataTransfer is undefined) - $log.warn([ - "Could not initiate drag due to ", - err.message - ].join("")); - } - - } - - function endDrag() { - // Clear the drag data after the drag is complete - dndService.removeData(GestureConstants.MCT_DRAG_TYPE); - dndService.removeData(GestureConstants.MCT_EXTENDED_DRAG_TYPE); - } - - // Mark the element as draggable, and handle the dragstart event - $log.debug("Attaching drag gesture"); - element.attr('draggable', 'true'); - element.on('dragstart', startDrag); - element.on('dragend', endDrag); - - this.element = element; - this.startDragCallback = startDrag; - this.endDragCallback = endDrag; - } - - DragGesture.prototype.destroy = function () { - // Detach listener - this.element.removeAttr('draggable'); - this.element.off('dragstart', this.startDragCallback); - this.element.off('dragend', this.endDragCallback); - }; - - return DragGesture; - } -); diff --git a/platform/representation/src/gestures/DropGesture.js b/platform/representation/src/gestures/DropGesture.js deleted file mode 100644 index 311ab3ec61..0000000000 --- a/platform/representation/src/gestures/DropGesture.js +++ /dev/null @@ -1,129 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/** - * Module defining DropGesture. Created by vwoeltje on 11/17/14. - */ -define( - ['./GestureConstants'], - function (GestureConstants) { - - /** - * A DropGesture adds and maintains event handlers upon an element - * such that it may act as a drop target for drag-drop composition. - * - * @memberof platform/representation - * @constructor - * @param $q Angular's $q, for promise handling - * @param element the jqLite-wrapped representation element - * @param {DomainObject} domainObject the domain object whose - * composition should be modified as a result of the drop. - */ - function DropGesture(dndService, $q, element, domainObject) { - var actionCapability = domainObject.getCapability('action'), - action; // Action for the drop, when it occurs - - function broadcastDrop(id, event) { - // Find the relevant scope... - var rect, - scope = element.scope && element.scope(); - - if (scope && scope.$broadcast) { - // Get the representation's bounds, to convert - // drop position - rect = element[0].getBoundingClientRect(); - - // ...and broadcast the event. This allows specific - // views to have post-drop behavior which depends on - // drop position. - scope.$broadcast( - GestureConstants.MCT_DROP_EVENT, - id, - { - x: event.pageX - rect.left, - y: event.pageY - rect.top - } - ); - } - } - - function dragOver(e) { - var event = (e || {}).originalEvent || e, - selectedObject = dndService.getData( - GestureConstants.MCT_EXTENDED_DRAG_TYPE - ); - - if (selectedObject) { - // TODO: Vary this based on modifier keys - action = actionCapability.getActions({ - key: 'compose', - selectedObject: selectedObject - })[0]; - if (action) { - event.dataTransfer.dropEffect = 'move'; - - // Indicate that we will accept the drag - event.preventDefault(); // Required in Chrome? - - return false; - } - } - } - - function drop(e) { - var event = (e || {}).originalEvent || e, - id = event.dataTransfer.getData(GestureConstants.MCT_DRAG_TYPE); - - // Handle the drop; add the dropped identifier to the - // destination domain object's composition, and persist - // the change. - if (id) { - e.preventDefault(); - $q.when(action && action.perform()).then(function () { - broadcastDrop(id, event); - }); - - } - } - - // We can only handle drops if we have access to actions... - if (actionCapability) { - // Listen for dragover, to indicate we'll accept a drag - element.on('dragover', dragOver); - - // Listen for the drop itself - element.on('drop', drop); - } - - this.element = element; - this.dragOverCallback = dragOver; - this.dropCallback = drop; - } - - DropGesture.prototype.destroy = function () { - this.element.off('dragover', this.dragOverCallback); - this.element.off('drop', this.dropCallback); - }; - - return DropGesture; - } -); diff --git a/platform/representation/src/gestures/GestureConstants.js b/platform/representation/src/gestures/GestureConstants.js deleted file mode 100644 index 1463126c7f..0000000000 --- a/platform/representation/src/gestures/GestureConstants.js +++ /dev/null @@ -1,53 +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. - *****************************************************************************/ - -/** - * Constants used by domain object gestures. - * @class platform/representation.GestureConstants - */ -define({ - /** - * The string identifier for the data type used for drag-and-drop - * composition of domain objects. (e.g. in event.dataTransfer.setData - * calls.) - * @memberof platform/representation.GestureConstants - */ - MCT_DRAG_TYPE: 'mct-domain-object-id', - /** - * The string identifier for the data type used for drag-and-drop - * composition of domain objects, by object instance (passed through - * the dndService) - * @memberof platform/representation.GestureConstants - */ - MCT_EXTENDED_DRAG_TYPE: 'mct-domain-object', - /** - * An estimate for the dimensions of a context menu, used for - * positioning. - * @memberof platform/representation.GestureConstants - */ - MCT_MENU_DIMENSIONS: [170, 200], - /** - * Identifier for drop events. - * @memberof platform/representation.GestureConstants - */ - MCT_DROP_EVENT: 'mctDrop' -}); diff --git a/platform/representation/src/gestures/GestureProvider.js b/platform/representation/src/gestures/GestureProvider.js deleted file mode 100644 index 284f34ecfe..0000000000 --- a/platform/representation/src/gestures/GestureProvider.js +++ /dev/null @@ -1,133 +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. - *****************************************************************************/ - -/** - * Module defining GestureProvider. Created by vwoeltje on 11/22/14. - */ -define( - [], - function () { - - /** - * Handles the attachment of gestures (responses to DOM events, - * generally) to DOM elements which represent domain objects. - * - * @interface GestureService - */ - /** - * Attach a set of gestures (indicated by key) to a - * DOM element which represents a specific domain object. - * @method GestureService#attachGestures - * @param element the jqLite-wrapped DOM element which the - * user will interact with - * @param {DomainObject} domainObject the domain object which - * is represented by that element - * @param {string[]} gestureKeys an array of keys identifying - * which gestures should apply; these will be matched - * against the keys defined in the gestures' extension - * definitions - * @return {Destroyable} an object with a `destroy` - * method which can (and should) be used when - * gestures should no longer be applied to an element. - */ - - /** - * The GestureProvider exposes defined gestures. Gestures are used - * do describe and handle general-purpose interactions with the DOM - * that should be interpreted as interactions with domain objects, - * such as right-clicking to expose context menus. - * - * Gestures are defined individually as extensions of the - * `gestures` category. The gesture provider merely serves as an - * intermediary between these and the `mct-representation` directive - * where they are used. - * - * @memberof platform/representation - * @implements {GestureService} - * @constructor - * @param {Gesture[]} gestures an array of all gestures which are - * available as extensions - */ - function GestureProvider(gestures) { - var gestureMap = {}; - - // Assemble all gestures into a map, for easy look up - gestures.forEach(function (gesture) { - gestureMap[gesture.key] = gestureMap[gesture.key] || gesture; - }); - - this.gestureMap = gestureMap; - } - - function releaseGesture(gesture) { - // Invoke the gesture's "destroy" method (if there is one) - // to release any held resources and detach event handlers. - if (gesture && gesture.destroy) { - gesture.destroy(); - } - } - - GestureProvider.prototype.attachGestures = function attachGestures(element, domainObject, gestureKeys) { - // Look up the desired gestures, filter for applicability, - // and instantiate them. Maintain a reference to allow them - // to be destroyed as a group later. - var gestureMap = this.gestureMap, - attachedGestures = gestureKeys.map(function (key) { - return gestureMap[key]; - }).filter(function (Gesture) { - return Gesture !== undefined && (Gesture.appliesTo - ? Gesture.appliesTo(domainObject) - : true); - }).map(function (Gesture) { - return new Gesture(element, domainObject); - }); - - return { - destroy: function () { - // Just call all the individual "destroy" methods - attachedGestures.forEach(releaseGesture); - } - }; - }; - - /** - * A destroyable object may have resources allocated which require - * explicit release. - * - * @interface Destroyable - */ - /** - * Release any resources associated with this object. - * - * @method Destroyable#destroy - */ - - /** - * A gesture describes manners in which certain representations of - * domain objects may respond to DOM events upon those representations. - * @interface Gesture - * @augments Destroyable - */ - - return GestureProvider; - } -); diff --git a/platform/representation/src/gestures/GestureRepresenter.js b/platform/representation/src/gestures/GestureRepresenter.js deleted file mode 100644 index 8ed69638f8..0000000000 --- a/platform/representation/src/gestures/GestureRepresenter.js +++ /dev/null @@ -1,68 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * The GestureRepresenter is responsible for installing predefined - * gestures upon mct-representation instances. - * Gestures themselves are pulled from the gesture service; this - * simply wraps that behavior in a Representer interface, such that - * it may be included among other such Representers used to prepare - * specific representations. - * @param {GestureService} gestureService the service which provides - * gestures - * @param {Scope} scope the Angular scope for this representation - * @param element the JQLite-wrapped mct-representation element - * @constructor - * @implements {Representer} - * @memberof platform/representation - */ - function GestureRepresenter(gestureService, scope, element) { - this.gestureService = gestureService; - this.element = element; - } - - GestureRepresenter.prototype.represent = function represent(representation, domainObject) { - // Clear out any existing gestures - this.destroy(); - - // Attach gestures - by way of the service. - this.gestureHandle = this.gestureService.attachGestures( - this.element, - domainObject, - (representation || {}).gestures || [] - ); - }; - - GestureRepresenter.prototype.destroy = function () { - // Release any resources associated with these gestures - if (this.gestureHandle) { - this.gestureHandle.destroy(); - } - }; - - return GestureRepresenter; - } -); diff --git a/platform/representation/src/services/DndService.js b/platform/representation/src/services/DndService.js deleted file mode 100644 index de5d01f601..0000000000 --- a/platform/representation/src/services/DndService.js +++ /dev/null @@ -1,73 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - [], - function () { - - /** - * Drag-and-drop service. - * Supplements HTML5 drag-and-drop support by: - * * Storing arbitrary JavaScript objects (not just strings.) - * * Allowing inspection of dragged objects during `dragover` events, - * etc. (which cannot be done in Chrome for security reasons) - * @memberof platform/representation - * @constructor - * @param $log Angular's $log service - */ - function DndService($log) { - var data = {}; - - return { - /** - * Set drag data associated with a given type. - * @param {string} key the type's identiifer - * @param {*} value the data being dragged - * @memberof platform/representation.DndService# - */ - setData: function (key, value) { - $log.debug("Setting drag data for " + key); - data[key] = value; - }, - /** - * Get drag data associated with a given type. - * @returns {*} the data being dragged - * @memberof platform/representation.DndService# - */ - getData: function (key) { - return data[key]; - }, - /** - * Remove data associated with active drags. - * @param {string} key the type to remove - * @memberof platform/representation.DndService# - */ - removeData: function (key) { - $log.debug("Clearing drag data for " + key); - delete data[key]; - } - }; - } - - return DndService; - } -); diff --git a/platform/representation/test/MCTIncludeSpec.js b/platform/representation/test/MCTIncludeSpec.js deleted file mode 100644 index bee7ca4d47..0000000000 --- a/platform/representation/test/MCTIncludeSpec.js +++ /dev/null @@ -1,108 +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. - *****************************************************************************/ - -/** - * MCTIncudeSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../src/MCTInclude"], - function (MCTInclude) { - - describe("The mct-include directive", function () { - var testTemplates, - testUrls, - mockLinker, - mockScope, - mockElement, - mockChangeTemplate, - mctInclude; - - function fireWatch(expr, value) { - mockScope.$watch.calls.all().forEach(function (call) { - if (call.args[0] === expr) { - call.args[1](value); - } - }); - } - - beforeEach(function () { - testTemplates = [ - { - key: "abc", - bundle: { - path: "a", - resources: "b" - }, - templateUrl: "c/template.html" - }, - { - key: "xyz", - bundle: { - path: "x", - resources: "y" - }, - templateUrl: "z/template.html" - } - ]; - testUrls = {}; - testTemplates.forEach(function (t, i) { - testUrls[t.key] = "some URL " + String(i); - }); - mockLinker = jasmine.createSpyObj( - 'templateLinker', - ['link', 'getPath'] - ); - mockScope = jasmine.createSpyObj('$scope', ['$watch', '$on']); - mockElement = jasmine.createSpyObj('element', ['empty']); - mockChangeTemplate = jasmine.createSpy('changeTemplate'); - mockLinker.link.and.returnValue(mockChangeTemplate); - mockLinker.getPath.and.callFake(function (template) { - return testUrls[template.key]; - }); - mctInclude = new MCTInclude(testTemplates, mockLinker); - mctInclude.link(mockScope, mockElement, {}); - }); - - it("is restricted to elements", function () { - expect(mctInclude.restrict).toEqual("E"); - }); - - it("exposes templates via the templateLinker", function () { - expect(mockLinker.link) - .toHaveBeenCalledWith(mockScope, mockElement, undefined); - }); - - it("reads a template location from a scope's key variable", function () { - mockScope.key = 'abc'; - fireWatch('key', mockScope.key); - expect(mockChangeTemplate) - .toHaveBeenCalledWith(testTemplates[0]); - - mockScope.key = 'xyz'; - fireWatch('key', mockScope.key); - expect(mockChangeTemplate) - .toHaveBeenCalledWith(testTemplates[1]); - }); - - }); - } -); diff --git a/platform/representation/test/MCTRepresentationSpec.js b/platform/representation/test/MCTRepresentationSpec.js deleted file mode 100644 index 195a511337..0000000000 --- a/platform/representation/test/MCTRepresentationSpec.js +++ /dev/null @@ -1,343 +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. - *****************************************************************************/ - -/** - * MCTRepresentationSpec. Created by vwoeltje on 11/6/14. - */ -define( - ["../src/MCTRepresentation"], - function (MCTRepresentation) { - - var JQLITE_FUNCTIONS = ["on", "off", "attr", "removeAttr"], - LOG_FUNCTIONS = ["error", "warn", "info", "debug"], - DOMAIN_OBJECT_METHODS = ["getId", "getModel", "getCapability", "hasCapability", "useCapability"]; - - describe("The mct-representation directive", function () { - var testRepresentations, - testViews, - testUrls, - mockRepresenters, - mockMutationCapability, - mockQ, - mockLinker, - mockLog, - mockChangeTemplate, - mockScope, - mockElement, - mockDomainObject, - testModel, - mctRepresentation; - - function mockPromise(value) { - return (value && value.then) ? value : { - then: function (callback) { - return mockPromise(callback(value)); - } - }; - } - - function fireWatch(expr, value) { - mockScope.$watch.calls.all().forEach(function (call) { - if (call.args[0] === expr) { - call.args[1](value); - } - }); - } - - beforeEach(function () { - testUrls = {}; - - testRepresentations = [ - { - key: "abc", - bundle: { - path: "a", - resources: "b" - }, - templateUrl: "c/template.html" - }, - { - key: "def", - bundle: { - path: "d", - resources: "e" - }, - templateUrl: "f/template.html", - uses: ["testCapability", "otherTestCapability"] - } - ]; - - testViews = [ - { - key: "uvw", - bundle: { - path: "u", - resources: "v" - }, - templateUrl: "w/template.html", - gestures: ["testGesture", "otherTestGesture"] - }, - { - key: "xyz", - bundle: { - path: "x", - resources: "y" - }, - templateUrl: "z/template.html" - } - ]; - - testModel = { someKey: "some value" }; - - testUrls = {}; - testViews.concat(testRepresentations).forEach(function (t, i) { - testUrls[t.key] = "some URL " + String(i); - }); - - mockRepresenters = ["A", "B"].map(function (name) { - var constructor = jasmine.createSpy("Representer" + name), - representer = jasmine.createSpyObj( - "representer" + name, - ["represent", "destroy"] - ); - constructor.and.returnValue(representer); - - return constructor; - }); - - mockQ = { when: mockPromise }; - mockLinker = jasmine.createSpyObj( - 'templateLinker', - ['link', 'getPath'] - ); - mockChangeTemplate = jasmine.createSpy('changeTemplate'); - mockLog = jasmine.createSpyObj("$log", LOG_FUNCTIONS); - - mockMutationCapability = - jasmine.createSpyObj("mutation", ["listen"]); - - mockScope = jasmine.createSpyObj("scope", ["$watch", "$on"]); - mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS); - mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS); - - mockDomainObject.getModel.and.returnValue(testModel); - mockLinker.link.and.returnValue(mockChangeTemplate); - mockLinker.getPath.and.callFake(function (ext) { - return testUrls[ext.key]; - }); - - mockDomainObject.getCapability.and.callFake(function (c) { - return c === 'mutation' && mockMutationCapability; - }); - - mctRepresentation = new MCTRepresentation( - testRepresentations, - testViews, - mockRepresenters, - mockQ, - mockLinker, - mockLog - ); - mctRepresentation.link(mockScope, mockElement); - }); - - it("is restricted to elements", function () { - expect(mctRepresentation.restrict).toEqual("E"); - }); - - it("exposes templates via the templateLinker", function () { - expect(mockLinker.link) - .toHaveBeenCalledWith(mockScope, mockElement); - }); - - it("watches scope when linked", function () { - expect(mockScope.$watch).toHaveBeenCalledWith( - "key", - jasmine.any(Function) - ); - expect(mockScope.$watch).toHaveBeenCalledWith( - "domainObject", - jasmine.any(Function) - ); - }); - - it("recognizes keys for representations", function () { - mockScope.key = "abc"; - mockScope.domainObject = mockDomainObject; - - // Trigger the watch - fireWatch('key', mockScope.key); - fireWatch('domainObject', mockDomainObject); - - expect(mockChangeTemplate) - .toHaveBeenCalledWith(testRepresentations[0]); - }); - - it("recognizes keys for views", function () { - mockScope.key = "xyz"; - mockScope.domainObject = mockDomainObject; - - // Trigger the watches - fireWatch('key', mockScope.key); - fireWatch('domainObject', mockDomainObject); - - expect(mockChangeTemplate) - .toHaveBeenCalledWith(testViews[1]); - }); - - it("does not load templates until there is an object", function () { - mockScope.key = "xyz"; - - // Trigger the watch - fireWatch('key', mockScope.key); - - expect(mockChangeTemplate) - .not.toHaveBeenCalledWith(jasmine.any(Object)); - - mockScope.domainObject = mockDomainObject; - fireWatch('domainObject', mockDomainObject); - - expect(mockChangeTemplate) - .toHaveBeenCalledWith(jasmine.any(Object)); - }); - - it("loads declared capabilities", function () { - mockScope.key = "def"; - mockScope.domainObject = mockDomainObject; - - // Trigger the watch - mockScope.$watch.calls.all()[0].args[1](); - - expect(mockDomainObject.useCapability) - .toHaveBeenCalledWith("testCapability"); - expect(mockDomainObject.useCapability) - .toHaveBeenCalledWith("otherTestCapability"); - }); - - it("logs when no representation is available for a key", function () { - mockScope.key = "someUnknownThing"; - - // Verify precondition - expect(mockLog.warn).not.toHaveBeenCalled(); - - // Trigger the watch - mockScope.$watch.calls.all()[0].args[1](); - - // Should have gotten a warning - that's an unknown key - expect(mockLog.warn).toHaveBeenCalled(); - }); - - it("clears out obsolete properties from scope", function () { - mockScope.key = "def"; - mockScope.domainObject = mockDomainObject; - mockDomainObject.useCapability.and.returnValue("some value"); - - // Trigger the watch - mockScope.$watch.calls.all()[0].args[1](); - expect(mockScope.testCapability).toBeDefined(); - - // Change the view - mockScope.key = "xyz"; - - // Trigger the watch again; should clear capability from scope - mockScope.$watch.calls.all()[0].args[1](); - expect(mockScope.testCapability).toBeUndefined(); - }); - - describe("when a domain object has been observed", function () { - var mockContext, - mockContext2, - mockLink, - mockParent; - - beforeEach(function () { - mockContext = jasmine.createSpyObj('context', ['getPath']); - mockContext2 = jasmine.createSpyObj('context', ['getPath']); - mockLink = jasmine.createSpyObj( - 'linkedObject', - DOMAIN_OBJECT_METHODS - ); - mockParent = jasmine.createSpyObj( - 'parentObject', - DOMAIN_OBJECT_METHODS - ); - - mockDomainObject.getCapability.and.callFake(function (c) { - return { - context: mockContext, - mutation: mockMutationCapability - }[c]; - }); - mockLink.getCapability.and.callFake(function (c) { - return { - context: mockContext2, - mutation: mockMutationCapability - }[c]; - }); - mockDomainObject.hasCapability.and.callFake(function (c) { - return c === 'context'; - }); - mockLink.hasCapability.and.callFake(function (c) { - return c === 'context'; - }); - mockLink.getModel.and.returnValue({}); - - mockContext.getPath.and.returnValue([mockDomainObject]); - mockContext2.getPath.and.returnValue([mockParent, mockLink]); - - mockLink.getId.and.returnValue('test-id'); - mockDomainObject.getId.and.returnValue('test-id'); - - mockParent.getId.and.returnValue('parent-id'); - - mockScope.key = "abc"; - mockScope.domainObject = mockDomainObject; - - mockScope.$watch.calls.all()[0].args[1](); - }); - - it("listens for mutation of that object", function () { - expect(mockMutationCapability.listen) - .toHaveBeenCalledWith(jasmine.any(Function)); - }); - - it("detects subsequent changes among linked instances", function () { - var callCount = mockChangeTemplate.calls.count(); - - mockScope.domainObject = mockLink; - mockScope.$watch.calls.all()[0].args[1](); - - expect(mockChangeTemplate.calls.count()) - .toEqual(callCount + 1); - }); - - it("does not trigger excess template changes for same instances", function () { - var callCount = mockChangeTemplate.calls.count(); - mockScope.$watch.calls.all()[0].args[1](); - expect(mockChangeTemplate.calls.count()).toEqual(callCount); - }); - - }); - - }); - } -); diff --git a/platform/representation/test/TemplateLinkerSpec.js b/platform/representation/test/TemplateLinkerSpec.js deleted file mode 100644 index aabe33d264..0000000000 --- a/platform/representation/test/TemplateLinkerSpec.js +++ /dev/null @@ -1,239 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define( - ["../src/TemplateLinker"], - function (TemplateLinker) { - - var JQLITE_METHODS = ['replaceWith', 'empty', 'html', 'contents'], - SCOPE_METHODS = ['$on', '$new', '$destroy']; - - describe("TemplateLinker", function () { - var mockTemplateRequest, - mockSce, - mockCompile, - mockLog, - mockScope, - mockElement, - mockTemplates, - mockElements, - mockContents, - mockNewScope, - mockPromise, - linker; - - function testExtension(path, res, templatePath) { - return { - bundle: { - path: path, - resources: res - }, - templateUrl: templatePath - }; - } - - beforeEach(function () { - mockTemplateRequest = jasmine.createSpy('$templateRequest'); - mockSce = jasmine.createSpyObj('$sce', ['trustAsResourceUrl']); - mockCompile = jasmine.createSpy('$compile'); - mockLog = jasmine.createSpyObj('$log', ['error', 'warn']); - mockScope = jasmine.createSpyObj('$scope', SCOPE_METHODS); - mockNewScope = jasmine.createSpyObj('$scope', SCOPE_METHODS); - mockElement = jasmine.createSpyObj('element', JQLITE_METHODS); - mockPromise = jasmine.createSpyObj('promise', ['then']); - mockTemplates = {}; - mockElements = {}; - mockContents = {}; - - mockTemplateRequest.and.returnValue(mockPromise); - mockCompile.and.callFake(function (toCompile) { - var html = typeof toCompile === 'string' - ? toCompile : toCompile.testHtml; - mockTemplates[html] = jasmine.createSpy('template'); - mockElements[html] = - jasmine.createSpyObj('templateEl', JQLITE_METHODS); - mockTemplates[html].and.returnValue(mockElements[html]); - - return mockTemplates[html]; - }); - mockSce.trustAsResourceUrl.and.callFake(function (url) { - return { trusted: url }; - }); - mockScope.$new.and.returnValue(mockNewScope); - mockElement.html.and.callFake(function (html) { - mockContents[html] = - jasmine.createSpyObj('contentsEl', JQLITE_METHODS); - mockContents[html].testHtml = html; - }); - mockElement.contents.and.callFake(function () { - return mockContents[ - mockElement.html.calls.mostRecent().args[0] - ]; - }); - - linker = new TemplateLinker( - mockTemplateRequest, - mockSce, - mockCompile, - mockLog - ); - }); - - it("resolves extension paths", function () { - var testExt = testExtension('a', 'b', 'c/d.html'); - expect(linker.getPath(testExt)).toEqual('a/b/c/d.html'); - }); - - describe("when linking elements", function () { - var changeTemplate, - commentElement; - - function findCommentElement() { - mockCompile.calls.all().forEach(function (call) { - var html = call.args[0]; - if (html.indexOf(" -
      diff --git a/src/adapter/views/LegacyViewProvider.js b/src/adapter/views/LegacyViewProvider.js deleted file mode 100644 index 1a1714c76c..0000000000 --- a/src/adapter/views/LegacyViewProvider.js +++ /dev/null @@ -1,141 +0,0 @@ -define([ - -], function ( - -) { - const DEFAULT_VIEW_PRIORITY = 100; - - const PRIORITY_LEVELS = { - "fallback": Number.NEGATIVE_INFINITY, - "default": -100, - "none": 0, - "optional": DEFAULT_VIEW_PRIORITY, - "preferred": 1000, - "mandatory": Number.POSITIVE_INFINITY - }; - - function LegacyViewProvider(legacyView, openmct, convertToLegacyObject) { - return { - key: legacyView.key, - name: legacyView.name, - cssClass: legacyView.cssClass, - description: legacyView.description, - canEdit: function () { - return legacyView.editable === true; - }, - canView: function (domainObject) { - if (!domainObject || !domainObject.identifier) { - return false; - } - - if (legacyView.type) { - return domainObject.type === legacyView.type; - } - - let legacyObject = convertToLegacyObject(domainObject); - if (legacyView.needs) { - let meetsNeeds = legacyView.needs.every(k => legacyObject.hasCapability(k)); - if (!meetsNeeds) { - return false; - } - } - - return openmct.$injector.get('policyService').allow( - 'view', legacyView, legacyObject - ); - }, - view: function (domainObject) { - let $rootScope = openmct.$injector.get('$rootScope'); - let templateLinker = openmct.$injector.get('templateLinker'); - let scope = $rootScope.$new(true); - let legacyObject = convertToLegacyObject(domainObject); - let isDestroyed = false; - let unlistenToStatus; - let element; - scope.domainObject = legacyObject; - scope.model = legacyObject.getModel(); - let child; - let parent; - - return { - show: function (container) { - parent = container; - child = document.createElement('div'); - parent.appendChild(child); - let statusCapability = legacyObject.getCapability('status'); - unlistenToStatus = statusCapability.listen((newStatus) => { - child.classList.remove('s-status-timeconductor-unsynced'); - - if (newStatus.includes('timeconductor-unsynced')) { - child.classList.add('s-status-timeconductor-unsynced'); - } - }); - - // TODO: implement "gestures" support ? - let uses = legacyView.uses || []; - let promises = []; - let results = uses.map(function (capabilityKey, i) { - let result = legacyObject.useCapability(capabilityKey); - if (result.then) { - promises.push(result.then(function (r) { - results[i] = r; - })); - } - - return result; - }); - - function link() { - if (isDestroyed) { - return; - } - - uses.forEach(function (key, i) { - scope[key] = results[i]; - }); - element = openmct.$angular.element(child); - templateLinker.link( - scope, - element, - legacyView - ); - child.classList.add('u-contents'); - } - - if (promises.length) { - Promise.all(promises) - .then(function () { - link(); - scope.$digest(); - }); - } else { - link(); - } - }, - onClearData() { - scope.$broadcast('clearData'); - }, - destroy: function () { - element.off(); - element.remove(); - scope.$destroy(); - element = null; - scope = null; - unlistenToStatus(); - } - }; - }, - priority: function () { - let priority = legacyView.priority || DEFAULT_VIEW_PRIORITY; - if (typeof priority === 'string') { - priority = PRIORITY_LEVELS[priority]; - } - - return priority; - } - }; - } - - return LegacyViewProvider; - -}); diff --git a/src/adapter/views/TypeInspectorViewProvider.js b/src/adapter/views/TypeInspectorViewProvider.js deleted file mode 100644 index f7c3c6bd9c..0000000000 --- a/src/adapter/views/TypeInspectorViewProvider.js +++ /dev/null @@ -1,98 +0,0 @@ -define([ - -], function ( - -) { - function TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject) { - let representation = openmct.$injector.get('representations[]') - .filter((r) => r.key === typeDefinition.inspector)[0]; - - return { - key: representation.key, - name: representation.name, - cssClass: representation.cssClass, - description: representation.description, - canView: function (selection) { - if (selection.length !== 1 || selection[0].length === 0) { - return false; - } - - let selectionContext = selection[0][0].context; - - if (!selectionContext.item) { - return false; - } - - return selectionContext.item.type === typeDefinition.key; - }, - view: function (selection) { - let domainObject = selection[0][0].context.item; - let $rootScope = openmct.$injector.get('$rootScope'); - let templateLinker = openmct.$injector.get('templateLinker'); - let scope = $rootScope.$new(true); - let legacyObject = convertToLegacyObject(domainObject); - let isDestroyed = false; - let element; - scope.domainObject = legacyObject; - scope.model = legacyObject.getModel(); - - return { - show: function (container) { - let child = document.createElement('div'); - container.appendChild(child); - // TODO: implement "gestures" support ? - let uses = representation.uses || []; - let promises = []; - let results = uses.map(function (capabilityKey, i) { - let result = legacyObject.useCapability(capabilityKey); - if (result.then) { - promises.push(result.then(function (r) { - results[i] = r; - })); - } - - return result; - }); - - function link() { - if (isDestroyed) { - return; - } - - uses.forEach(function (key, i) { - scope[key] = results[i]; - }); - element = openmct.$angular.element(child); - templateLinker.link( - scope, - element, - representation - ); - container.style.height = '100%'; - } - - if (promises.length) { - Promise.all(promises) - .then(function () { - link(); - scope.$digest(); - }); - } else { - link(); - } - }, - destroy: function () { - element.off(); - element.remove(); - scope.$destroy(); - element = null; - scope = null; - } - }; - } - }; - } - - return TypeInspectorViewProvider; - -}); diff --git a/src/adapter/views/installLegacyViews.js b/src/adapter/views/installLegacyViews.js deleted file mode 100644 index 86419a09c9..0000000000 --- a/src/adapter/views/installLegacyViews.js +++ /dev/null @@ -1,32 +0,0 @@ -define([ - './LegacyViewProvider', - './TypeInspectorViewProvider', - 'objectUtils' -], function ( - LegacyViewProvider, - TypeInspectorViewProvider, - objectUtils -) { - function installLegacyViews(openmct, legacyViews, instantiate) { - - function convertToLegacyObject(domainObject) { - let keyString = objectUtils.makeKeyString(domainObject.identifier); - let oldModel = objectUtils.toOldFormat(domainObject); - - return instantiate(oldModel, keyString); - } - - legacyViews.forEach(function (legacyView) { - openmct.objectViews.addProvider(new LegacyViewProvider(legacyView, openmct, convertToLegacyObject)); - }); - - let inspectorTypes = openmct.$injector.get('types[]') - .filter((t) => Object.prototype.hasOwnProperty.call(t, 'inspector')); - - inspectorTypes.forEach(function (typeDefinition) { - openmct.inspectorViews.addProvider(new TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject)); - }); - } - - return installLegacyViews; -}); diff --git a/src/api/Branding.js b/src/api/Branding.js index 502db096a2..c448217415 100644 --- a/src/api/Branding.js +++ b/src/api/Branding.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * diff --git a/src/api/Editor.js b/src/api/Editor.js index 02c679a161..d919da3837 100644 --- a/src/api/Editor.js +++ b/src/api/Editor.js @@ -1,6 +1,6 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * diff --git a/src/api/actions/ActionCollection.js b/src/api/actions/ActionCollection.js index 6545dd4a05..6606616b01 100644 --- a/src/api/actions/ActionCollection.js +++ b/src/api/actions/ActionCollection.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -85,8 +85,6 @@ class ActionCollection extends EventEmitter { } destroy() { - super.removeAllListeners(); - if (!this.skipEnvironmentObservers) { this.objectUnsubscribes.forEach(unsubscribe => { unsubscribe(); @@ -96,6 +94,7 @@ class ActionCollection extends EventEmitter { } this.emit('destroy', this.view); + this.removeAllListeners(); } getVisibleActions() { diff --git a/src/api/actions/ActionCollectionSpec.js b/src/api/actions/ActionCollectionSpec.js index 99145b7673..3dbb133294 100644 --- a/src/api/actions/ActionCollectionSpec.js +++ b/src/api/actions/ActionCollectionSpec.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * diff --git a/src/api/actions/ActionsAPI.js b/src/api/actions/ActionsAPI.js index 8af7b3cbb1..c6a88dbd62 100644 --- a/src/api/actions/ActionsAPI.js +++ b/src/api/actions/ActionsAPI.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * diff --git a/src/api/actions/ActionsAPISpec.js b/src/api/actions/ActionsAPISpec.js index e293d6ba85..58759acb92 100644 --- a/src/api/actions/ActionsAPISpec.js +++ b/src/api/actions/ActionsAPISpec.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * diff --git a/src/api/annotation/AnnotationAPI.js b/src/api/annotation/AnnotationAPI.js new file mode 100644 index 0000000000..a197620389 --- /dev/null +++ b/src/api/annotation/AnnotationAPI.js @@ -0,0 +1,277 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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 { v4 as uuid } from 'uuid'; +import EventEmitter from 'EventEmitter'; + +/** + * @readonly + * @enum {String} AnnotationType + * @property {String} NOTEBOOK The notebook annotation type + * @property {String} GEOSPATIAL The geospatial annotation type + * @property {String} PIXEL_SPATIAL The pixel-spatial annotation type + * @property {String} TEMPORAL The temporal annotation type + * @property {String} PLOT_SPATIAL The plot-spatial annotation type + */ +const ANNOTATION_TYPES = Object.freeze({ + NOTEBOOK: 'NOTEBOOK', + GEOSPATIAL: 'GEOSPATIAL', + PIXEL_SPATIAL: 'PIXEL_SPATIAL', + TEMPORAL: 'TEMPORAL', + PLOT_SPATIAL: 'PLOT_SPATIAL' +}); + +/** + * @typedef {Object} Tag + * @property {String} key a unique identifier for the tag + * @property {String} backgroundColor eg. "#cc0000" + * @property {String} foregroundColor eg. "#ffffff" + */ +export default class AnnotationAPI extends EventEmitter { + constructor(openmct) { + super(); + this.openmct = openmct; + this.availableTags = {}; + + this.ANNOTATION_TYPES = ANNOTATION_TYPES; + + this.openmct.types.addType('annotation', { + name: 'Annotation', + description: 'A user created note or comment about time ranges, pixel space, and geospatial features.', + creatable: false, + cssClass: 'icon-notebook', + initialize: function (domainObject) { + domainObject.targets = domainObject.targets || {}; + domainObject.originalContextPath = domainObject.originalContextPath || ''; + domainObject.tags = domainObject.tags || []; + domainObject.contentText = domainObject.contentText || ''; + domainObject.annotationType = domainObject.annotationType || 'plotspatial'; + } + }); + } + + /** + * Create the a generic annotation + * @typedef {Object} CreateAnnotationOptions + * @property {String} name a name for the new parameter + * @property {import('../objects/ObjectAPI').DomainObject} domainObject the domain object to create + * @property {ANNOTATION_TYPES} annotationType the type of annotation to create + * @property {Tag[]} tags + * @property {String} contentText + * @property {import('../objects/ObjectAPI').Identifier[]} targets + */ + /** + * @method create + * @param {CreateAnnotationOptions} options + * @returns {Promise} a promise which will resolve when the domain object + * has been created, or be rejected if it cannot be saved + */ + async create({name, domainObject, annotationType, tags, contentText, targets}) { + if (!Object.keys(this.ANNOTATION_TYPES).includes(annotationType)) { + throw new Error(`Unknown annotation type: ${annotationType}`); + } + + if (!Object.keys(targets).length) { + throw new Error(`At least one target is required to create an annotation`); + } + + const domainObjectKeyString = this.openmct.objects.makeKeyString(domainObject.identifier); + const originalPathObjects = await this.openmct.objects.getOriginalPath(domainObjectKeyString); + const originalContextPath = this.openmct.objects.getRelativePath(originalPathObjects); + const namespace = domainObject.identifier.namespace; + const type = 'annotation'; + const typeDefinition = this.openmct.types.get(type); + const definition = typeDefinition.definition; + + const createdObject = { + name, + type, + identifier: { + key: uuid(), + namespace + }, + tags, + annotationType, + contentText, + originalContextPath + }; + + if (definition.initialize) { + definition.initialize(createdObject); + } + + createdObject.targets = targets; + createdObject.originalContextPath = originalContextPath; + + const success = await this.openmct.objects.save(createdObject); + if (success) { + this.emit('annotationCreated', createdObject); + + return createdObject; + } else { + throw new Error('Failed to create object'); + } + } + + defineTag(tagKey, tagsDefinition) { + this.availableTags[tagKey] = tagsDefinition; + } + + getAvailableTags() { + if (this.availableTags) { + const rearrangedToArray = Object.keys(this.availableTags).map(tagKey => { + return { + id: tagKey, + ...this.availableTags[tagKey] + }; + }); + + return rearrangedToArray; + } else { + return []; + } + } + + async getAnnotation(query, searchType) { + let foundAnnotation = null; + + const searchResults = (await Promise.all(this.openmct.objects.search(query, null, searchType))).flat(); + if (searchResults) { + foundAnnotation = searchResults[0]; + } + + return foundAnnotation; + } + + async addAnnotationTag(existingAnnotation, targetDomainObject, targetSpecificDetails, annotationType, tag) { + if (!existingAnnotation) { + const targets = {}; + const targetKeyString = this.openmct.objects.makeKeyString(targetDomainObject.identifier); + targets[targetKeyString] = targetSpecificDetails; + const contentText = `${annotationType} tag`; + const annotationCreationArguments = { + name: contentText, + domainObject: targetDomainObject, + annotationType, + tags: [tag], + contentText, + targets + }; + const newAnnotation = await this.create(annotationCreationArguments); + + return newAnnotation; + } else { + const tagArray = [tag, ...existingAnnotation.tags]; + this.openmct.objects.mutate(existingAnnotation, 'tags', tagArray); + + return existingAnnotation; + } + } + + removeAnnotationTag(existingAnnotation, tagToRemove) { + if (existingAnnotation && existingAnnotation.tags.includes(tagToRemove)) { + const cleanedArray = existingAnnotation.tags.filter(extantTag => extantTag !== tagToRemove); + this.openmct.objects.mutate(existingAnnotation, 'tags', cleanedArray); + } else { + throw new Error(`Asked to remove tag (${tagToRemove}) that doesn't exist`, existingAnnotation); + } + } + + removeAnnotationTags(existingAnnotation) { + // just removes tags on the annotation as we can't really delete objects + if (existingAnnotation && existingAnnotation.tags) { + this.openmct.objects.mutate(existingAnnotation, 'tags', []); + } + } + + #getMatchingTags(query) { + if (!query) { + return []; + } + + const matchingTags = Object.keys(this.availableTags).filter(tagKey => { + if (this.availableTags[tagKey] && this.availableTags[tagKey].label) { + return this.availableTags[tagKey].label.toLowerCase().includes(query.toLowerCase()); + } + + return false; + }); + + return matchingTags; + } + + #addTagMetaInformationToResults(results, matchingTagKeys) { + const tagsAddedToResults = results.map(result => { + const fullTagModels = result.tags.map(tagKey => { + const tagModel = this.availableTags[tagKey]; + tagModel.tagID = tagKey; + + return tagModel; + }); + + return { + fullTagModels, + matchingTagKeys, + ...result + }; + }); + + return tagsAddedToResults; + } + + async #addTargetModelsToResults(results) { + const modelAddedToResults = await Promise.all(results.map(async result => { + const targetModels = await Promise.all(Object.keys(result.targets).map(async (targetID) => { + const targetModel = await this.openmct.objects.get(targetID); + const targetKeyString = this.openmct.objects.makeKeyString(targetModel.identifier); + const originalPathObjects = await this.openmct.objects.getOriginalPath(targetKeyString); + + return { + originalPath: originalPathObjects, + ...targetModel + }; + })); + + return { + targetModels, + ...result + }; + })); + + return modelAddedToResults; + } + + /** + * @method searchForTags + * @param {String} query A query to match against tags. E.g., "dr" will match the tags "drilling" and "driving" + * @param {Object} abortController An optional abort method to stop the query + * @returns {Promise} returns a model of matching tags with their target domain objects attached + */ + async searchForTags(query, abortController) { + const matchingTagKeys = this.#getMatchingTags(query); + const searchResults = (await Promise.all(this.openmct.objects.search(matchingTagKeys, abortController, this.openmct.objects.SEARCH_TYPES.TAGS))).flat(); + const appliedTagSearchResults = this.#addTagMetaInformationToResults(searchResults, matchingTagKeys); + const appliedTargetsModels = await this.#addTargetModelsToResults(appliedTagSearchResults); + + return appliedTargetsModels; + } +} diff --git a/src/api/annotation/AnnotationAPISpec.js b/src/api/annotation/AnnotationAPISpec.js new file mode 100644 index 0000000000..731aead3cc --- /dev/null +++ b/src/api/annotation/AnnotationAPISpec.js @@ -0,0 +1,176 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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 { createOpenMct, resetApplicationState } from '../../utils/testing'; +import ExampleTagsPlugin from "../../../example/exampleTags/plugin"; + +describe("The Annotation API", () => { + let openmct; + let mockObjectProvider; + let mockDomainObject; + let mockAnnotationObject; + + beforeEach((done) => { + openmct = createOpenMct(); + openmct.install(new ExampleTagsPlugin()); + const availableTags = openmct.annotation.getAvailableTags(); + mockDomainObject = { + type: 'notebook', + name: 'fooRabbitNotebook', + identifier: { + key: 'some-object', + namespace: 'fooNameSpace' + } + }; + mockAnnotationObject = { + type: 'annotation', + name: 'Some Notebook Annotation', + annotationType: openmct.annotation.ANNOTATION_TYPES.NOTEBOOK, + tags: [availableTags[0].id, availableTags[1].id], + identifier: { + key: 'anAnnotationKey', + namespace: 'fooNameSpace' + }, + targets: { + 'fooNameSpace:some-object': { + entryId: 'fooBarEntry' + } + } + }; + + mockObjectProvider = jasmine.createSpyObj("mock provider", [ + "create", + "update", + "get" + ]); + // eslint-disable-next-line require-await + mockObjectProvider.get = async (identifier) => { + if (identifier.key === mockDomainObject.identifier.key) { + return mockDomainObject; + } else if (identifier.key === mockAnnotationObject.identifier.key) { + return mockAnnotationObject; + } else { + return null; + } + }; + + mockObjectProvider.create.and.returnValue(Promise.resolve(true)); + mockObjectProvider.update.and.returnValue(Promise.resolve(true)); + + openmct.objects.addProvider('fooNameSpace', mockObjectProvider); + openmct.on('start', done); + openmct.startHeadless(); + }); + afterEach(async () => { + openmct.objects.providers = {}; + await resetApplicationState(openmct); + }); + it("is defined", () => { + expect(openmct.annotation).toBeDefined(); + }); + + describe("Creation", () => { + it("can create annotations", async () => { + const annotationCreationArguments = { + name: 'Test Annotation', + domainObject: mockDomainObject, + annotationType: openmct.annotation.ANNOTATION_TYPES.NOTEBOOK, + tags: ['sometag'], + contentText: "fooContext", + targets: {'fooTarget': {}} + }; + const annotationObject = await openmct.annotation.create(annotationCreationArguments); + expect(annotationObject).toBeDefined(); + expect(annotationObject.type).toEqual('annotation'); + }); + it("fails if annotation is an unknown type", async () => { + try { + await openmct.annotation.create('Garbage Annotation', mockDomainObject, 'garbageAnnotation', ['sometag'], "fooContext", {'fooTarget': {}}); + } catch (error) { + expect(error).toBeDefined(); + } + }); + }); + + describe("Tagging", () => { + it("can create a tag", async () => { + const annotationObject = await openmct.annotation.addAnnotationTag(null, mockDomainObject, {entryId: 'foo'}, openmct.annotation.ANNOTATION_TYPES.NOTEBOOK, 'aWonderfulTag'); + expect(annotationObject).toBeDefined(); + expect(annotationObject.type).toEqual('annotation'); + expect(annotationObject.tags).toContain('aWonderfulTag'); + }); + it("can delete a tag", async () => { + const originalAnnotationObject = await openmct.annotation.addAnnotationTag(null, mockDomainObject, {entryId: 'foo'}, openmct.annotation.ANNOTATION_TYPES.NOTEBOOK, 'aWonderfulTag'); + const annotationObject = await openmct.annotation.addAnnotationTag(originalAnnotationObject, mockDomainObject, {entryId: 'foo'}, openmct.annotation.ANNOTATION_TYPES.NOTEBOOK, 'anotherTagToRemove'); + expect(annotationObject).toBeDefined(); + openmct.annotation.removeAnnotationTag(annotationObject, 'anotherTagToRemove'); + expect(annotationObject.tags).toEqual(['aWonderfulTag']); + openmct.annotation.removeAnnotationTag(annotationObject, 'aWonderfulTag'); + expect(annotationObject.tags).toEqual([]); + }); + it("throws an error if deleting non-existent tag", async () => { + const annotationObject = await openmct.annotation.addAnnotationTag(null, mockDomainObject, {entryId: 'foo'}, openmct.annotation.ANNOTATION_TYPES.NOTEBOOK, 'aWonderfulTag'); + expect(annotationObject).toBeDefined(); + expect(() => { + openmct.annotation.removeAnnotationTag(annotationObject, 'ThisTagShouldNotExist'); + }).toThrow(); + }); + it("can remove all tags", async () => { + const annotationObject = await openmct.annotation.addAnnotationTag(null, mockDomainObject, {entryId: 'foo'}, openmct.annotation.ANNOTATION_TYPES.NOTEBOOK, 'aWonderfulTag'); + expect(annotationObject).toBeDefined(); + expect(() => { + openmct.annotation.removeAnnotationTags(annotationObject); + }).not.toThrow(); + expect(annotationObject.tags).toEqual([]); + }); + }); + + describe("Search", () => { + let sharedWorkerToRestore; + beforeEach(async () => { + // use local worker + sharedWorkerToRestore = openmct.objects.inMemorySearchProvider.worker; + openmct.objects.inMemorySearchProvider.worker = null; + await openmct.objects.inMemorySearchProvider.index(mockDomainObject); + await openmct.objects.inMemorySearchProvider.index(mockAnnotationObject); + }); + afterEach(() => { + openmct.objects.inMemorySearchProvider.worker = sharedWorkerToRestore; + }); + it("can search for tags", async () => { + const results = await openmct.annotation.searchForTags('S'); + expect(results).toBeDefined(); + expect(results.length).toEqual(1); + }); + it("can get notebook annotations", async () => { + const targetKeyString = openmct.objects.makeKeyString(mockDomainObject.identifier); + const query = { + targetKeyString, + entryId: 'fooBarEntry' + }; + + const results = await openmct.annotation.getAnnotation(query, openmct.objects.SEARCH_TYPES.NOTEBOOK_ANNOTATIONS); + expect(results).toBeDefined(); + expect(results.tags.length).toEqual(2); + }); + }); +}); diff --git a/src/api/api.js b/src/api/api.js index b6e4af91f6..505213476a 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -24,6 +24,7 @@ define([ './actions/ActionsAPI', './composition/CompositionAPI', './Editor', + './faultmanagement/FaultManagementAPI', './forms/FormsAPI', './indicators/IndicatorAPI', './menu/MenuAPI', @@ -33,11 +34,14 @@ define([ './status/StatusAPI', './telemetry/TelemetryAPI', './time/TimeAPI', - './types/TypeRegistry' + './types/TypeRegistry', + './user/UserAPI', + './annotation/AnnotationAPI' ], function ( ActionsAPI, CompositionAPI, EditorAPI, + FaultManagementAPI, FormsAPI, IndicatorAPI, MenuAPI, @@ -47,14 +51,17 @@ define([ StatusAPI, TelemetryAPI, TimeAPI, - TypeRegistry + TypeRegistry, + UserAPI, + AnnotationAPI ) { return { ActionsAPI: ActionsAPI.default, CompositionAPI: CompositionAPI, EditorAPI: EditorAPI, + FaultManagementAPI: FaultManagementAPI, FormsAPI: FormsAPI, - IndicatorAPI: IndicatorAPI, + IndicatorAPI: IndicatorAPI.default, MenuAPI: MenuAPI.default, NotificationAPI: NotificationAPI.default, ObjectAPI: ObjectAPI, @@ -62,6 +69,8 @@ define([ StatusAPI: StatusAPI.default, TelemetryAPI: TelemetryAPI, TimeAPI: TimeAPI.default, - TypeRegistry: TypeRegistry + TypeRegistry: TypeRegistry, + UserAPI: UserAPI.default, + AnnotationAPI: AnnotationAPI.default }; }); diff --git a/src/api/composition/CompositionAPI.js b/src/api/composition/CompositionAPI.js index 8e23faa81d..02dfb0315e 100644 --- a/src/api/composition/CompositionAPI.js +++ b/src/api/composition/CompositionAPI.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * diff --git a/src/api/composition/CompositionCollection.js b/src/api/composition/CompositionCollection.js index 5a013fbe30..e04bbe888a 100644 --- a/src/api/composition/CompositionCollection.js +++ b/src/api/composition/CompositionCollection.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * diff --git a/src/api/composition/DefaultCompositionProvider.js b/src/api/composition/DefaultCompositionProvider.js index 99a60cf51b..3a46b127fb 100644 --- a/src/api/composition/DefaultCompositionProvider.js +++ b/src/api/composition/DefaultCompositionProvider.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * diff --git a/src/api/faultmanagement/FaultManagementAPI.js b/src/api/faultmanagement/FaultManagementAPI.js new file mode 100644 index 0000000000..b22a85c093 --- /dev/null +++ b/src/api/faultmanagement/FaultManagementAPI.js @@ -0,0 +1,106 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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 FaultManagementAPI { + constructor(openmct) { + this.openmct = openmct; + } + + addProvider(provider) { + this.provider = provider; + } + + supportsActions() { + return this.provider?.acknowledgeFault !== undefined && this.provider?.shelveFault !== undefined; + } + + request(domainObject) { + if (!this.provider?.supportsRequest(domainObject)) { + return Promise.reject(); + } + + return this.provider.request(domainObject); + } + + subscribe(domainObject, callback) { + if (!this.provider?.supportsSubscribe(domainObject)) { + return Promise.reject(); + } + + return this.provider.subscribe(domainObject, callback); + } + + acknowledgeFault(fault, ackData) { + return this.provider.acknowledgeFault(fault, ackData); + } + + shelveFault(fault, shelveData) { + return this.provider.shelveFault(fault, shelveData); + } +} + +/** @typedef {object} Fault + * @property {string} type + * @property {object} fault + * @property {boolean} fault.acknowledged + * @property {object} fault.currentValueInfo + * @property {number} fault.currentValueInfo.value + * @property {string} fault.currentValueInfo.rangeCondition + * @property {string} fault.currentValueInfo.monitoringResult + * @property {string} fault.id + * @property {string} fault.name + * @property {string} fault.namespace + * @property {number} fault.seqNum + * @property {string} fault.severity + * @property {boolean} fault.shelved + * @property {string} fault.shortDescription + * @property {string} fault.triggerTime + * @property {object} fault.triggerValueInfo + * @property {number} fault.triggerValueInfo.value + * @property {string} fault.triggerValueInfo.rangeCondition + * @property {string} fault.triggerValueInfo.monitoringResult + * @example + * { + * "type": "", + * "fault": { + * "acknowledged": true, + * "currentValueInfo": { + * "value": 0, + * "rangeCondition": "", + * "monitoringResult": "" + * }, + * "id": "", + * "name": "", + * "namespace": "", + * "seqNum": 0, + * "severity": "", + * "shelved": true, + * "shortDescription": "", + * "triggerTime": "", + * "triggerValueInfo": { + * "value": 0, + * "rangeCondition": "", + * "monitoringResult": "" + * } + * } + * } + */ diff --git a/src/api/faultmanagement/FaultManagementAPISpec.js b/src/api/faultmanagement/FaultManagementAPISpec.js new file mode 100644 index 0000000000..61c87402e7 --- /dev/null +++ b/src/api/faultmanagement/FaultManagementAPISpec.js @@ -0,0 +1,144 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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 { + createOpenMct, + resetApplicationState +} from '../../utils/testing'; + +const faultName = 'super duper fault'; +const aFault = { + type: '', + fault: { + acknowledged: true, + currentValueInfo: { + value: 0, + rangeCondition: '', + monitoringResult: '' + }, + id: '', + name: faultName, + namespace: '', + seqNum: 0, + severity: '', + shelved: true, + shortDescription: '', + triggerTime: '', + triggerValueInfo: { + value: 0, + rangeCondition: '', + monitoringResult: '' + } + } +}; +const faultDomainObject = { + name: 'it is not your fault', + type: 'faultManagement', + identifier: { + key: 'nobodies', + namespace: 'fault' + } +}; +const aComment = 'THIS is my fault.'; +const faultManagementProvider = { + request() { + return Promise.resolve([aFault]); + }, + subscribe(domainObject, callback) { + return () => {}; + }, + supportsRequest(domainObject) { + return domainObject.type === 'faultManagement'; + }, + supportsSubscribe(domainObject) { + return domainObject.type === 'faultManagement'; + }, + acknowledgeFault(fault, { comment = '' }) { + return Promise.resolve({ + success: true + }); + }, + shelveFault(fault, shelveData) { + return Promise.resolve({ + success: true + }); + } +}; + +describe('The Fault Management API', () => { + let openmct; + + beforeEach(() => { + openmct = createOpenMct(); + openmct.install(openmct.plugins.FaultManagement()); + // openmct.install(openmct.plugins.example.ExampleFaultSource()); + openmct.faults.addProvider(faultManagementProvider); + }); + + afterEach(() => { + return resetApplicationState(openmct); + }); + + it('allows you to request a fault', async () => { + spyOn(faultManagementProvider, 'supportsRequest').and.callThrough(); + + let faultResponse = await openmct.faults.request(faultDomainObject); + + expect(faultManagementProvider.supportsRequest).toHaveBeenCalledWith(faultDomainObject); + expect(faultResponse[0].fault.name).toEqual(faultName); + }); + + it('allows you to subscribe to a fault', () => { + spyOn(faultManagementProvider, 'subscribe').and.callThrough(); + spyOn(faultManagementProvider, 'supportsSubscribe').and.callThrough(); + + let unsubscribe = openmct.faults.subscribe(faultDomainObject, () => {}); + + expect(unsubscribe).toEqual(jasmine.any(Function)); + expect(faultManagementProvider.supportsSubscribe).toHaveBeenCalledWith(faultDomainObject); + expect(faultManagementProvider.subscribe).toHaveBeenCalledOnceWith(faultDomainObject, jasmine.any(Function)); + + }); + + it('will tell you if the fault management provider supports actions', () => { + expect(openmct.faults.supportsActions()).toBeTrue(); + }); + + it('will allow you to acknowledge a fault', async () => { + spyOn(faultManagementProvider, 'acknowledgeFault').and.callThrough(); + + let ackResponse = await openmct.faults.acknowledgeFault(aFault, aComment); + + expect(faultManagementProvider.acknowledgeFault).toHaveBeenCalledWith(aFault, aComment); + expect(ackResponse.success).toBeTrue(); + }); + + it('will allow you to shelve a fault', async () => { + spyOn(faultManagementProvider, 'shelveFault').and.callThrough(); + + let shelveResponse = await openmct.faults.shelveFault(aFault, aComment); + + expect(faultManagementProvider.shelveFault).toHaveBeenCalledWith(aFault, aComment); + expect(shelveResponse.success).toBeTrue(); + }); + +}); diff --git a/src/api/forms/FormController.js b/src/api/forms/FormController.js index 93f87b9dcf..68e195f4eb 100644 --- a/src/api/forms/FormController.js +++ b/src/api/forms/FormController.js @@ -8,6 +8,7 @@ import NumberField from './components/controls/NumberField.vue'; import SelectField from './components/controls/SelectField.vue'; import TextAreaField from './components/controls/TextAreaField.vue'; import TextField from './components/controls/TextField.vue'; +import ToggleSwitchField from './components/controls/ToggleSwitchField.vue'; import Vue from 'vue'; @@ -21,7 +22,8 @@ export const DEFAULT_CONTROLS_MAP = { 'numberfield': NumberField, 'select': SelectField, 'textarea': TextAreaField, - 'textfield': TextField + 'textfield': TextField, + 'toggleSwitch': ToggleSwitchField }; export default class FormControl { @@ -67,10 +69,11 @@ export default class FormControl { */ _getControlViewProvider(control) { const self = this; + let rowComponent; return { show(element, model, onChange) { - const rowComponent = new Vue({ + rowComponent = new Vue({ el: element, components: { FormControlComponent: DEFAULT_CONTROLS_MAP[control] @@ -88,8 +91,10 @@ export default class FormControl { }); return rowComponent; + }, + destroy() { + rowComponent.$destroy(); } }; } } - diff --git a/src/api/forms/FormsAPI.js b/src/api/forms/FormsAPI.js index e6cf746335..4399793254 100644 --- a/src/api/forms/FormsAPI.js +++ b/src/api/forms/FormsAPI.js @@ -1,5 +1,5 @@ /***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government + * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * @@ -23,10 +23,13 @@ import FormController from './FormController'; import FormProperties from './components/FormProperties.vue'; +import EventEmitter from 'EventEmitter'; import Vue from 'vue'; -export default class FormsAPI { +export default class FormsAPI extends EventEmitter { constructor(openmct) { + super(); + this.openmct = openmct; this.formController = new FormController(openmct); } @@ -107,6 +110,8 @@ export default class FormsAPI { let onDismiss; let onSave; + const self = this; + const promise = new Promise((resolve, reject) => { onSave = onFormSave(resolve); onDismiss = onFormDismiss(reject); @@ -115,7 +120,7 @@ export default class FormsAPI { const vm = new Vue({ components: { FormProperties }, provide: { - openmct: this.openmct + openmct: self.openmct }, data() { return { @@ -132,7 +137,7 @@ export default class FormsAPI { if (element) { element.append(formElement); } else { - overlay = this.openmct.overlays.overlay({ + overlay = self.openmct.overlays.overlay({ element: vm.$el, size: 'small', onDestroy: () => vm.$destroy() @@ -140,6 +145,7 @@ export default class FormsAPI { } function onFormPropertyChange(data) { + self.emit('onFormPropertyChange', data); if (onChange) { onChange(data); } diff --git a/src/api/forms/FormsAPISpec.js b/src/api/forms/FormsAPISpec.js new file mode 100644 index 0000000000..ac7f0fc9fb --- /dev/null +++ b/src/api/forms/FormsAPISpec.js @@ -0,0 +1,157 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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 { createOpenMct, resetApplicationState } from '../../utils/testing'; + +describe('The Forms API', () => { + let openmct; + let element; + + beforeEach((done) => { + element = document.createElement('div'); + element.style.display = 'block'; + element.style.width = '1920px'; + element.style.height = '1080px'; + + openmct = createOpenMct(); + openmct.on('start', done); + + openmct.startHeadless(element); + }); + + afterEach(() => { + return resetApplicationState(openmct); + }); + + it('openmct supports form API', () => { + expect(openmct.forms).not.toBe(null); + }); + + describe('check default form controls exists', () => { + it('autocomplete', () => { + const control = openmct.forms.getFormControl('autocomplete'); + expect(control).not.toBe(null); + }); + + it('clock', () => { + const control = openmct.forms.getFormControl('composite'); + expect(control).not.toBe(null); + }); + + it('datetime', () => { + const control = openmct.forms.getFormControl('datetime'); + expect(control).not.toBe(null); + }); + + it('file-input', () => { + const control = openmct.forms.getFormControl('file-input'); + expect(control).not.toBe(null); + }); + + it('locator', () => { + const control = openmct.forms.getFormControl('locator'); + expect(control).not.toBe(null); + }); + + it('numberfield', () => { + const control = openmct.forms.getFormControl('numberfield'); + expect(control).not.toBe(null); + }); + + it('select', () => { + const control = openmct.forms.getFormControl('select'); + expect(control).not.toBe(null); + }); + + it('textarea', () => { + const control = openmct.forms.getFormControl('textarea'); + expect(control).not.toBe(null); + }); + + it('textfield', () => { + const control = openmct.forms.getFormControl('textfield'); + expect(control).not.toBe(null); + }); + }); + + it('supports user defined form controls', () => { + const newFormControl = { + show: () => { + console.log('show new control'); + }, + destroy: () => { + console.log('destroy'); + } + }; + openmct.forms.addNewFormControl('newFormControl', newFormControl); + const control = openmct.forms.getFormControl('newFormControl'); + expect(control).not.toBe(null); + expect(control.show).not.toBe(null); + expect(control.destroy).not.toBe(null); + }); + + describe('show form on UI', () => { + let formStructure; + + beforeEach(() => { + formStructure = { + title: 'Test Show Form', + sections: [ + { + rows: [ + { + key: 'name', + control: 'textfield', + name: 'Title', + pattern: '\\S+', + required: false, + cssClass: 'l-input-lg', + value: 'Test Name' + } + ] + } + ] + }; + }); + + it('when container element is provided', (done) => { + openmct.forms.showForm(formStructure, { element }).catch(() => { + done(); + }); + const titleElement = element.querySelector('.c-overlay__dialog-title'); + expect(titleElement.textContent).toBe(formStructure.title); + + element.querySelector('.js-cancel-button').click(); + }); + + it('when container element is not provided', (done) => { + openmct.forms.showForm(formStructure).catch(() => { + done(); + }); + + const titleElement = document.querySelector('.c-overlay__dialog-title'); + const title = titleElement.textContent; + + expect(title).toBe(formStructure.title); + document.querySelector('.js-cancel-button').click(); + }); + }); +}); diff --git a/src/api/forms/components/FormProperties.vue b/src/api/forms/components/FormProperties.vue index 090a2c8ca0..5cf322c180 100644 --- a/src/api/forms/components/FormProperties.vue +++ b/src/api/forms/components/FormProperties.vue @@ -21,9 +21,9 @@ *****************************************************************************/