├── .browserslistrc ├── .codecov.yml ├── .eslintrc.cjs ├── .github ├── dependabot.yml └── workflows │ ├── auto_publish_release.yml │ ├── create_release_pr.yml │ ├── main.yml │ ├── renovate_validate.yml │ ├── shortlog.yml │ └── update_copyright.yml ├── .gitignore ├── .mailmap ├── .nycrc ├── .yarn └── releases │ ├── yarn-3.7.0.cjs │ └── yarn-4.9.2.cjs ├── .yarnrc.yml ├── CHANGES.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── changes.d ├── 2017.feat.md ├── 2025.feat.md ├── 2135.feat.md ├── 2163.fix.md └── template.jinja ├── cypress.config.cjs ├── docs └── misc │ └── google-analytics.md ├── eslintrc-dist.cjs ├── etc └── uid.py ├── index.html ├── jsconfig.json ├── jsdoc.conf.json ├── package.json ├── public ├── favicon.png ├── fonts.css ├── fonts │ ├── roboto-v20-latin-100.woff │ ├── roboto-v20-latin-100italic.woff │ ├── roboto-v20-latin-300.woff │ ├── roboto-v20-latin-300italic.woff │ ├── roboto-v20-latin-500.woff │ ├── roboto-v20-latin-500italic.woff │ ├── roboto-v20-latin-700.woff │ ├── roboto-v20-latin-700italic.woff │ ├── roboto-v20-latin-900.woff │ ├── roboto-v20-latin-900italic.woff │ ├── roboto-v20-latin-italic.woff │ └── roboto-v20-latin-regular.woff └── img │ └── logo.svg ├── pyproject.toml ├── renovate.json ├── scripts └── concurrently.cjs ├── src ├── App.vue ├── components │ ├── Markdown.vue │ ├── README.md │ ├── core │ │ ├── Alert.vue │ │ └── CopyBtn.vue │ ├── cylc │ │ ├── ConnectionStatus.vue │ │ ├── Drawer.vue │ │ ├── GraphNode.vue │ │ ├── GraphSubgraph.vue │ │ ├── Header.vue │ │ ├── Info.vue │ │ ├── Job.vue │ │ ├── Mutation.vue │ │ ├── SVGTask.vue │ │ ├── Task.vue │ │ ├── TaskFilter.vue │ │ ├── TaskFilterSelect.vue │ │ ├── Toolbar.vue │ │ ├── ViewToolbar.vue │ │ ├── WarningIcon.vue │ │ ├── analysis │ │ │ ├── AnalysisTable.vue │ │ │ ├── BoxPlot.vue │ │ │ ├── TimeSeries.vue │ │ │ └── filter.js │ │ ├── commandMenu │ │ │ ├── Menu.vue │ │ │ └── plugin.js │ │ ├── common │ │ │ ├── FlowNumsChip.vue │ │ │ ├── filter.js │ │ │ └── sort.js │ │ ├── gantt │ │ │ ├── GanttChart.vue │ │ │ └── filter.js │ │ ├── gscan │ │ │ ├── GScan.vue │ │ │ ├── WorkflowIcon.vue │ │ │ ├── filters.js │ │ │ └── sort.js │ │ ├── log │ │ │ └── Log.vue │ │ ├── table │ │ │ ├── Table.vue │ │ │ └── sort.js │ │ ├── tree │ │ │ ├── GScanTreeItem.vue │ │ │ ├── JobDetails.vue │ │ │ ├── Tree.vue │ │ │ ├── TreeItem.vue │ │ │ └── util.js │ │ └── workspace │ │ │ ├── Lumino.vue │ │ │ ├── Toolbar.vue │ │ │ ├── Widget.vue │ │ │ └── lumino-widget.js │ └── graphqlFormGenerator │ │ ├── EditRuntimeForm.vue │ │ ├── FormGenerator.vue │ │ ├── FormInput.vue │ │ ├── components │ │ ├── BroadcastSetting.vue │ │ ├── Enum.vue │ │ ├── List.vue │ │ ├── MapItem.vue │ │ ├── NonNull.vue │ │ ├── Object.vue │ │ └── vuetify.js │ │ └── mixins.js ├── composables │ └── localStorage.js ├── graphql │ ├── graphiql.js │ ├── index.js │ └── merge.js ├── i18n │ ├── index.js │ └── languages.json ├── lang │ ├── en-GB │ │ ├── App.json │ │ ├── NotFound.json │ │ ├── Toolbar.json │ │ ├── UserProfile.json │ │ └── Workflows.json │ ├── index.js │ └── pt-BR │ │ ├── App.json │ │ ├── NotFound.json │ │ ├── Toolbar.json │ │ ├── UserProfile.json │ │ └── Workflows.json ├── layouts │ ├── Default.vue │ ├── Empty.vue │ └── README.md ├── main.js ├── mixins │ ├── README.md │ ├── graphql.js │ ├── subscription.js │ └── subscriptionComponent.js ├── model │ ├── Alert.model.js │ ├── JobState.model.js │ ├── Subscription.model.js │ ├── SubscriptionQuery.model.js │ ├── TaskOutput.model.js │ ├── TaskState.model.js │ ├── User.model.js │ ├── ViewState.model.js │ └── WorkflowState.model.js ├── plugins │ └── vuetify.js ├── router │ ├── index.js │ └── paths.js ├── services │ ├── callbacks.js │ ├── eventBus.js │ ├── mock │ │ ├── .eslintrc.cjs │ │ ├── checkpoint │ │ │ └── get_checkpoint.py │ │ ├── generate │ │ ├── graphql.cjs │ │ ├── json-server.cjs │ │ ├── json │ │ │ ├── IntrospectionQuery.json │ │ │ ├── README.md │ │ │ ├── analysisQuery.json │ │ │ ├── familyProxy.json │ │ │ ├── ganttQuery.json │ │ │ ├── index.cjs │ │ │ ├── infoView.json │ │ │ ├── logData.cjs │ │ │ ├── logFiles.cjs │ │ │ ├── taskProxy.json │ │ │ ├── userprofile.json │ │ │ ├── util.cjs │ │ │ ├── version.json │ │ │ └── workflows │ │ │ │ ├── index.cjs │ │ │ │ ├── multi.json │ │ │ │ └── one.json │ │ ├── one │ │ │ └── flow.cylc │ │ └── websockets.cjs │ ├── plugin.js │ ├── treeCallback.js │ ├── user.service.js │ └── workflow.service.js ├── store │ ├── README.md │ ├── app.module.js │ ├── index.js │ ├── options.js │ ├── user.module.js │ └── workflows.module.js ├── styles │ ├── _util.scss │ ├── cylc │ │ ├── _dashboard.scss │ │ ├── _drawer.scss │ │ ├── _gscan.scss │ │ ├── _header.scss │ │ ├── _job.scss │ │ ├── _markdown.scss │ │ ├── _menu.scss │ │ ├── _mutation_form.scss │ │ ├── _table.scss │ │ ├── _toolbar.scss │ │ ├── _tooltip.scss │ │ ├── _tree.scss │ │ ├── _user_profile.scss │ │ ├── _variables.scss │ │ ├── _warning.scss │ │ └── _workflow.scss │ ├── index.scss │ └── settings.scss ├── utils │ ├── aotf.js │ ├── font-size.js │ ├── graph-utils.js │ ├── icons.js │ ├── index.js │ ├── initialOptions.js │ ├── outputs.js │ ├── tasks.js │ ├── toolbar.js │ ├── uid.js │ ├── urls.js │ └── viewToolbar.js └── views │ ├── Analysis.vue │ ├── Dashboard.vue │ ├── Gantt.vue │ ├── Graph.vue │ ├── GraphiQL.vue │ ├── Guide.vue │ ├── Info.vue │ ├── Log.vue │ ├── NoAuth.vue │ ├── NotFound.vue │ ├── README.md │ ├── SimpleTree.vue │ ├── Table.vue │ ├── Tree.vue │ ├── UserProfile.vue │ ├── Workflows.vue │ ├── WorkflowsTable.vue │ ├── Workspace.vue │ └── views.js ├── tests ├── .eslintrc.cjs ├── component │ ├── .eslintrc.cjs │ ├── fixtures │ │ └── example.json │ ├── specs │ │ ├── boxPlot.cy.js │ │ ├── copyBtn.cy.js │ │ ├── cylc-graph-node.cy.js │ │ ├── cylc-icons.cy.js │ │ ├── ganttchart.cy.js │ │ ├── info.cy.js │ │ ├── log.cy.js │ │ ├── treeItem.cy.js │ │ ├── utils │ │ │ └── task.js │ │ └── viewToolbar.cy.js │ └── support │ │ ├── commands.js │ │ ├── component-index.html │ │ └── index.js ├── e2e │ ├── .eslintrc.cjs │ ├── specs │ │ ├── alert.cy.js │ │ ├── analysis.cy.js │ │ ├── aotf.cy.js │ │ ├── dashboard.cy.js │ │ ├── drawer.cy.js │ │ ├── editRuntimeForm.cy.js │ │ ├── gantt.cy.js │ │ ├── graph.cy.js │ │ ├── graphiql.cy.js │ │ ├── gscan.cy.js │ │ ├── header.cy.js │ │ ├── info.cy.js │ │ ├── layout.cy.js │ │ ├── log.cy.js │ │ ├── menu.cy.js │ │ ├── mutation.cy.js │ │ ├── table.cy.js │ │ ├── toolbar.cy.js │ │ ├── tree.cy.js │ │ ├── url.cy.js │ │ ├── userprofile.cy.js │ │ ├── workflowsTable.cy.js │ │ ├── workflowservice.cy.js │ │ └── workspace.cy.js │ └── support │ │ ├── commands.js │ │ ├── graphql.js │ │ └── index.js ├── unit │ ├── components │ │ ├── app.vue.spec.js │ │ ├── cylc │ │ │ ├── analysis │ │ │ │ ├── analysistable.vue.spec.js │ │ │ │ ├── analysistimeseries.vue.spec.js │ │ │ │ └── filter.spec.js │ │ │ ├── commandMenu │ │ │ │ └── menu.vue.spec.js │ │ │ ├── common │ │ │ │ ├── filter.spec.js │ │ │ │ ├── flowNumsChip.vue.spec.js │ │ │ │ └── sort.spec.js │ │ │ ├── gantt │ │ │ │ ├── filter.spec.js │ │ │ │ └── ganttChart.vue.spec.js │ │ │ ├── gscan │ │ │ │ ├── gscan.vue.spec.js │ │ │ │ ├── sort.spec.js │ │ │ │ ├── utils.js │ │ │ │ └── workflowicon.spec.js │ │ │ ├── job.vue.spec.js │ │ │ ├── log │ │ │ │ └── log.vue.spec.js │ │ │ ├── mutation.vue.spec.js │ │ │ ├── table │ │ │ │ ├── sort.spec.js │ │ │ │ ├── table.data.js │ │ │ │ └── table.vue.spec.js │ │ │ ├── toolbar.vue.spec.js │ │ │ ├── tree │ │ │ │ ├── tree.data.js │ │ │ │ ├── tree.vue.spec.js │ │ │ │ └── treeitem.vue.spec.js │ │ │ └── workflow │ │ │ │ └── toolbar.vue.spec.js │ │ └── graphqlFormGenerator │ │ │ ├── editRuntimeForm.vue.spec.js │ │ │ └── formgenerator.vue.spec.js │ ├── graphql │ │ └── merge.spec.js │ ├── mixins │ │ ├── graphql.spec.js │ │ ├── subscription.spec.js │ │ └── subscriptionComponent.spec.js │ ├── model │ │ ├── alert.model.spec.js │ │ ├── jobstate.model.spec.js │ │ ├── subscription.model.spec.js │ │ ├── subscriptionquery.model.spec.js │ │ └── taskstate.model.spec.js │ ├── router │ │ └── index.spec.js │ ├── services │ │ ├── testCallback.js │ │ ├── user.service.spec.js │ │ └── workflow.service.spec.js │ ├── setup.js │ ├── store │ │ ├── app.spec.js │ │ ├── index.spec.js │ │ ├── user.spec.js │ │ └── workflows.spec.js │ ├── utils │ │ ├── aotf.spec.js │ │ ├── font-size.spec.js │ │ ├── graph-utils.spec.js │ │ ├── graphql.spec.js │ │ ├── index.spec.js │ │ ├── initialOptions.spec.js │ │ ├── tasks.spec.js │ │ ├── toolbar.spec.js │ │ ├── uid.spec.js │ │ ├── urls.spec.js │ │ └── viewToolbar.spec.js │ └── views │ │ ├── gantt.vue.spec.js │ │ ├── log.vue.spec.js │ │ ├── table.vue.spec.js │ │ ├── tree.vue.spec.js │ │ └── views.spec.js └── util.js ├── vite.config.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | cover 90% and not dead 2 | Firefox ESR 3 | not op_mini all 4 | not iOS < 15 5 | not Safari < 15 6 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | # Codecov settings 2 | # After modifying this file, it might be worth to validate it: 3 | # see https://api.codecov.io/validate 4 | 5 | codecov: 6 | require_ci_to_pass: true 7 | 8 | coverage: 9 | precision: 2 10 | round: down 11 | # define the colour bar limits here (red...green) 12 | range: "75...100" 13 | 14 | # diff type 15 | status: 16 | project: 17 | default: 18 | # fail if coverage below this: 19 | target: '85%' 20 | # fail if coverage drops by this much: 21 | threshold: '2%' 22 | patch: 23 | default: 24 | target: '85%' 25 | 26 | # turn off comments to pull requests 27 | comment: false 28 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | module.exports = { 19 | root: true, 20 | parserOptions: { 21 | /* Allow new ECMAScript syntax but not globals. This is because vite/esbuild 22 | transforms syntax to es2015 (at the earliest) but does not pollyfill APIs. */ 23 | ecmaVersion: 'latest', 24 | }, 25 | extends: [ 26 | 'standard', 27 | 'eslint:recommended', 28 | 'plugin:vue/vue3-essential', 29 | 'plugin:vuetify/base', 30 | 'plugin:cypress/recommended', 31 | ], 32 | rules: { 33 | 'comma-dangle': [ 34 | 'error', 35 | { 36 | arrays: 'only-multiline', 37 | objects: 'only-multiline', 38 | imports: 'only-multiline', 39 | exports: 'only-multiline', 40 | functions: 'only-multiline', 41 | }, 42 | ], 43 | 'no-console': [ 44 | 'error', 45 | { 46 | allow: ['warn', 'error'] 47 | } 48 | ], 49 | 'template-curly-spacing': [ 50 | 'off' 51 | ], 52 | 'vue/multi-word-component-names': [ 53 | 'off' 54 | ], 55 | 'vue/valid-v-slot': [ 56 | 'error', 57 | { 58 | allowModifiers: true 59 | } 60 | ], 61 | 'promise/param-names': [ 62 | 'error' 63 | ], 64 | 'promise/no-return-wrap': [ 65 | 'error' 66 | ], 67 | 'cypress/unsafe-to-chain-command': [ 68 | 'off' 69 | ], 70 | }, 71 | } 72 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | labels: 8 | - 'dependencies' 9 | - 'infrastructure' 10 | -------------------------------------------------------------------------------- /.github/workflows/create_release_pr.yml: -------------------------------------------------------------------------------- 1 | name: Release stage 1 - create release PR 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: Version 8 | required: true 9 | branch: 10 | description: The branch to open the PR against 11 | required: false 12 | default: 'master' 13 | 14 | jobs: 15 | create-release-pr: 16 | runs-on: ubuntu-latest 17 | timeout-minutes: 5 18 | steps: 19 | 20 | - name: Sanitise workflow inputs # Should be 1st step 21 | uses: cylc/release-actions/stage-1/sanitize-inputs@v1 22 | 23 | - name: Checkout repo 24 | uses: actions/checkout@v4 25 | with: 26 | ref: ${{ env.BASE_REF }} 27 | fetch-depth: 0 # need to fetch all commits to check contributors 28 | 29 | - name: Check CONTRIBUTING.md 30 | uses: cylc/release-actions/check-shortlog@v1 31 | 32 | - name: Setup Python 33 | uses: actions/setup-python@v5 34 | with: 35 | python-version: '3.x' 36 | 37 | - name: Create & checkout PR branch 38 | uses: cylc/release-actions/stage-1/checkout-pr-branch@v1 39 | 40 | - name: Set the package version 41 | run: | 42 | npm version $VERSION 43 | 44 | - name: Generate changelog 45 | run: | 46 | python3 -m pip install -q towncrier 47 | towncrier build --yes --version $VERSION 48 | 49 | - name: Create pull request 50 | uses: cylc/release-actions/stage-1/create-release-pr@v1 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | with: 54 | test-workflows: main.yml 55 | -------------------------------------------------------------------------------- /.github/workflows/renovate_validate.yml: -------------------------------------------------------------------------------- 1 | name: Validate Renovate config 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - renovate.json 7 | workflow_dispatch: 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | env: 14 | FORCE_COLOR: 2 15 | 16 | jobs: 17 | validate: 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 5 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | 24 | - name: Setup Node 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: 'lts/*' 28 | 29 | - name: Validate config 30 | run: npx --yes --package renovate -- renovate-config-validator 31 | -------------------------------------------------------------------------------- /.github/workflows/shortlog.yml: -------------------------------------------------------------------------------- 1 | name: check contributor list 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | paths: 7 | - 'CONTRIBUTING.md' 8 | - '.github/workflows/shortlog.yml' 9 | - '.mailmap' 10 | 11 | jobs: 12 | test: 13 | runs-on: 'ubuntu-latest' 14 | timeout-minutes: 10 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 # need to fetch all commits to check contributors 20 | 21 | - name: Check CONTRIBUTING.md 22 | uses: cylc/release-actions/check-shortlog@v1 23 | -------------------------------------------------------------------------------- /.github/workflows/update_copyright.yml: -------------------------------------------------------------------------------- 1 | # THIS FILE IS PART OF THE CYLC WORKFLOW ENGINE. 2 | # Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | name: update copyright year 18 | 19 | on: 20 | workflow_dispatch: 21 | schedule: 22 | - cron: '25 4 1 1 *' # Every Jan 1st @ 04:25 UTC 23 | 24 | jobs: 25 | update-copyright: 26 | if: github.repository_owner == 'cylc' 27 | runs-on: ubuntu-latest 28 | timeout-minutes: 10 29 | steps: 30 | 31 | - name: Checkout repo 32 | uses: actions/checkout@v4 33 | 34 | - name: Configure git 35 | uses: cylc/release-actions/configure-git@v1 36 | 37 | - name: Checkout PR branch 38 | uses: cylc/release-actions/checkout-copyright-branch@v1 39 | 40 | - name: Update copyright year 41 | env: 42 | README_FILE: 'README.md' 43 | run: | 44 | pattern="().*(<\/span>)" 45 | sed -i -E "s/${pattern}/\1${YEAR}\2/" "$README_FILE" 46 | 47 | - name: Commit & push 48 | run : | 49 | git commit -a -m "Update copyright year" -m "Workflow: ${{ github.workflow }}, run: ${{ github.run_number }}" 50 | git push origin "$BRANCH_NAME" 51 | 52 | - name: Create pull request 53 | uses: cylc/release-actions/create-pr@v1 54 | env: 55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | with: 57 | head: ${{ env.BRANCH_NAME }} 58 | title: 'Auto PR: update copyright year' 59 | body: 'Happy new year!' 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | node_modules 4 | /dist/ 5 | /docs/jsdoc/ 6 | 7 | /tests/e2e/reports/ 8 | /tests/e2e/videos/ 9 | /tests/e2e/screenshots/ 10 | /tests/unit/coverage/ 11 | 12 | # local env files 13 | .env.local 14 | .env.*.local 15 | 16 | # Log files 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | selenium-debug.log* 21 | vite.config.*.timestamp* 22 | 23 | # Coverage 24 | .nyc_output/ 25 | /coverage/ 26 | 27 | # Editor directories and files 28 | .idea 29 | .vscode 30 | *.suo 31 | *.ntvs* 32 | *.njsproj 33 | *.sln 34 | *.sw* 35 | 36 | # Editor-related config that we may want to commit down the line, 37 | # just not yet 38 | vue-shim.d.js 39 | 40 | # Yarn 41 | .yarn/* 42 | !.yarn/patches 43 | !.yarn/releases 44 | !.yarn/plugins 45 | !.yarn/sdks 46 | !.yarn/versions 47 | .pnp.* 48 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*.js", "src/**/*.vue"], 3 | "exclude": [ 4 | "**/node_modules/**", 5 | "**/docs/**", 6 | "**/mock/**", 7 | "**/tests/**", 8 | "**/*.spec.js", 9 | "**/*.mock.*.js", 10 | "src/graphql/graphiql.js", 11 | "src/i18n/**/*.*", 12 | "src/lang/**/*.*", 13 | "src/plugins/**/*.*", 14 | "src/services/mock/**/*.*", 15 | "src/styles/**/*.*", 16 | "src/components/index.js" 17 | ], 18 | "excludeNodeModules": true, 19 | "extension": [".js", ".vue"], 20 | "reporter": ["html", "lcov", "text-summary"], 21 | "cache": false, 22 | "all": false, 23 | "check-coverage": false, 24 | "instrument": true, 25 | "sourceMap": true 26 | } 27 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | enableStrictSsl: true 2 | 3 | enableTelemetry: 0 4 | 5 | networkSettings: 6 | registry.npmjs.org: 7 | enableNetwork: true 8 | registry.yarnpkg.com: 9 | enableNetwork: true 10 | 11 | nodeLinker: node-modules 12 | 13 | yarnPath: .yarn/releases/yarn-4.9.2.cjs 14 | -------------------------------------------------------------------------------- /changes.d/2017.feat.md: -------------------------------------------------------------------------------- 1 | Added option to set automatic scrolling in log view 2 | -------------------------------------------------------------------------------- /changes.d/2025.feat.md: -------------------------------------------------------------------------------- 1 | Added conditional default log file based on job outcome 2 | -------------------------------------------------------------------------------- /changes.d/2135.feat.md: -------------------------------------------------------------------------------- 1 | Show more Cylc version info in the sidebar. 2 | -------------------------------------------------------------------------------- /changes.d/2163.fix.md: -------------------------------------------------------------------------------- 1 | Fixed sorting controls appearing in wrong tab when multiple analysis view tabs are open. 2 | -------------------------------------------------------------------------------- /changes.d/template.jinja: -------------------------------------------------------------------------------- 1 | {% if sections[""] %} 2 | {% for category, val in definitions.items() if category in sections[""] %} 3 | ### {{ definitions[category]['name'] }} 4 | 5 | {% for text, pulls in sections[""][category].items() %} 6 | {{ pulls|join(', ') }} - {{ text }} 7 | 8 | {% endfor %} 9 | {% endfor %} 10 | {% else %} 11 | No significant changes. 12 | 13 | {% endif %} 14 | -------------------------------------------------------------------------------- /cypress.config.cjs: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | const vitePreprocessor = require('cypress-vite') 3 | const path = require('path') 4 | 5 | module.exports = defineConfig({ 6 | video: false, 7 | defaultCommandTimeout: 10000, 8 | execTimeout: 60000, 9 | taskTimeout: 60000, 10 | pageLoadTimeout: 60000, 11 | requestTimeout: 30000, 12 | responseTimeout: 30000, 13 | fixturesFolder: 'tests/e2e/fixtures', 14 | screenshotsFolder: 'tests/e2e/screenshots', 15 | videosFolder: 'tests/e2e/videos', 16 | 17 | e2e: { 18 | baseUrl: 'http://localhost:5173', 19 | setupNodeEvents (on, config) { 20 | // For test coverage 21 | require('@cypress/code-coverage/task')(on, config) 22 | 23 | /* By default, Cypress uses webpack to transform spec files before 24 | running them. But it makes more sense to use vite instead, using the 25 | same config as the project. 26 | Note: Set NODE_ENV to prevent vite eslint plugin linting errors failing 27 | the transform (e.g. no-only-tests rule) */ 28 | process.env.NODE_ENV = 'development' 29 | on( 30 | 'file:preprocessor', 31 | vitePreprocessor({ 32 | configFile: path.resolve(__dirname, './vite.config.js'), 33 | mode: 'development', 34 | }) 35 | ) 36 | return config 37 | }, 38 | specPattern: 'tests/e2e/specs/**/*.cy.{js,jsx,ts,tsx}', 39 | supportFile: 'tests/e2e/support/index.js' 40 | }, 41 | 42 | component: { 43 | devServer: { 44 | framework: 'vue', 45 | bundler: 'vite' 46 | }, 47 | setupNodeEvents (on, config) { 48 | // For test coverage 49 | require('@cypress/code-coverage/task')(on, config) 50 | return config 51 | }, 52 | specPattern: 'tests/component/**/*.cy.{js,jsx,ts,tsx}', 53 | supportFile: 'tests/component/support/index.js', 54 | indexHtmlFile: 'tests/component/support/component-index.html' 55 | }, 56 | 57 | env: { 58 | // eslint-disable-next-line no-unneeded-ternary 59 | coverage: process.env.COVERAGE ? true : false 60 | }, 61 | 62 | morgan: false, // Disable XHR logging as it's very noisy 63 | }) 64 | -------------------------------------------------------------------------------- /docs/misc/google-analytics.md: -------------------------------------------------------------------------------- 1 | # Google Analytics 2 | 3 | The first version of this project had a Google Analytics example. This was 4 | removed later (see GitHub issue #14). 5 | 6 | But in case any user wants it back, or in case there is another piece of code 7 | that a user would like to use, that is similar to what Google Analytics look 8 | like (e.g. New Relic for monitoring), here's what was changed. 9 | 10 | ## Router 11 | 12 | The router, which in some Vue.js application is located in `router.js`, actually 13 | had its own folder `router/` with an `index.js` and a `paths.js`. 14 | 15 | In the `index.js`, this code was present. 16 | 17 | ```vuejs 18 | import VueAnalytics from 'vue-analytics' 19 | ... 20 | // Bootstrap Analytics 21 | // Set in .env 22 | // https://github.com/MatteoGabriele/vue-analytics 23 | if (process.env.GOOGLE_ANALYTICS) { 24 | Vue.use(VueAnalytics, { 25 | id: process.env.GOOGLE_ANALYTICS, 26 | router, 27 | autoTracking: { 28 | page: process.env.NODE_ENV !== 'development' 29 | } 30 | }) 31 | } 32 | ``` 33 | 34 | ## package.json 35 | 36 | In `devDependencies`, in our `package.json`, we had the following entry: 37 | 38 | ```js 39 | "devDependencies": { 40 | "vue-analytics": "^5.16.2", 41 | } 42 | ``` 43 | 44 | ## Final note 45 | 46 | Hopefully this will give some basis for investigating how to add back Google 47 | Analytics, or how to add some other external JS code. 48 | -------------------------------------------------------------------------------- /eslintrc-dist.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /** 19 | * Eslint config for dist directory. 20 | * 21 | * Note: we can't keep it in there because it would get wiped by build. 22 | */ 23 | module.exports = { 24 | plugins: ['compat'], 25 | extends: ['plugin:compat/recommended'], 26 | env: { 27 | browser: true, 28 | }, 29 | parserOptions: { 30 | sourceType: 'module', 31 | // Don't need to worry about ECMA syntax as that's handled by Vite/ESBuild 32 | ecmaVersion: 'latest', 33 | }, 34 | noInlineConfig: true, 35 | reportUnusedDisableDirectives: false, // doesn't seem to work 36 | settings: { 37 | polyfills: [ 38 | // Used by GraphiQL, shouldn't be a problem: 39 | 'navigator.userAgentData', 40 | ], 41 | }, 42 | } 43 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Cylc UI 25 | 26 | 27 | 30 |
31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src/**/*.js", 4 | "src/**/*.mjs", 5 | "src/**/*.cjs", 6 | "src/**/*.vue", 7 | "tests/**/*" 8 | ], 9 | "exclude": [ 10 | "src/services/mock/json/*" 11 | ], 12 | "compilerOptions": { 13 | // "checkJs": true, 14 | "paths": { 15 | "@/*": ["./src/*"] 16 | } 17 | }, 18 | "watchOptions": { 19 | "excludeFiles": ["**/node_modules"] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /jsdoc.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true 4 | }, 5 | "source": { 6 | "include": ["."], 7 | "includePattern": ".+\\.js(doc|x)?$", 8 | "exclude": [ 9 | ".github", 10 | ".nyc_output", 11 | "coverage", 12 | "dist", 13 | "docs", 14 | "node_modules", 15 | "public" 16 | ] 17 | }, 18 | "plugins": [], 19 | "recurseDepth": 10, 20 | "opts": { 21 | "destination": "./docs/jsdoc", 22 | "encoding": "utf8", 23 | "recurse": true, 24 | "private": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cylc/cylc-ui/7f250354aa54273bd6a745c7ea35d49a037daa5c/public/favicon.png -------------------------------------------------------------------------------- /public/fonts/roboto-v20-latin-100.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cylc/cylc-ui/7f250354aa54273bd6a745c7ea35d49a037daa5c/public/fonts/roboto-v20-latin-100.woff -------------------------------------------------------------------------------- /public/fonts/roboto-v20-latin-100italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cylc/cylc-ui/7f250354aa54273bd6a745c7ea35d49a037daa5c/public/fonts/roboto-v20-latin-100italic.woff -------------------------------------------------------------------------------- /public/fonts/roboto-v20-latin-300.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cylc/cylc-ui/7f250354aa54273bd6a745c7ea35d49a037daa5c/public/fonts/roboto-v20-latin-300.woff -------------------------------------------------------------------------------- /public/fonts/roboto-v20-latin-300italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cylc/cylc-ui/7f250354aa54273bd6a745c7ea35d49a037daa5c/public/fonts/roboto-v20-latin-300italic.woff -------------------------------------------------------------------------------- /public/fonts/roboto-v20-latin-500.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cylc/cylc-ui/7f250354aa54273bd6a745c7ea35d49a037daa5c/public/fonts/roboto-v20-latin-500.woff -------------------------------------------------------------------------------- /public/fonts/roboto-v20-latin-500italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cylc/cylc-ui/7f250354aa54273bd6a745c7ea35d49a037daa5c/public/fonts/roboto-v20-latin-500italic.woff -------------------------------------------------------------------------------- /public/fonts/roboto-v20-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cylc/cylc-ui/7f250354aa54273bd6a745c7ea35d49a037daa5c/public/fonts/roboto-v20-latin-700.woff -------------------------------------------------------------------------------- /public/fonts/roboto-v20-latin-700italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cylc/cylc-ui/7f250354aa54273bd6a745c7ea35d49a037daa5c/public/fonts/roboto-v20-latin-700italic.woff -------------------------------------------------------------------------------- /public/fonts/roboto-v20-latin-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cylc/cylc-ui/7f250354aa54273bd6a745c7ea35d49a037daa5c/public/fonts/roboto-v20-latin-900.woff -------------------------------------------------------------------------------- /public/fonts/roboto-v20-latin-900italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cylc/cylc-ui/7f250354aa54273bd6a745c7ea35d49a037daa5c/public/fonts/roboto-v20-latin-900italic.woff -------------------------------------------------------------------------------- /public/fonts/roboto-v20-latin-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cylc/cylc-ui/7f250354aa54273bd6a745c7ea35d49a037daa5c/public/fonts/roboto-v20-latin-italic.woff -------------------------------------------------------------------------------- /public/fonts/roboto-v20-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cylc/cylc-ui/7f250354aa54273bd6a745c7ea35d49a037daa5c/public/fonts/roboto-v20-latin-regular.woff -------------------------------------------------------------------------------- /public/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.towncrier] 2 | directory = "changes.d" 3 | filename = "CHANGES.md" 4 | template = "changes.d/template.jinja" 5 | underlines = ["", "", ""] 6 | title_format = "## cylc-ui-{version} (Released {project_date})" 7 | issue_format = "[#{issue}](https://github.com/cylc/cylc-ui/pull/{issue})" 8 | ignore = ["template.jinja"] 9 | 10 | # These changelog sections will be shown in the defined order: 11 | [[tool.towncrier.type]] 12 | directory = "feat" # NB this is just the filename not directory e.g. 123.break.md 13 | name = "🚀 Enhancements" 14 | showcontent = true 15 | [[tool.towncrier.type]] 16 | directory = "fix" 17 | name = "🔧 Fixes" 18 | showcontent = true 19 | -------------------------------------------------------------------------------- /scripts/concurrently.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const concurrently = require('concurrently') 3 | 4 | const args = process.argv.slice(2) 5 | const { VITE_OPTIONS } = process.env 6 | 7 | const allCommands = { 8 | 'serve:jupyterhub': { 9 | command: 'yarn run serve:jupyterhub', 10 | name: 'SERVER', 11 | prefixColor: 'yellow' 12 | }, 13 | 'serve:vue': { 14 | command: `yarn run serve:vue ${VITE_OPTIONS ?? ''}`, 15 | name: 'VITE', 16 | prefixColor: 'blue' 17 | }, 18 | preview: { 19 | command: `yarn run -B vite preview --mode offline ${VITE_OPTIONS ?? ''}`, 20 | name: 'VITE', 21 | prefixColor: 'blue' 22 | }, 23 | 'e2e:open': { 24 | command: 'yarn run -B cypress open --e2e', 25 | name: 'TESTS', 26 | prefixColor: 'magenta' 27 | }, 28 | 'cy:run': { 29 | command: 'yarn run -B cypress run', 30 | name: 'TESTS', 31 | prefixColor: 'cyan' 32 | } 33 | } 34 | 35 | concurrently( 36 | args.map((arg) => allCommands[arg]), 37 | { 38 | successCondition: 'first', 39 | killOthers: ['success', 'failure'], 40 | } 41 | ) 42 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 27 | 28 | 52 | -------------------------------------------------------------------------------- /src/components/Markdown.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 21 | 22 | 25 | 26 | 48 | -------------------------------------------------------------------------------- /src/components/README.md: -------------------------------------------------------------------------------- 1 | # Components 2 | 3 | These are reusable components which may be used throughout the app. 4 | 5 | * `graphqlFormGenerator` contains components used for auto-generating forms 6 | from GraphQL mutations. 7 | * `cylc` contains components for Cylc things. 8 | * `core` - legacy directory, we should merge this in with other components. 9 | -------------------------------------------------------------------------------- /src/components/core/Alert.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 40 | 41 | 67 | -------------------------------------------------------------------------------- /src/components/core/CopyBtn.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 34 | 35 | 57 | -------------------------------------------------------------------------------- /src/components/cylc/ConnectionStatus.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 36 | 37 | 58 | -------------------------------------------------------------------------------- /src/components/cylc/GraphSubgraph.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 21 | 50 | 51 | 73 | -------------------------------------------------------------------------------- /src/components/cylc/Task.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 22 | 23 | 34 | 35 | 58 | 59 | 70 | -------------------------------------------------------------------------------- /src/components/cylc/TaskFilter.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 50 | 51 | 83 | -------------------------------------------------------------------------------- /src/components/cylc/Toolbar.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 22 | 23 | 45 | 46 | 68 | -------------------------------------------------------------------------------- /src/components/cylc/analysis/filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /** 19 | * Function to determine if a task should be displayed given a certain filter 20 | * Checks the name includes a search string and if the platform is equal to 21 | * that chosen 22 | * 23 | * @export 24 | * @param {object} task - The task to evaluate 25 | * @param {object} tasksFilter - The filter to apply to the task 26 | * @return {boolean} Boolean determining if task should be displayed 27 | */ 28 | export function matchTask (task, tasksFilter) { 29 | let ret = true 30 | if (tasksFilter.name?.trim()) { 31 | ret &&= task.name.includes(tasksFilter.name) 32 | } 33 | if (tasksFilter.platformOption.trim?.()) { 34 | ret &&= task.platform === tasksFilter.platformOption 35 | } 36 | return ret 37 | } 38 | 39 | /** 40 | * Function to find the unique platforms in an array of tasks 41 | * 42 | * @export 43 | * @param {array} tasks - The tasks to search for unique platforms 44 | * @return {array} - An array of unique platform objects 45 | */ 46 | export function platformOptions (tasks) { 47 | const platformOptions = [{ value: -1, title: 'All' }] 48 | const platforms = [] 49 | for (const task of tasks) { 50 | if (!platforms.includes(task.platform)) { 51 | platforms.push(task.platform) 52 | platformOptions.push({ value: task.platform, title: task.platform }) 53 | } 54 | } 55 | return platformOptions 56 | } 57 | -------------------------------------------------------------------------------- /src/components/cylc/common/FlowNumsChip.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 34 | 35 | 57 | -------------------------------------------------------------------------------- /src/components/cylc/common/filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /* Logic for filtering tasks. */ 19 | 20 | /** 21 | * Return true if the node ID matches the given ID, or if no ID is given. 22 | * 23 | * @param {Object} node 24 | * @param {?string} id 25 | * @return {boolean} 26 | */ 27 | export function matchID (node, id) { 28 | return !id?.trim() || node.tokens.relativeID.includes(id) 29 | } 30 | 31 | /** 32 | * Return true if the given list of states includes the node state, or if 33 | * if the list is empty. 34 | * 35 | * @param {Object} node 36 | * @param {?string[]} states 37 | * @returns {boolean} 38 | */ 39 | export function matchState (node, states) { 40 | return !states?.length || states.includes(node.node.state) 41 | } 42 | 43 | /** 44 | * Return true if a node matches the specified id/state filter. 45 | * 46 | * @export 47 | * @param {Object} node 48 | * @param {?string} id 49 | * @param {?string[]} states 50 | * @return {boolean} 51 | */ 52 | export function matchNode (node, id, states) { 53 | return matchID(node, id) && matchState(node, states) 54 | } 55 | -------------------------------------------------------------------------------- /src/components/cylc/gscan/WorkflowIcon.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 23 | 24 | 54 | -------------------------------------------------------------------------------- /src/components/cylc/gscan/filters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /** 19 | * @param {WorkflowGScanNode|WorkflowNamePartGScanNode} workflow 20 | * @param {?string} name - name filter 21 | * @returns {boolean} 22 | */ 23 | export function filterByName (workflow, name) { 24 | return !name || workflow.tokens.workflow.toLowerCase().includes(name.toLowerCase()) 25 | } 26 | 27 | /** 28 | * @private 29 | * @param {Object=} stateTotals - object with the keys being states, and values the count 30 | * @return {string[]} 31 | */ 32 | function getWorkflowStates (stateTotals) { 33 | return !stateTotals 34 | ? [] 35 | : Object.keys(stateTotals).filter((state) => stateTotals[state] > 0) 36 | } 37 | 38 | /** 39 | * @param {WorkflowGScanNode|WorkflowNamePartGScanNode} workflow 40 | * @param {string[]} workflowStates 41 | * @param {string[]} taskStates 42 | * @returns {boolean} 43 | */ 44 | export function filterByState (workflow, workflowStates, taskStates) { 45 | // workflow states 46 | if ( 47 | workflowStates.length && !workflowStates.includes(workflow.node.status) 48 | ) { 49 | return false 50 | } 51 | // task states 52 | if (taskStates.length) { 53 | return getWorkflowStates(workflow.node.stateTotals).some( 54 | (item) => taskStates.includes(item) 55 | ) 56 | } 57 | return true 58 | } 59 | -------------------------------------------------------------------------------- /src/components/cylc/table/sort.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /** 19 | * Comparator function for sorting datetime strings. 20 | * 21 | * @param {string} a - The first element for comparison. 22 | * @param {string} b - The second element for comparison. 23 | * @return {number} A number > 0 if a > b, or < 0 if a < b, or 0 if a === b 24 | */ 25 | export function datetimeComparator (a, b) { 26 | return new Date(a) - new Date(b) 27 | } 28 | 29 | /** 30 | * Comparator function for sorting numbers. 31 | * 32 | * @param {number} a 33 | * @param {number} b 34 | * @returns {number} 35 | */ 36 | export function numberComparator (a, b) { 37 | return a - b 38 | } 39 | -------------------------------------------------------------------------------- /src/components/cylc/tree/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | 17 | export function getNodeChildren (node, cyclePointsOrderDesc, flat) { 18 | // returns child nodes folling the family tree and following sort order 19 | if (node.type === 'workflow' && !cyclePointsOrderDesc) { 20 | // a user configuration has configured the sort order for cycle points to 21 | // be reversed 22 | return [...node.children].reverse() 23 | } else if (node.type === 'cycle' && !flat) { 24 | // display tasks in the inheritance tree 25 | if (node.familyTree?.length) { 26 | const rootFamily = node.familyTree[0] 27 | return rootFamily.children 28 | } else { 29 | return [] 30 | } 31 | } 32 | return node.children 33 | } 34 | 35 | export function getIndent (depth) { 36 | return `calc(${depth} * var(--c-tree-indent))` 37 | } 38 | -------------------------------------------------------------------------------- /src/components/cylc/workspace/Widget.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 26 | 27 | 58 | -------------------------------------------------------------------------------- /src/components/graphqlFormGenerator/components/Enum.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 29 | 30 | 63 | -------------------------------------------------------------------------------- /src/components/graphqlFormGenerator/components/NonNull.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 31 | 32 | 46 | -------------------------------------------------------------------------------- /src/components/graphqlFormGenerator/components/Object.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 35 | 36 | 54 | -------------------------------------------------------------------------------- /src/composables/localStorage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /* Centralised settings set in localStorage, with their defaults. 19 | 20 | NOTE: by providing a default to useLocalStorage(), it automatically 21 | deserializes to the correct type instead of just string. 22 | 23 | These must be composables because otherwise useLocalStorage() gets executed at 24 | import time and so can't pick up changes to localStorage in unit tests. */ 25 | 26 | import { useLocalStorage } from '@vueuse/core' 27 | 28 | export const useCyclePointsOrderDesc = () => useLocalStorage('cyclePointsOrderDesc', true) 29 | 30 | export const useJobTheme = () => useLocalStorage('jobTheme', 'default') 31 | 32 | export const useReducedAnimation = () => useLocalStorage('reducedAnimation', false) 33 | -------------------------------------------------------------------------------- /src/i18n/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /** 19 | * Vue i18n 20 | * 21 | * @library 22 | * 23 | * http://kazupon.github.io/vue-i18n/en/ 24 | */ 25 | 26 | import { createI18n } from 'vue-i18n' 27 | import messages from '@/lang' 28 | 29 | export const i18n = createI18n({ 30 | legacy: false, 31 | locale: 'en-GB', 32 | fallbackLocale: 'en-GB', 33 | messages 34 | }) 35 | -------------------------------------------------------------------------------- /src/i18n/languages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "English", 4 | "locale": "en-GB", 5 | "country": "gb" 6 | }, 7 | { 8 | "name": "Português", 9 | "locale": "pt-BR", 10 | "country": "br" 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /src/lang/en-GB/App.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cylc UI", 3 | "dashboard": "Dashboard", 4 | "workflow": "Workflow {name}", 5 | "workflows": "Workflows", 6 | "notFound": "Not Found", 7 | "userProfile": "User Profile", 8 | "guide": "Guide" 9 | } 10 | -------------------------------------------------------------------------------- /src/lang/en-GB/NotFound.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Page not found", 3 | "message": "Maybe the page you are looking for has been removed, or you typed in the wrong address", 4 | "goBack": "Go Back", 5 | "toHomepage": "Go to Homepage" 6 | } 7 | -------------------------------------------------------------------------------- /src/lang/en-GB/Toolbar.json: -------------------------------------------------------------------------------- 1 | { 2 | "control": "Control", 3 | "addView": "Add View" 4 | } 5 | -------------------------------------------------------------------------------- /src/lang/en-GB/UserProfile.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Settings", 3 | "yourProfile": "Your Profile", 4 | "username": "Username", 5 | "permissions": "Authorized Operations" 6 | } 7 | -------------------------------------------------------------------------------- /src/lang/en-GB/Workflows.json: -------------------------------------------------------------------------------- 1 | { 2 | "tableHeader": "Workflows Table", 3 | "tableColumnName": "Name", 4 | "tableColumnOwner": "Owner", 5 | "tableColumnHost": "Host", 6 | "tableColumnPort": "Port", 7 | "tableColumnActions": "Actions" 8 | } 9 | -------------------------------------------------------------------------------- /src/lang/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /** 19 | * Compile & export the i18n messages object from the JSON files in this 20 | * directory. 21 | * 22 | * @see https://kazupon.github.io/vue-i18n/guide/messages.html 23 | */ 24 | 25 | const modules = import.meta.glob('./**/*.json', { eager: true }) 26 | 27 | const messages = {} 28 | 29 | for (const file in modules) { 30 | const path = file.replace(/(\.\/|\.json$)/g, '').split('/') 31 | 32 | path.reduce((o, s, i) => { 33 | if (o[s]) return o[s] 34 | 35 | o[s] = i + 1 === path.length 36 | ? Object.assign({}, modules[file]) 37 | : {} 38 | 39 | return o[s] 40 | }, messages) 41 | } 42 | 43 | export default messages 44 | -------------------------------------------------------------------------------- /src/lang/pt-BR/App.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cylc UI", 3 | "dashboard": "Dashboard", 4 | "workflow": "Workflow {name}", 5 | "workflows": "Workflows", 6 | "notFound": "Página não encontrada", 7 | "userProfile": "Perfil de Usuário", 8 | "guide": "Guia" 9 | } 10 | -------------------------------------------------------------------------------- /src/lang/pt-BR/NotFound.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Página não encontrada", 3 | "message": "Talvez a página que você está procurando tenha sido removida ou você tenha digitado o endereço errado", 4 | "goBack": "Voltar", 5 | "toHomepage": "Voltar para a página principal" 6 | } 7 | -------------------------------------------------------------------------------- /src/lang/pt-BR/Toolbar.json: -------------------------------------------------------------------------------- 1 | { 2 | "control": "Controle", 3 | "addView": "Adicionar Painel" 4 | } 5 | -------------------------------------------------------------------------------- /src/lang/pt-BR/UserProfile.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Configurações", 3 | "yourProfile": "Seu perfil de Usuário", 4 | "username": "Nome de Usuário", 5 | "permissions": "Operações Autorizadas" 6 | } 7 | -------------------------------------------------------------------------------- /src/lang/pt-BR/Workflows.json: -------------------------------------------------------------------------------- 1 | { 2 | "tableHeader": "Tabela de Workflows", 3 | "tableColumnName": "Nome", 4 | "tableColumnOwner": "Usuário", 5 | "tableColumnHost": "Servidor", 6 | "tableColumnPort": "Porta", 7 | "tableColumnActions": "Ações" 8 | } 9 | -------------------------------------------------------------------------------- /src/layouts/Empty.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 23 | 24 | 29 | 30 | 33 | -------------------------------------------------------------------------------- /src/layouts/README.md: -------------------------------------------------------------------------------- 1 | # Layouts 2 | 3 | This directory contains the top-level layouts that all parts of the app use. 4 | 5 | E.G. layouts define the sidebar, toolbar and the main content area. 6 | -------------------------------------------------------------------------------- /src/mixins/README.md: -------------------------------------------------------------------------------- 1 | # Mixins 2 | 3 | Mixins contain common functionalities that can be applied to multiple 4 | components. Mixins are kinda like inheritance, but more Functional. 5 | -------------------------------------------------------------------------------- /src/mixins/graphql.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { mapState } from 'vuex' 19 | 20 | /** 21 | * A mixin that contains data used for a GraphQL subscription, such as the 22 | * query variables. 23 | * 24 | * To be used in Views that are bound to Vue-Router routes that contain the 25 | * :workflowName param. 26 | */ 27 | export default { 28 | props: { 29 | /** This is set by vue-router */ 30 | workflowName: { 31 | type: String, 32 | required: true 33 | } 34 | }, 35 | computed: { 36 | /** 37 | * We use the user from the store to compute the workflow ID. The view 38 | * has only the workflow name from the Vue route. We then combine it 39 | * with the user name to create the workflow ID. 40 | * 41 | * @return {import('@/model/User.model').User} 42 | */ 43 | ...mapState('user', ['user']), 44 | /** 45 | * Compute the workflow ID using the Vue route parameter 46 | * `workflowName` and the user from the store. 47 | * 48 | * @return {string} - the Workflow ID used in this view 49 | */ 50 | workflowId () { 51 | return `~${this.user.owner}/${this.workflowName}` 52 | }, 53 | /** 54 | * GraphQL query variables. 55 | * 56 | * @returns {{workflowId: string}} 57 | */ 58 | variables () { 59 | return { 60 | workflowId: this.workflowId 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/mixins/subscription.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | import ViewState from '@/model/ViewState.model' 18 | import { toRaw } from 'vue' 19 | import { mapActions } from 'vuex' 20 | 21 | /** 22 | * A mixin for data common to views and components with a view state. Useful 23 | * when you load data for the view, and have loading state such as NO_STATE, 24 | * LOADING, ERROR, COMPLETE, etc. 25 | * 26 | * Also maps the setAlert action, to notify the user about errors. 27 | * 28 | * @see ViewState 29 | * @see Alert 30 | */ 31 | export default { 32 | data () { 33 | return { 34 | viewState: ViewState.NO_STATE 35 | } 36 | }, 37 | computed: { 38 | isLoading () { 39 | // Note: this.viewState is Proxy object so comparison 40 | // doesn't work without toRaw() 41 | return toRaw(this.viewState) === ViewState.LOADING 42 | } 43 | }, 44 | methods: { 45 | ...mapActions(['setAlert']) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/model/Alert.model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /** Alert model */ 19 | export class Alert { 20 | /** 21 | * @param {Error|string} err - original thrown error to log if possible, or an error message. 22 | * @param {string} color - color of the displayed alert. 23 | * @param {?string} msg - a custom message to display in the alert instead of err. 24 | */ 25 | constructor (err, color, msg = null) { 26 | this.err = err 27 | this.text = msg || err 28 | this.color = color 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/model/JobState.model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { Enumify } from 'enumify' 19 | 20 | /** 21 | * Cylc valid job states. 22 | * 23 | * @see https://cylc.github.io/cylc-admin/proposal-state-names.html#taskjob-states 24 | */ 25 | class JobState extends Enumify { 26 | static SUBMITTED = new JobState('submitted') 27 | static SUBMIT_FAILED = new JobState('submit-failed') 28 | static RUNNING = new JobState('running') 29 | static SUCCEEDED = new JobState('succeeded') 30 | static FAILED = new JobState('failed') 31 | static _ = this.closeEnum() 32 | 33 | /** 34 | * Constructor. 35 | * @param {String} name 36 | */ 37 | constructor (name) { 38 | super() 39 | this.name = name 40 | } 41 | } 42 | 43 | export const JobStateNames = JobState.enumValues.map(({ name }) => name) 44 | 45 | /** 46 | * Get the appropriate log file name for a job based on its state. 47 | * 48 | * @param {string=} state 49 | * @returns {string=} log file name if state is defined & valid, else undefined. 50 | */ 51 | export function getJobLogFileFromState (state) { 52 | switch (state) { 53 | case JobState.FAILED.name: 54 | return 'job.err' 55 | case JobState.SUBMITTED.name: 56 | case JobState.SUBMIT_FAILED.name: 57 | return 'job-activity.log' 58 | case JobState.RUNNING.name: 59 | case JobState.SUCCEEDED.name: 60 | return 'job.out' 61 | } 62 | } 63 | 64 | export default JobState 65 | -------------------------------------------------------------------------------- /src/model/SubscriptionQuery.model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /** @typedef {import('graphql').DocumentNode} DocumentNode */ 19 | 20 | /** 21 | * A subscription query. It is part of a Subscription, and contains query and auxiliary data 22 | * such as query name, variables, and callbacks. 23 | * 24 | * The name of the query is an important part of the data, as it is used as key in a dictionary 25 | * that holds the queries. It can be used to merge two queries when they have the same name. 26 | * 27 | * Callbacks are Vuex **actions** (i.e. we call store.dispatch(), not store.commit()), and resolve 28 | * asynchronously. 29 | * 30 | * @see Subscription 31 | */ 32 | class SubscriptionQuery { 33 | /** 34 | * @param {DocumentNode} query 35 | * @param {Object.} variables 36 | * @param {String} name 37 | * @param {Array} callbacks 38 | * @param {boolean} isDelta 39 | * @param {boolean} isGlobalCallback 40 | */ 41 | constructor (query, variables, name, callbacks, isDelta, isGlobalCallback) { 42 | this.query = query 43 | this.variables = variables 44 | this.name = name 45 | this.callbacks = callbacks 46 | this.isDelta = isDelta 47 | this.isGlobalCallback = isGlobalCallback 48 | } 49 | } 50 | 51 | export default SubscriptionQuery 52 | -------------------------------------------------------------------------------- /src/model/TaskOutput.model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { Enumify } from 'enumify' 19 | 20 | /** 21 | * @typedef {Object} TaskOutput 22 | * @property {string} name - enum name 23 | */ 24 | class TaskOutput extends Enumify { 25 | // @see: https://cylc.github.io/cylc-admin/proposal-state-names.html#outputs 26 | // @see: https://github.com/cylc/cylc-flow/blob/bb79a6e03437927ecf97deb6a34fa8f1e7ab0835/cylc/flow/task_outputs.py 27 | static EXPIRED = new TaskOutput('expired') 28 | static SUBMITTED = new TaskOutput('submitted') 29 | static SUBMIT_FAILED = new TaskOutput('submit-failed') 30 | static STARTED = new TaskOutput('started') 31 | static SUCCEEDED = new TaskOutput('succeeded') 32 | static FAILED = new TaskOutput('failed') 33 | static _ = this.closeEnum() 34 | 35 | /** 36 | * Constructor. 37 | * @param {String} name 38 | */ 39 | constructor (name) { 40 | super() 41 | this.name = name 42 | } 43 | } 44 | 45 | export const TASK_OUTPUT_NAMES = [ 46 | TaskOutput.SUBMITTED.name, 47 | TaskOutput.STARTED.name, 48 | TaskOutput.SUCCEEDED.name, 49 | TaskOutput.SUBMIT_FAILED.name, 50 | TaskOutput.FAILED.name, 51 | TaskOutput.EXPIRED.name 52 | ] 53 | 54 | export default TaskOutput 55 | -------------------------------------------------------------------------------- /src/model/TaskState.model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { Enumify } from 'enumify' 19 | 20 | /** 21 | * Cylc valid task states. 22 | */ 23 | export class TaskState extends Enumify { 24 | static SUBMIT_FAILED = new TaskState('submit-failed') 25 | static FAILED = new TaskState('failed') 26 | static EXPIRED = new TaskState('expired') 27 | static RUNNING = new TaskState('running') 28 | static SUBMITTED = new TaskState('submitted') 29 | static PREPARING = new TaskState('preparing') 30 | static WAITING = new TaskState('waiting') 31 | static SUCCEEDED = new TaskState('succeeded') 32 | static _ = this.closeEnum() 33 | 34 | /** 35 | * Constructor. 36 | * @param {String} name 37 | */ 38 | constructor (name) { 39 | super() 40 | this.name = name 41 | } 42 | } 43 | 44 | /** 45 | * Task states ordered for display purposes. 46 | */ 47 | export const TaskStateUserOrder = [ 48 | TaskState.WAITING, 49 | TaskState.PREPARING, 50 | TaskState.SUBMITTED, 51 | TaskState.RUNNING, 52 | TaskState.SUCCEEDED, 53 | TaskState.SUBMIT_FAILED, 54 | TaskState.FAILED, 55 | TaskState.EXPIRED 56 | ] 57 | 58 | export const TaskStateNames = TaskStateUserOrder.map(({ name }) => name) 59 | 60 | export default TaskState 61 | -------------------------------------------------------------------------------- /src/model/User.model.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | export default class User { 19 | constructor ({ username, owner, permissions, mode, initials, color, extensions }) { 20 | /** 21 | * @type {string} 22 | */ 23 | this.username = username 24 | /** 25 | * the UIS owner (i.e. the user who's workflows we are looking at) 26 | * (this might not be the authenticated user for multi-user setups) 27 | * @type {string} 28 | */ 29 | this.owner = owner 30 | /** 31 | * list of permissions 32 | * @type {string[]} 33 | */ 34 | this.permissions = permissions 35 | /** 36 | * single or multi user mode 37 | * @type {string} 38 | */ 39 | this.mode = mode 40 | /** 41 | * user initials 42 | * @type {string} 43 | */ 44 | this.initials = initials 45 | /** 46 | * user avatar color if set 47 | * @type {string | null} 48 | */ 49 | this.color = color 50 | /** 51 | * Jupyter server extensions. 52 | */ 53 | this.extensions = extensions 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/model/ViewState.model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { Enumify } from 'enumify' 19 | 20 | /** 21 | * Represents the state of a view, like views with subscriptions, or other views 22 | * that load data. 23 | * 24 | * Besides the obvious loading and complete, it also includes the initial NO_STATE 25 | * state, and the COMPLETE state. 26 | * 27 | * @see https://dev.to/mpocock1/state-management-how-to-tell-a-bad-boolean-from-a-good-boolean-260n 28 | * @see https://lillo.dev/articles/slaying-a-ui-antipattern/1-fetch-data-with-elm-pattern/ 29 | */ 30 | class ViewState extends Enumify { 31 | static NO_STATE = new ViewState() 32 | static LOADING = new ViewState() 33 | static ERROR = new ViewState() 34 | static COMPLETE = new ViewState() 35 | static _ = this.closeEnum() 36 | } 37 | 38 | export default ViewState 39 | -------------------------------------------------------------------------------- /src/model/WorkflowState.model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { Enumify } from 'enumify' 19 | 20 | import { 21 | mdiPauseCircle, 22 | mdiPlayCircle, 23 | mdiSkipNextCircle, 24 | mdiStopCircle 25 | } from '@mdi/js' 26 | 27 | /** 28 | * Cylc valid workflow states. 29 | */ 30 | export class WorkflowState extends Enumify { 31 | static RUNNING = new WorkflowState('running', mdiPlayCircle) 32 | static PAUSED = new WorkflowState('paused', mdiPauseCircle) 33 | static STOPPING = new WorkflowState('stopping', mdiSkipNextCircle) 34 | static STOPPED = new WorkflowState('stopped', mdiStopCircle) 35 | static _ = this.closeEnum() 36 | 37 | /** 38 | * Constructor. 39 | * @param {String} name 40 | * @param {String} icon 41 | */ 42 | constructor (name, icon) { 43 | super() 44 | this.name = name 45 | this.icon = icon 46 | } 47 | } 48 | 49 | /** 50 | * Workflow states ordered for display purposes. 51 | * @type {Map} - Using a map to prevent more unexpected sorting issues 52 | * @see https://stackoverflow.com/questions/5525795/does-javascript-guarantee-object-property-order/38218582#38218582 53 | */ 54 | export const WorkflowStateOrder = new Map([ 55 | [WorkflowState.RUNNING.name, 1], 56 | [WorkflowState.PAUSED.name, 1], 57 | [WorkflowState.STOPPING.name, 1], 58 | [WorkflowState.STOPPED.name, 2], 59 | [undefined, 9] 60 | ]) 61 | 62 | /** @type {string[]} */ 63 | export const WorkflowStateNames = WorkflowState.enumValues.map(({ name }) => name) 64 | 65 | export default WorkflowState 66 | -------------------------------------------------------------------------------- /src/services/callbacks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | class DeltasCallback { 19 | /** 20 | * @param {Vuex} store 21 | * @param {Array} errors 22 | */ 23 | init (store, errors) {} 24 | 25 | /** 26 | * @param {Deltas} deltas 27 | * @param {Vuex} store 28 | * @param {Array} errors 29 | */ 30 | before (deltas, store, errors) {} 31 | 32 | /** 33 | * @param {Deltas} deltas 34 | * @param {Vuex} store 35 | * @param {Array} errors 36 | */ 37 | after (deltas, store, errors) {} 38 | 39 | /** 40 | * @param {Vuex} store - Vuex store 41 | * @param {Array} errors 42 | */ 43 | tearDown (store, errors) {} 44 | 45 | /** 46 | * @param {DeltasAdded|Object} added 47 | * @param {Vuex} store 48 | * @param {Array} errors 49 | */ 50 | onAdded (added, store, errors) {} 51 | 52 | /** 53 | * @param {DeltasUpdated|Object} updated 54 | * @param {Vuex} store 55 | * @param {Array} errors 56 | */ 57 | onUpdated (updated, store, errors) {} 58 | 59 | /** 60 | * @param {DeltasPruned|Object} pruned - 61 | * @param {Vuex} store - Vuex store 62 | * @param {Array} errors 63 | */ 64 | onPruned (pruned, store, errors) {} 65 | 66 | /** 67 | * @param {Vuex} store - Vuex store 68 | * @param {Array} errors 69 | */ 70 | commit (store, errors) {} 71 | } 72 | 73 | export default DeltasCallback 74 | -------------------------------------------------------------------------------- /src/services/eventBus.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import mitt from 'mitt' 19 | 20 | /** 21 | * Global event bus for the app. 22 | * 23 | * @see https://github.com/developit/mitt 24 | */ 25 | export const eventBus = mitt() 26 | -------------------------------------------------------------------------------- /src/services/mock/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'no-console': 'off' 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/services/mock/generate: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | # NOTE: this may be very out of date! 18 | 19 | if ! sed --version 2>/dev/null | grep -q 'GNU' 2>/dev/null; then 20 | echo -e '** GNU sed required **\n' >&2 21 | exit 1 22 | fi 23 | 24 | cd "$(dirname "$0")" || exit 1 25 | CHECK_FILE='json/OnWorkflowTreeDeltasData.json' 26 | 27 | cylc install --flow-name="one" --no-run-name --directory=one 28 | mkdir "${HOME}/cylc-run/one/bin" 29 | cp checkpoint/get_checkpoint.py "${HOME}/cylc-run/one/bin/" 30 | if cylc play "one" --fcp=20000102T0000Z --no-detach; then 31 | cp \ 32 | "${HOME}/cylc-run/one/work/20000102T0000Z/checkpoint/checkpoint" \ 33 | "${CHECK_FILE}" 34 | rm -r "${HOME}/cylc-run/one" 35 | else 36 | echo -e '** Workflow run failed **\n' >&2 37 | exit 1 38 | fi 39 | 40 | sed -i \ 41 | -e "s|$USER|user|g" \ 42 | -e "s|$(hostname)|localhost|g" \ 43 | -e "s|$(hostname | tr '[:upper:]' '[:lower:]')|localhost|g" \ 44 | "${CHECK_FILE}" 45 | -------------------------------------------------------------------------------- /src/services/mock/json/familyProxy.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "familyProxy": { 4 | "runtime": { 5 | "platform": "", 6 | "script": "", 7 | "initScript": "", 8 | "envScript": "", 9 | "errScript": "", 10 | "exitScript": "", 11 | "preScript": "echo Terminus", 12 | "postScript": "", 13 | "workSubDir": "", 14 | "executionPollingIntervals": "", 15 | "executionRetryDelays": "", 16 | "executionTimeLimit": "None", 17 | "submissionPollingIntervals": "", 18 | "submissionRetryDelays": "", 19 | "directives": [], 20 | "environment": [], 21 | "outputs": [], 22 | "runMode": "Live", 23 | "__typename": "Runtime" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/services/mock/json/index.cjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | const IntrospectionQuery = require('./IntrospectionQuery.json') 19 | const taskProxy = require('./taskProxy.json') 20 | const familyProxy = require('./familyProxy.json') 21 | const { one, workflows, Workflow } = require('./workflows/index.cjs') 22 | const { LogData } = require('./logData.cjs') 23 | const { LogFiles, JobState } = require('./logFiles.cjs') 24 | const analysisQuery = require('./analysisQuery.json') 25 | const ganttQuery = require('./ganttQuery.json') 26 | const InfoViewSubscription = require('./infoView.json') 27 | 28 | module.exports = { 29 | IntrospectionQuery, 30 | taskProxy, 31 | familyProxy, 32 | LogData, 33 | LogFiles, 34 | JobState, 35 | App: workflows, 36 | Workflow, 37 | GraphIQLTest: one, 38 | analysisTaskQuery: analysisQuery.taskQuery, 39 | analysisJobQuery: analysisQuery.jobQuery, 40 | analysisQuery, 41 | ganttQuery, 42 | InfoViewSubscription 43 | } 44 | -------------------------------------------------------------------------------- /src/services/mock/json/logFiles.cjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | const { simulatedDelay } = require('./util.cjs') 19 | const { Workflow } = require('./workflows/index.cjs') 20 | 21 | const deletedFile = 'deleted.log' 22 | 23 | const jobLogFiles = [ 24 | 'job.out', 25 | 'job.err', 26 | 'job', 27 | 'job-activity.log', 28 | ] 29 | 30 | const workflowLogFiles = [ 31 | 'scheduler/01-start-01.log', 32 | deletedFile, 33 | ] 34 | 35 | /** 36 | * Return a mock GQL response for list of log files. 37 | * 38 | * @param {{ id: string }} variables 39 | */ 40 | const LogFiles = async ({ id }) => { 41 | await simulatedDelay(500) 42 | return { 43 | data: { 44 | logFiles: { 45 | files: id == null 46 | ? [] 47 | : id.includes('//') ? jobLogFiles : workflowLogFiles 48 | } 49 | } 50 | } 51 | } 52 | 53 | /** 54 | * Return a mock GQL response for job state. 55 | * 56 | * @param {{ id: string }} variables 57 | */ 58 | const JobState = async ({ id, workflowId }) => { 59 | if (!workflowId.startsWith('~')) { 60 | workflowId = `~user/${workflowId}` 61 | } 62 | const { deltas } = Workflow({ workflowId }) 63 | const searchID = id.replace( 64 | /\/NN$/, '' 65 | ).replace( 66 | /\/(\d+)$/, (match, p1) => `/${parseInt(p1)}` // strips leading zeroes 67 | ) 68 | const { state } = deltas?.added?.jobs?.find((job) => job.id.includes(searchID)) ?? {} 69 | await simulatedDelay(500) 70 | return { 71 | data: { 72 | jobs: state ? [{ id, state }] : [] 73 | } 74 | } 75 | } 76 | 77 | module.exports = { 78 | JobState, 79 | LogFiles, 80 | deletedFile, 81 | jobLogFiles, 82 | workflowLogFiles, 83 | } 84 | -------------------------------------------------------------------------------- /src/services/mock/json/taskProxy.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "taskProxy": { 4 | "runtime": { 5 | "platform": "", 6 | "script": "sleep 4\nif [[ \"$CYLC_TASK_SUBMIT_NUMBER\" > 1 ]]; then\n false\nfi", 7 | "initScript": "", 8 | "envScript": "echo Anacreon", 9 | "errScript": "", 10 | "exitScript": "", 11 | "preScript": "echo Terminus", 12 | "postScript": "", 13 | "workSubDir": "", 14 | "executionPollingIntervals": "", 15 | "executionRetryDelays": "PT2M, PT2M, PT2M, PT2M, PT2M, PT5M", 16 | "executionTimeLimit": "PT2H", 17 | "submissionPollingIntervals": "", 18 | "submissionRetryDelays": "", 19 | "directives": [ 20 | { 21 | "key": "--nodes", 22 | "value": "5" 23 | }, 24 | { 25 | "key": "--account", 26 | "value": "h_seldon" 27 | } 28 | ], 29 | "environment": [ 30 | { 31 | "key": "FACTION", 32 | "value": "Foundation" 33 | }, 34 | { 35 | "key": "MAYOR", 36 | "value": "Hardin" 37 | } 38 | ], 39 | "outputs": [], 40 | "runMode": "Live", 41 | "__typename": "Runtime" 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/services/mock/json/userprofile.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "user", 3 | "name": "user", 4 | "display_name": "Anonymous Ganymede", 5 | "initials": null, 6 | "avatar_url": null, 7 | "color": null, 8 | "permissions": [ 9 | "play", 10 | "remove", 11 | "resume", 12 | "setGraphWindowExtent", 13 | "setHoldPoint", 14 | "set", 15 | "setVerbosity", 16 | "stop", 17 | "trigger", 18 | "read", 19 | "broadcast", 20 | "extTrigger", 21 | "pause", 22 | "kill", 23 | "message", 24 | "poll", 25 | "release", 26 | "releaseHoldPoint", 27 | "reload", 28 | "scan", 29 | "clean", 30 | "hold" 31 | ], 32 | "mode": "single user", 33 | "owner": "user", 34 | "extensions": { 35 | "cylc": "/cylc" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/services/mock/json/util.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | const process = require('process') 19 | 20 | /** 21 | * Simulate a loading delay. 22 | * 23 | * NOTE: skips the delay if the CI env var is set so as not to slow down tests. 24 | * 25 | * @param {number} delay - in ms 26 | * @returns {Promise} 27 | */ 28 | function simulatedDelay (delay) { 29 | return process.env.CI 30 | ? Promise.resolve() 31 | : new Promise((resolve) => setTimeout(resolve, delay)) 32 | } 33 | 34 | module.exports = { 35 | simulatedDelay, 36 | } 37 | -------------------------------------------------------------------------------- /src/services/mock/json/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "cylc-flow": "X.Y.Z", 3 | "cylc-uiserver": "X.Y.Z" 4 | } 5 | -------------------------------------------------------------------------------- /src/services/mock/json/workflows/index.cjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | const one = require('./one') 19 | const multi = require('./multi') 20 | 21 | const workflows = [one, ...multi] 22 | 23 | function Workflow ({ workflowId }) { 24 | return workflows.find(({ deltas }) => deltas.id === workflowId) || {} 25 | } 26 | 27 | module.exports = { 28 | one, 29 | workflows, 30 | Workflow, 31 | } 32 | -------------------------------------------------------------------------------- /src/services/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { shallowRef } from 'vue' 19 | import { createSubscriptionClient, createGraphQLUrls } from '@/graphql' 20 | import SubscriptionWorkflowService from '@/services/workflow.service' 21 | import { fetchData } from '@/utils/urls' 22 | 23 | /** 24 | * A plugin that loads the application services. 25 | */ 26 | export default { 27 | /** 28 | * @param {Object} app - Vue application 29 | */ 30 | install (app) { 31 | this._installWorkflowService(app) 32 | 33 | const versionInfo = shallowRef(null) 34 | app.provide('versionInfo', versionInfo) 35 | fetchData('version').then((data) => { versionInfo.value = data }) 36 | }, 37 | 38 | /** 39 | * Creates a workflow service for the application. 40 | * 41 | * The service is available as `vm.$workflowService`. 42 | * 43 | * @private 44 | * @param {Object} app - Vue application 45 | */ 46 | _installWorkflowService (app) { 47 | const graphQLUrls = createGraphQLUrls() 48 | const client = createSubscriptionClient(graphQLUrls.wsUrl) 49 | const workflowService = new SubscriptionWorkflowService( 50 | graphQLUrls.httpUrl, 51 | client 52 | ) 53 | // Composition API: 54 | app.provide('workflowService', workflowService) 55 | // Options API (legacy): 56 | app.config.globalProperties.$workflowService = workflowService 57 | }, 58 | } 59 | -------------------------------------------------------------------------------- /src/services/user.service.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import User from '@/model/User.model' 19 | import { fetchData } from '@/utils/urls' 20 | 21 | /** 22 | * Gets the user profile from the backend server. 23 | * @returns {Promise} 24 | */ 25 | export async function getUserProfile () { 26 | return new User(await fetchData('userprofile')) 27 | } 28 | -------------------------------------------------------------------------------- /src/store/README.md: -------------------------------------------------------------------------------- 1 | # Store 2 | 3 | The stores contain application wide data such as information about the 4 | authenticated user and workflow data. 5 | 6 | The main store is the workflow store which contains all of the information 7 | that the views requested. This is managed by the WorkflowService. 8 | -------------------------------------------------------------------------------- /src/store/app.module.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { markRaw } from 'vue' 19 | 20 | const state = () => ({ 21 | title: null, 22 | workspaceLayouts: new Map(), 23 | }) 24 | 25 | const mutations = { 26 | setTitle (state, title) { 27 | state.title = title 28 | }, 29 | saveLayout ({ workspaceLayouts }, { workflowName, layout, views }) { 30 | // Delete and re-add to keep this FIFO 31 | workspaceLayouts.delete(workflowName) 32 | /* NOTE: use markRaw to prevent proxying of the Lumino layout in particular. 33 | It is not necessary for this saved state to be reactive, and moreover 34 | proxying the layout breaks some parts of the 3rd party Lumino backend. */ 35 | workspaceLayouts.set(workflowName, markRaw({ layout, views })) 36 | if (workspaceLayouts.size > 100) { 37 | const firstKey = workspaceLayouts.keys().next().value 38 | workspaceLayouts.delete(firstKey) 39 | } 40 | } 41 | } 42 | 43 | export const app = { 44 | namespaced: true, 45 | state, 46 | mutations 47 | } 48 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { createStore } from 'vuex' 19 | import storeOptions from '@/store/options' 20 | 21 | export const store = createStore(storeOptions) 22 | -------------------------------------------------------------------------------- /src/store/options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | // Modules 19 | import { app } from './app.module' 20 | import { workflows } from './workflows.module' 21 | import { user } from './user.module' 22 | 23 | // State 24 | const state = () => ({ 25 | /** 26 | * Application alert. 27 | */ 28 | alert: null, 29 | /** 30 | * Whether the application is offline or not. 31 | */ 32 | offline: false, 33 | /** 34 | * Number of references that have set the loading state. 35 | * TODO: we can probably remove it and use a different approach for alerts (see bootstrap toast). 36 | */ 37 | refCount: 0 38 | }) 39 | 40 | // Actions 41 | const actions = { 42 | /** 43 | * 44 | * @param {*} param0 45 | * @param {?import('@/model/Alert.model').Alert} alert 46 | */ 47 | setAlert ({ commit }, alert) { 48 | // log to console when the alert is not null (null can mean to remove the alert) 49 | if (alert?.color === 'error') { 50 | console.error(alert.err) 51 | } else if (alert) { 52 | // eslint-disable-next-line no-console 53 | console.log(alert.err) 54 | } 55 | commit('SET_ALERT', alert) 56 | } 57 | } 58 | 59 | // Mutations 60 | const mutations = { 61 | SET_ALERT (state, alert) { 62 | state.alert = alert 63 | }, 64 | SET_OFFLINE (state, offline) { 65 | state.offline = offline 66 | } 67 | } 68 | 69 | // Create a new store 70 | export default { 71 | modules: { 72 | app, 73 | workflows, 74 | user 75 | }, 76 | actions, 77 | mutations, 78 | state 79 | } 80 | -------------------------------------------------------------------------------- /src/store/user.module.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | export const state = () => ({ 19 | user: null, 20 | }) 21 | 22 | export const mutations = { 23 | SET_USER (state, user) { 24 | state.user = user 25 | } 26 | } 27 | 28 | export const user = { 29 | namespaced: true, 30 | state, 31 | mutations, 32 | } 33 | -------------------------------------------------------------------------------- /src/styles/_util.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | @use "sass:map"; 19 | 20 | @mixin theme-dependent($prop, $color, $intensity, $important: null) { 21 | // Set the specified color property in a way that just works regardless of 22 | // whether the theme is light or dark. 23 | // If light theme, lighten the specified color by the specified intensity; 24 | // if dark, darken. 25 | @each $theme in 'light', 'dark' { 26 | .v-theme--#{$theme} & { 27 | #{$prop}: map.get($color, '#{$theme}en-#{$intensity}') $important; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/styles/cylc/_dashboard.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | .c-dashboard { 19 | /* to align icons in the links section */ 20 | .v-list-item__prepend { 21 | align-self: center; 22 | } 23 | 24 | table { 25 | tbody { 26 | tr { 27 | height: 50px; 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/styles/cylc/_drawer.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | @use "sass:map"; 19 | @use '../settings'; 20 | @use '../util'; 21 | 22 | body.resizing-drawer { 23 | cursor: ew-resize !important; 24 | #c-sidebar, .v-main { 25 | // Prevent Vuetify-provided transitions during resize to ensure responsiveness 26 | transition: none !important; 27 | } 28 | } 29 | 30 | #c-sidebar { 31 | @include util.theme-dependent(background-color, settings.$grey, 4); 32 | 33 | .resize-bar { 34 | display: block; 35 | width: 4px; 36 | height: 100%; 37 | position: absolute; 38 | top: 0; 39 | right: 0; 40 | cursor: ew-resize; 41 | transition: background-color 0.2s; 42 | 43 | &:hover, body.resizing-drawer & { 44 | background: map.get(settings.$blue, "base"); 45 | transition-delay: 0.5s; 46 | } 47 | } 48 | 49 | .v-navigation-drawer__append { 50 | overflow: hidden; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/styles/cylc/_gscan.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | @use "sass:map"; 19 | @use '../settings'; 20 | 21 | @mixin hover { 22 | &:hover { 23 | background-color: map.get(settings.$grey, 'lighten-2'); 24 | } 25 | } 26 | 27 | .c-gscan { 28 | --c-tree-indent: 1rem; 29 | .c-gscan-workflows { 30 | .c-gscan-workflow { 31 | .c-workflow-stopped { 32 | opacity: 0.5; 33 | } 34 | .c-gscan-workflow-name { 35 | white-space: nowrap; 36 | overflow: hidden; 37 | text-overflow: ellipsis; 38 | } 39 | .c-gscan-workflow-states { 40 | .empty-state { 41 | opacity: 0.2; 42 | } 43 | } 44 | 45 | .c-treeitem { 46 | margin: 0.25em 0; 47 | 48 | .c-treeitem { 49 | margin: 0; 50 | } 51 | 52 | .node { 53 | .v-list-item { 54 | @include hover; 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/styles/cylc/_header.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | @use "sass:map"; 19 | @use '../settings'; 20 | @use '../util'; 21 | 22 | .c-header { 23 | display: block; 24 | 25 | @include util.theme-dependent(background-color, settings.$grey, 3); 26 | 27 | .c-environment-info { 28 | font-size: 1rem; 29 | font-weight: map.get(settings.$font-weights, 'regular'); 30 | 31 | .v-chip { 32 | font-size: 1rem; 33 | @include util.theme-dependent(background-color, settings.$grey, 5, !important); 34 | @include util.theme-dependent(border-color, settings.$grey, 1, !important); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/styles/cylc/_markdown.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | .markdown { 19 | p, ul, ol { 20 | &:not(:last-child) { 21 | margin-bottom: 0.8em; 22 | } 23 | } 24 | ul, ol { 25 | padding-left: 24px; 26 | } 27 | .v-theme--light & code { 28 | background-color: rgba(0, 0, 0, 0.05); 29 | } 30 | .v-theme--dark & code { 31 | background-color: rgba(255, 255, 255, 0.1); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/styles/cylc/_menu.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | .c-mutation-menu { 19 | z-index: 2000 !important; 20 | 21 | .v-card-title { 22 | font-size: 1.1rem !important; 23 | line-height: 1.5rem; 24 | white-space: normal; 25 | } 26 | 27 | .c-mutation { 28 | .v-list-item__prepend > .v-icon { 29 | opacity: 1; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/styles/cylc/_mutation_form.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | .c-mutation-dialog { 19 | margin-left: 0; 20 | margin-right: 0; 21 | 22 | .mutation-desc { 23 | .v-expansion-panel-header { 24 | font-size: 1em; 25 | line-height: inherit; 26 | p { 27 | margin-bottom: 0; 28 | } 29 | } 30 | } 31 | 32 | .remove-btn.v-btn--disabled { 33 | opacity: 0.12; 34 | } 35 | 36 | .c-key-val { 37 | .v-input .v-field--disabled { 38 | opacity: 0.6; 39 | } 40 | 41 | .v-col-auto { 42 | display: flex; 43 | justify-content: center; 44 | align-items: center; 45 | $margin: 8px; 46 | margin-left: $margin; 47 | margin-right: $margin; 48 | 49 | &:first-child { 50 | margin-left: 0; 51 | } 52 | &:last-child { 53 | margin-right: 0; 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/styles/cylc/_table.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | .c-table { 19 | .v-data-table { 20 | font-size: 0.9em; 21 | } 22 | 23 | th, td { 24 | white-space: nowrap; 25 | } 26 | 27 | .c-task, .c-job { 28 | display: flex; 29 | align-items: center; 30 | height: 100%; 31 | font-size: 1.2em; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/styles/cylc/_toolbar.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | @use '../settings'; 19 | @use 'variables'; 20 | 21 | .c-toolbar { 22 | $icon-size: 24px; 23 | 24 | .v-toolbar__content > *:not(:last-child) { 25 | margin-right: 0.5rem; 26 | } 27 | 28 | .c-toolbar-title { 29 | flex: 0 1 auto; 30 | } 31 | 32 | .status-msg { 33 | color: variables.$font-dimished-color; 34 | white-space: nowrap; 35 | text-overflow: ellipsis; 36 | overflow: hidden; 37 | // make this shrink more than title (can see status in mutation menu if needed): 38 | flex-shrink: 10; 39 | // override vuetify .text-* class letter spacing: 40 | letter-spacing: normal !important; 41 | } 42 | 43 | .v-icon { 44 | font-size: $icon-size; 45 | } 46 | 47 | .add-view { 48 | // make this shrink before anything else in the toolbar: 49 | flex-shrink: 100000; 50 | 51 | .v-btn__content { 52 | // force overflow onto a new line where we can hide it: 53 | max-height: $icon-size; 54 | min-width: $icon-size; 55 | flex-wrap: wrap; 56 | overflow: hidden; 57 | row-gap: 30px; 58 | justify-content: flex-end; 59 | column-gap: 0.5rem; 60 | 61 | .label { 62 | // this only gets clipped after getting hidden (forced to overflow on new line) 63 | white-space: nowrap; 64 | text-overflow: clip; 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/styles/cylc/_tooltip.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | .v-tooltip .v-overlay__content { 19 | max-width: 600px !important; 20 | // Increase default opacity of bg: 21 | background: rgba(var(--v-theme-surface-variant), 0.9); 22 | } 23 | -------------------------------------------------------------------------------- /src/styles/cylc/_user_profile.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | .c-user-profile { 19 | .v-field--disabled { 20 | // Increase opactity to make it easier to read disabled fields 21 | opacity: 0.6; 22 | } 23 | .v-container .v-row { 24 | margin-bottom: 1em; 25 | } 26 | table.c-job-state-table { 27 | td, th { 28 | padding: 0.2em; 29 | text-align: center; 30 | } 31 | 32 | .v-radio { 33 | /* allow the element to center */ 34 | display: inline-block; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/styles/cylc/_variables.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | $font-dimished-color: #707070; 19 | -------------------------------------------------------------------------------- /src/styles/cylc/_warning.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | .c-warn { 19 | svg { 20 | width: 1em; 21 | height: 1em; 22 | 23 | path { 24 | /* TODO - move colours to job or app theme */ 25 | stroke: rgb(170,170,170); 26 | fill: rgb(150,150,150); 27 | } 28 | 29 | path.active { 30 | stroke: rgb(255,122,122); 31 | fill: rgb(248,197,102); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/styles/cylc/_workflow.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | @use '../settings'; 19 | 20 | .workflow-panel { 21 | .main { 22 | display: flex; 23 | .content { 24 | min-width: 300px; 25 | min-height: 300px; 26 | display: flex; 27 | flex-direction: column; 28 | padding: 0; 29 | border: 1px solid #C0C0C0; 30 | border-top: none; 31 | background: white; 32 | position: relative; 33 | overflow: auto; 34 | } 35 | 36 | .lm-BoxPanel { 37 | flex: 1 1 auto; 38 | .lm-TabBar-content { 39 | padding-left: 0; 40 | } 41 | } 42 | } 43 | } 44 | 45 | .lm-TabBar-tab { 46 | display: flex; 47 | align-items: center; 48 | border-bottom: 1px solid #C0C0C0; 49 | } 50 | .lm-TabBar-tabLabel { 51 | font-family: settings.$body-font-family; 52 | font-size: 1rem; 53 | } 54 | .lm-TabBar-tabCloseIcon { 55 | color: inherit; 56 | cursor: pointer; 57 | } 58 | .lm-TabBar-tabCloseIcon:before { 59 | content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' style='width:24px;height:24px' viewBox='0 0 24 24'%3E%3Cpath fill='currentColor' d='M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z' /%3E%3C/svg%3E") !important; 60 | display: block; 61 | width: 1rem; 62 | height: 1rem; 63 | } 64 | -------------------------------------------------------------------------------- /src/styles/settings.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | // https://vuetifyjs.com/en/features/sass-variables/ 19 | 20 | @forward 'vuetify/settings'; 21 | 22 | /* If we need to override any of the Vuetify variables, we can do so e.g. 23 | @forward 'vuetify/settings' with ( 24 | $color-pack: false, 25 | ); 26 | */ 27 | -------------------------------------------------------------------------------- /src/utils/font-size.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | // Module for font size functions and constants. 19 | 20 | /** Font size increment in px */ 21 | export const INCREMENT = 2 22 | 23 | /** 24 | * Sets the font-size to a value. 25 | * 26 | * @param {?string} size - Value with units given (doesn't matter which unit). 27 | * If null then reset to default. 28 | */ 29 | export function resetFontSize (size = null) { 30 | localStorage.fontSize = size 31 | document.documentElement.style.fontSize = size 32 | } 33 | 34 | export function decreaseFontSize () { 35 | resetFontSize(`${getCurrentFontSize() - INCREMENT}px`) 36 | } 37 | 38 | export function increaseFontSize () { 39 | resetFontSize(`${getCurrentFontSize() + INCREMENT}px`) 40 | } 41 | 42 | /** 43 | * Get HTML element (computed) font size. 44 | * 45 | * @returns {number} current font size in px 46 | */ 47 | export function getCurrentFontSize () { 48 | const fontSize = window.getComputedStyle(document.documentElement).fontSize // px 49 | return parseFloat(fontSize) 50 | } 51 | -------------------------------------------------------------------------------- /src/utils/graph-utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /** 19 | * Convert graphviz edge bezier curve in dot format to SVG path . 20 | * 21 | * @param {string} pos - `pos` attribute of a graph edge in dot format. 22 | * @returns {string} The SVG path. 23 | */ 24 | export function posToPath (pos) { 25 | // pos starts with `e,` followed by a list of coordinates 26 | const parts = pos.substring(2).split(' ') 27 | // the last point comes first, followed by the others in order I.E: 28 | // -1, 0, 1, 2, ... -3, -2 29 | const [last, first] = parts.splice(0, 2) 30 | const path = parts.reduce( 31 | (acc, part) => `${acc} ${getCoord(part)},`, 32 | `M${getCoord(first)} C` 33 | ) 34 | return `${path} L ${getCoord(last)}` 35 | } 36 | 37 | /** 38 | * Convert dotcode `pos` coordinate to SVG path coordinate. 39 | * 40 | * @param {string} posCoord - A coordinate in dot format. 41 | * @returns {string} 42 | */ 43 | export function getCoord (posCoord) { 44 | const [x, y] = posCoord.split(',').map(parseFloat) 45 | return `${x} ${-y}` 46 | } 47 | 48 | /** 49 | * Calculate a non-cryptographic hash value for a given string. 50 | * 51 | * @param {string} string 52 | * @returns {number} 53 | */ 54 | export function nonCryptoHash (string) { 55 | let hash = 0 56 | let i 57 | let chr 58 | if (string.length === 0) return hash 59 | for (i = 0; i < string.length; i++) { 60 | chr = string.charCodeAt(i) 61 | hash = ((hash << 5) - hash) + chr 62 | hash |= 0 // Convert to 32bit integer 63 | } 64 | return hash 65 | } 66 | -------------------------------------------------------------------------------- /src/utils/icons.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /** 19 | * SVG icons for use in the Cylc UI: 20 | * - Centralise icons for use throughout multiple components. 21 | * - Define custom icons. 22 | * - Reformat icons for other source. 23 | * 24 | * Note, the `` component expects icons in the MDI format i.e.: 25 | * - A string representing an SVG path. 26 | * - Consisting of a bezier curve (e.g. `M 0,0 C 1,1 Z`). 27 | * - That fits within a 24px box. 28 | */ 29 | 30 | import { siJupyter } from 'simple-icons' 31 | 32 | export const jupyterLogo = siJupyter.svg.replace(/.*d="(.*)".*/, '$1') 33 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { ref, watch } from 'vue' 19 | 20 | /** 21 | * Watch source until it is truthy, then call the callback (and stop watching). 22 | * 23 | * Immediate by default. 24 | * 25 | * @param {import('vue').WatchSource} source 26 | * @param {import('vue').WatchCallback} callback 27 | * @param {import('vue').WatchOptions?} options 28 | */ 29 | export function when (source, callback, options = {}) { 30 | if (source.value) { 31 | callback() 32 | return 33 | } 34 | const unwatch = watch( 35 | source, 36 | (ready) => { 37 | if (ready) { 38 | unwatch() 39 | callback() 40 | } 41 | }, 42 | options 43 | ) 44 | } 45 | 46 | /** 47 | * Return a promise that resolves when the source becomes truthy. 48 | * 49 | * Awaitable version of when(). 50 | * 51 | * @param {import('vue').WatchSource} source 52 | * @param {import('vue').WatchOptions?} options 53 | * @returns {Promise} 54 | */ 55 | export function until (source, options = {}) { 56 | return new Promise((resolve) => { 57 | when(source, resolve, options) 58 | }) 59 | } 60 | 61 | /** 62 | * Return a ref that is permanently set to true when the source becomes truthy. 63 | * 64 | * @param {import('vue').WatchSource} source 65 | * @param {import('vue').WatchOptions?} options 66 | * @returns {import('vue').Ref} 67 | */ 68 | export function once (source, options = {}) { 69 | const _ref = ref(false) 70 | when( 71 | source, 72 | () => { _ref.value = true }, 73 | options 74 | ) 75 | return _ref 76 | } 77 | -------------------------------------------------------------------------------- /src/utils/initialOptions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /* The `initialOptions` prop is used by views to specify any options such as 19 | filters, inputs, toggles etc. that can be loaded when creating the view. 20 | This is used for saving the view state along with the tab layout. */ 21 | 22 | import { ref, watch } from 'vue' 23 | 24 | /** 25 | * @callback emitter 26 | * @param {updateInitialOptionsEvent} event 27 | * @param {...any} args 28 | */ 29 | 30 | /** initialOptions prop */ 31 | export const initialOptions = { 32 | type: Object, 33 | required: false, 34 | default: () => ({}) 35 | } 36 | 37 | export const updateInitialOptionsEvent = 'update:initialOptions' 38 | 39 | /** 40 | * Return a ref that is bound to a sub-property of the initialOptions prop for a view. 41 | * 42 | * When the ref's value changes, the initialOptions prop is updated with the new value. 43 | * 44 | * @param {string} name 45 | * @param {{ 46 | * props: { initialOptions: Record }, 47 | * emit: emitter, 48 | * }} param1 - component context 49 | * @param {T=} defaultValue 50 | * @returns {import('vue').Ref} 51 | */ 52 | export function useInitialOptions (name, { props, emit }, defaultValue) { 53 | const _ref = ref(props.initialOptions[name] ?? defaultValue) 54 | watch( 55 | _ref, 56 | (val, old) => emit( 57 | updateInitialOptionsEvent, 58 | { ...props.initialOptions, [name]: val } 59 | ), 60 | { immediate: true, deep: true } 61 | ) 62 | return _ref 63 | } 64 | -------------------------------------------------------------------------------- /src/utils/toolbar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { computed, ref } from 'vue' 19 | import { useDisplay } from 'vuetify' 20 | 21 | /** Height in px */ 22 | export const toolbarHeight = 48 23 | 24 | /** Global state of navigation drawer visibility */ 25 | const drawer = ref(false) 26 | 27 | function toggleDrawer () { 28 | drawer.value = !drawer.value 29 | } 30 | 31 | /** Composable that provides the global state of the navigation drawer visibility. */ 32 | export function useDrawer () { 33 | return { 34 | drawer, 35 | toggleDrawer, 36 | } 37 | } 38 | 39 | /** 40 | * Composable that returns a computed property for whether we should show 41 | * the hamburger nav drawer button. 42 | */ 43 | export function useNavBtn () { 44 | const { mobile } = useDisplay() 45 | return { 46 | showNavBtn: computed(() => mobile.value || !drawer.value), 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/utils/viewToolbar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | 17 | /** 18 | * Scale icon size to button size. 19 | * https://github.com/vuetifyjs/vuetify/issues/16288 20 | * 21 | * @param {string} btnSize - button size 22 | * @returns {string=} font size 23 | */ 24 | export function btnIconFontSize (btnSize) { 25 | const size = parseInt(btnSize) 26 | if (Number.isNaN(size)) { 27 | // do nothing for named sizes ('small', 'large', etc.) 28 | return undefined 29 | } 30 | // Round to even px then convert to rem 31 | return `${2 * Math.round(0.2 * size) / 16}rem` 32 | } 33 | 34 | export const btnProps = (size) => ({ 35 | icon: true, 36 | variant: 'text', 37 | size, 38 | style: { 39 | fontSize: btnIconFontSize(size) 40 | }, 41 | }) 42 | -------------------------------------------------------------------------------- /src/views/GraphiQL.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 21 | 22 | 64 | 65 | 77 | -------------------------------------------------------------------------------- /src/views/NoAuth.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 27 | -------------------------------------------------------------------------------- /src/views/NotFound.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 36 | 37 | 46 | -------------------------------------------------------------------------------- /tests/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | module.exports = { 19 | env: { 20 | mocha: true 21 | }, 22 | plugins: [ 23 | 'no-only-tests' 24 | ], 25 | rules: { 26 | 'no-console': 'off', 27 | 'no-only-tests/no-only-tests': 'error', 28 | }, 29 | overrides: [ 30 | { 31 | files: ['*.spec.js', '*.cy.js'], 32 | rules: { 33 | // Don't complain about certain chai assertions: 34 | 'no-unused-expressions': 'off' 35 | } 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /tests/component/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | module.exports = { 19 | plugins: [ 20 | 'cypress' 21 | ], 22 | env: { 23 | 'cypress/globals': true 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /tests/component/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /tests/component/specs/copyBtn.cy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import CopyBtn from '@/components/core/CopyBtn.vue' 19 | 20 | describe('Copy Button component', () => { 21 | function mountCopyBtn (props) { 22 | cy.vmount(CopyBtn, { props }) 23 | cy.addVuetifyStyles(cy) 24 | } 25 | it('copies text to the clipboard', { browser: 'electron' }, () => { 26 | // (Access to the clipboard in Cypress only reliably works in Electron) 27 | const text = 'I am writing this test while on a train' 28 | mountCopyBtn({ text }) 29 | 30 | cy.get('[data-cy=copy-to-clipboard]').as('copyBtn') 31 | .find('svg path:first').as('copyIcon') 32 | .then(($el) => { 33 | const icon = $el.attr('d') 34 | expect(icon.length).to.be.greaterThan(0) 35 | 36 | cy.get('@copyBtn') 37 | .click() 38 | // icon should change to indicate successful copy: 39 | .get('@copyIcon') 40 | .then(($el) => $el.attr('d')) 41 | .should('have.length.greaterThan', 0) 42 | .should('not.equal', icon) 43 | // clipboard should contain the text: 44 | cy.window().its('navigator.clipboard') 45 | .then((clip) => clip.readText()) 46 | .should('equal', text) 47 | }) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /tests/component/specs/utils/task.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | // mean elapsed time for the task in seconds 19 | export const MEAN_ELAPSED_TIME = 10000 20 | 21 | export function getStartTime (percent) { 22 | return new Date( 23 | // the current time in ms 24 | Date.now() - 25 | // minus the elapsed time in ms 26 | ( 27 | (MEAN_ELAPSED_TIME * 1000) * 28 | (percent / 100) 29 | ) 30 | ).toISOString() 31 | } 32 | -------------------------------------------------------------------------------- /tests/component/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /tests/component/support/component-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Components App 8 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/component/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/component.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | import '@cypress/code-coverage/support' 19 | 20 | // Import CSS 21 | import '@/styles/index.scss' 22 | 23 | import { createVuetify } from 'vuetify' 24 | import { mount } from 'cypress/vue' 25 | import { vuetifyOptions } from '@/plugins/vuetify' 26 | 27 | // vanilla mount function 28 | // e.g. cy.mount(MyComponent) 29 | Cypress.Commands.add('mount', mount) 30 | 31 | // mount function with Vuetify installed 32 | // e.g. cy.mount(MyVuetifyComponent) 33 | // see also addVuetifyStyles 34 | Cypress.Commands.add('vmount', (component, options = {}) => { 35 | return mount( 36 | component, 37 | { 38 | global: { 39 | plugins: [createVuetify(vuetifyOptions)] 40 | }, 41 | ...options 42 | } 43 | ) 44 | }) 45 | 46 | // add required classes to the Cypress root element 47 | // e.g. cy.addVuetifyStyles(cy) 48 | // use this if you need Vuetify styles to be applied 49 | Cypress.Commands.add('addVuetifyStyles', (cy) => { 50 | cy 51 | .document() 52 | .then((foo) => { 53 | const classList = foo.children[0].classList 54 | if (!classList.contains('v-application')) { 55 | foo.children[0].classList.add( 56 | 'v-application', 57 | 'v-application--is-ltr', 58 | 'theme--light', 59 | 'job_theme--default' 60 | ) 61 | } 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /tests/e2e/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | module.exports = { 19 | plugins: [ 20 | 'cypress' 21 | ], 22 | env: { 23 | 'cypress/globals': true 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /tests/e2e/specs/info.cy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | describe('Info View', () => { 19 | it('works', () => { 20 | // test opening the "Info View" from a task in the "Tree View" 21 | cy.visit('/#/workspace/one') 22 | // click on task 23 | .get('.node-data-task [data-c-interactive]:first') 24 | .click({ force: true }) 25 | 26 | // from the menu select the "Info" psudo-mutation 27 | .get('.v-list-item') 28 | .contains('Info') 29 | .click({ force: true }) 30 | 31 | // the info view should open 32 | .get('.c-info').should('be.visible') 33 | 34 | // the metadata panel should be expanded by default 35 | .find('.v-expansion-panel--active') 36 | .should('have.length', 1) 37 | .should('have.class', 'metadata-panel') 38 | 39 | // other panels should expand when clicked 40 | .get('.c-info .v-expansion-panel:nth-child(2)') 41 | .find('button') 42 | .click({ force: true }) 43 | .get('.v-expansion-panel--active') 44 | .should('have.length', 2) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /tests/e2e/specs/layout.cy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | describe('Default layout', () => { 19 | it('Should display errors from children elements captured at the Default layout level', () => { 20 | const errMsg = 'Error raised in Cypress stub!' 21 | // visit any page first, so that we create the window.app reference 22 | cy.visit('/#/guide') 23 | cy.get('[data-cy=alert-snack]') 24 | .should('not.exist') 25 | cy.window().its('app.$workflowService').then(service => { 26 | // mock service so that it returns an error 27 | cy.stub(service, 'subscribe').callsFake(() => { 28 | throw new Error('Error raised in Cypress stub!') 29 | }) 30 | // now visit dashboard, that calls service.subscribe, which will raise an uncaught error... 31 | cy.get('nav') 32 | .contains('.v-list-item', 'Dashboard') 33 | .click({ force: true }) 34 | cy.get('[data-cy=alert-snack]') 35 | .contains(errMsg) 36 | }) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /tests/e2e/specs/url.cy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | describe('URL handling', () => { 19 | it('strips token from querystring', () => { 20 | cy.visit('/?token=l0r3m1p5um#/') 21 | .get('#app') 22 | cy.url() 23 | .should('not.contain', '?') 24 | .should('not.contain', 'token') 25 | .should('not.contain', 'l0r3m1p5um') 26 | }) 27 | 28 | it('preserves other params in the querystring', () => { 29 | // Other query params moved to after hash so vue-router can access 30 | cy.visit('/?a=1&token=42&b=2#/') 31 | .get('#app') 32 | cy.url() 33 | .should('not.contain', 'token') 34 | .then((url) => { 35 | expect(url.endsWith('?a=1&b=2')).to.be.true 36 | }) 37 | }) 38 | 39 | it('reroutes to noAuth page if user isnt authorised', () => { 40 | cy.intercept('/userprofile', { 41 | body: { 42 | username: 'user', 43 | permissions: [], 44 | mode: 'single user', 45 | owner: 'user' 46 | } 47 | }) 48 | cy.visit('/#').get('#app') 49 | cy.url().should('contain', 'noAuth') 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /tests/e2e/specs/workflowsTable.cy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | describe('Workflows-Table view', () => { 19 | beforeEach(() => { 20 | cy.visit('/#/workflow-table') 21 | }) 22 | 23 | it("Opens mutation menu when clicking on a workflow's icon", () => { 24 | cy.get('[data-cy=workflows-table] [data-c-interactive]:first') 25 | .click() 26 | .get('.c-mutation-menu-list:first') 27 | .should('be.visible') 28 | }) 29 | 30 | it('Opens workspace view when clicking on workflow', () => { 31 | cy.get('[data-cy=workflows-table]') 32 | .contains('td', 'one') 33 | .click() 34 | .get('[data-cy=workspace-view]') 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | // *********************************************** 19 | // This example commands.js shows you how to 20 | // create various custom commands and overwrite 21 | // existing commands. 22 | // 23 | // For more comprehensive examples of custom 24 | // commands please read more here: 25 | // https://on.cypress.io/custom-commands 26 | // *********************************************** 27 | // 28 | // 29 | // -- This is a parent command -- 30 | // Cypress.Commands.add("login", (email, password) => { ... }) 31 | // 32 | // 33 | // -- This is a child command -- 34 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 35 | // 36 | // 37 | // -- This is a dual command -- 38 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 39 | // 40 | // 41 | // -- This is will overwrite an existing command -- 42 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 43 | -------------------------------------------------------------------------------- /tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | // *********************************************************** 19 | // This example support/index.js is processed and 20 | // loaded automatically before your test files. 21 | // 22 | // This is a great place to put global configuration and 23 | // behavior that modifies Cypress. 24 | // 25 | // You can change the location of this file or turn off 26 | // automatically serving support files with the 27 | // 'supportFile' configuration option. 28 | // 29 | // You can read more here: 30 | // https://on.cypress.io/configuration 31 | // *********************************************************** 32 | 33 | // For test coverage 34 | import '@cypress/code-coverage/support' 35 | 36 | // Import commands.js using ES2015 syntax: 37 | import './commands' 38 | 39 | Cypress.on('uncaught:exception', (err) => { 40 | if ( 41 | err.message.includes('ResizeObserver loop completed with undelivered notifications') || 42 | err.message.includes('ResizeObserver loop limit exceeded') 43 | ) { 44 | // Vuetify or something sometimes causes this error, but it is symptomless. 45 | // See also https://stackoverflow.com/a/50387233/3217306 46 | return false 47 | } 48 | }) 49 | -------------------------------------------------------------------------------- /tests/unit/components/cylc/commandMenu/menu.vue.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { mount } from '@vue/test-utils' 19 | import Menu, { getLogFileForNode } from '@/components/cylc/commandMenu/Menu.vue' 20 | import { Tokens } from '@/utils/uid' 21 | import { 22 | simpleTaskNode, 23 | simpleJobNode, 24 | simpleWorkflowNode, 25 | } from '$tests/unit/components/cylc/tree/tree.data' 26 | 27 | describe('Command menu', () => { 28 | it('has a title with the node ID excluding the username', () => { 29 | const wrapper = mount(Menu, { shallow: true }) 30 | wrapper.vm.target = { dataset: {} } 31 | const id = '~neil.armstrong/apollo//11/eagle' 32 | wrapper.vm.node = { 33 | id, 34 | tokens: new Tokens(id) 35 | } 36 | expect(wrapper.vm.title).toEqual('apollo//11/eagle') 37 | }) 38 | 39 | describe('getLogFileForNode()', () => { 40 | it.for([ 41 | { 42 | testID: 'job node', 43 | node: simpleJobNode, 44 | expected: 'job.err', 45 | }, 46 | { 47 | testID: 'task node with multiple jobs (picks latest)', 48 | node: simpleTaskNode, 49 | expected: 'job-activity.log', 50 | }, 51 | { 52 | testID: 'workflow node', 53 | node: simpleWorkflowNode, 54 | expected: undefined 55 | } 56 | ])('$testID', ({ node, expected }) => { 57 | expect(getLogFileForNode(node)).toEqual(expected) 58 | }) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /tests/unit/components/cylc/common/flowNumsChip.vue.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { mount } from '@vue/test-utils' 19 | import FlowNumsChip from '@/components/cylc/common/FlowNumsChip.vue' 20 | 21 | describe('Flow Nums Chip component', () => { 22 | describe('showFlowNums', () => { 23 | it.each([ 24 | [undefined, undefined], 25 | ['[]', false], 26 | ['[1]', false], 27 | ['[2]', true], 28 | ['[1, 2]', true], 29 | ])('%s -> %s', (flowNums, expected) => { 30 | const wrapper = mount(FlowNumsChip, { 31 | props: { flowNums }, 32 | shallow: true, 33 | }) 34 | expect(wrapper.vm.showFlowNums).toEqual(expected) 35 | }) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /tests/unit/components/cylc/common/sort.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { DEFAULT_COMPARATOR } from '@/components/cylc/common/sort' 19 | 20 | describe('DEFAULT_COMPARATOR', () => { 21 | it.each([ 22 | ['a', 'b', -1], 23 | ['b', 'a', 1], 24 | ['a', 'a', 0], 25 | ['A', 'a', 0], 26 | ['A', 'b', -1], 27 | ['1', '2', -1], 28 | ['01', '1', 0], 29 | ['20000101T0000Z', '20000101T0001Z', -1], 30 | ])('(%o, %o) -> %o', (a, b, expected) => { 31 | expect(DEFAULT_COMPARATOR(a, b)).toEqual(expected) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /tests/unit/components/cylc/gscan/workflowicon.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { shallowMount } from '@vue/test-utils' 19 | import WorkflowIcon from '@/components/cylc/gscan/WorkflowIcon.vue' 20 | import WorkflowState from '@/model/WorkflowState.model' 21 | import { createVuetify } from 'vuetify' 22 | import { mdiHelpCircle } from '@mdi/js' 23 | 24 | const vuetify = createVuetify() 25 | 26 | describe('WorkflowIcon', () => { 27 | it.each([ 28 | { 29 | status: '', 30 | expected: mdiHelpCircle 31 | }, 32 | { 33 | status: WorkflowState.STOPPED.name, 34 | expected: WorkflowState.STOPPED.icon 35 | }, 36 | ])('uses the right icon for state: $status', ({ status, expected }) => { 37 | const wrapper = shallowMount(WorkflowIcon, { 38 | global: { 39 | plugins: [vuetify] 40 | }, 41 | props: { 42 | status, 43 | } 44 | }) 45 | expect(wrapper.vm.getIcon()).to.equal(expected) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /tests/unit/components/cylc/job.vue.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { mount } from '@vue/test-utils' 19 | import Job from '@/components/cylc/Job.vue' 20 | 21 | describe('Job component', () => { 22 | it('should initialize props', () => { 23 | const wrapper = mount(Job, { 24 | props: { 25 | status: '', 26 | multiple: false 27 | } 28 | }) 29 | expect(wrapper.element.className).to.equal('c-job') 30 | expect(wrapper.get('.job').element.childElementCount).to.equal(1) 31 | }) 32 | it('should add a new class for multiple jobs', () => { 33 | const wrapper = mount(Job, { 34 | props: { 35 | status: 'failed', 36 | 'previous-state': 'submit-failed' 37 | } 38 | }) 39 | // The shadow is added as an extra child element for the Job SVG (two rects). 40 | expect(wrapper.get('.job').element.childElementCount).to.equal(2) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /tests/unit/components/cylc/log/log.vue.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { shallowMount } from '@vue/test-utils' 19 | import Log from '@/components/cylc/log/Log.vue' 20 | 21 | describe('Log component', () => { 22 | const wrapper = shallowMount(Log, { 23 | props: { 24 | logs: [], 25 | }, 26 | }) 27 | 28 | describe.each([ 29 | { line: '' }, 30 | { line: ' ' }, 31 | { line: 'Hello, Mr. Thompson' }, 32 | { line: '1969-07-20T20:17:00 the eagle has landed', expected: 'the eagle has landed' }, 33 | { line: '2038-01-19T03:14:07Z INFO - oops!', expected: 'INFO - oops!' }, 34 | { line: '2038-01-19T03:14:07+02:15 ', expected: '' }, 35 | { line: '2038-01-19T03:14:07-12:03 meow', expected: ' meow' }, 36 | { line: 'INIT_TIME=2023-04-27T13:53:09+01:00' }, 37 | 38 | ])('stripTimestamp($line)', ({ line, expected }) => { 39 | expected ??= line 40 | it(`returns '${expected}'`, () => { 41 | expect(wrapper.vm.stripTimestamp(line)).toEqual(expected) 42 | }) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /tests/unit/components/cylc/table/sort.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { 19 | datetimeComparator, 20 | numberComparator, 21 | } from '@/components/cylc/table/sort' 22 | 23 | describe('datetimeComparator()', () => { 24 | it('should rank datetime strings appropriately', () => { 25 | expect(datetimeComparator('2022-09-26T12:30:00Z', '2022-09-26T12:30:01Z')).to.be.lessThan(0) 26 | expect(datetimeComparator('2022-09-26T12:30:01Z', '2022-09-26T12:30:00Z')).to.be.greaterThan(0) 27 | expect(datetimeComparator('2022-09-26T12:30:00Z', '2022-09-26T12:30:00Z')).to.equal(0) 28 | }) 29 | }) 30 | 31 | describe('numberComparator()', () => { 32 | it.each([ 33 | [1, 2, -1], 34 | [2, 1, 1], 35 | [1, 1, 0], 36 | ])('(%o, %o) -> %o', (a, b, expected) => { 37 | expect(numberComparator(a, b)).toEqual(expected) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /tests/unit/components/cylc/toolbar.vue.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { mount } from '@vue/test-utils' 19 | import { createVuetify } from 'vuetify' 20 | import { createStore } from 'vuex' 21 | import Toolbar from '@/components/cylc/Toolbar.vue' 22 | import WorkflowState from '@/model/WorkflowState.model' 23 | import storeOptions from '@/store/options' 24 | 25 | describe('Toolbar component', () => { 26 | const vuetify = createVuetify({ 27 | display: {} 28 | }) 29 | const $route = { 30 | name: 'testRoute' 31 | } 32 | let store 33 | let mountFunction 34 | 35 | beforeEach(() => { 36 | store = createStore(storeOptions) 37 | mountFunction = () => mount(Toolbar, { 38 | global: { 39 | plugins: [vuetify, store], 40 | mocks: { $route } 41 | } 42 | }) 43 | 44 | store.state.workflows.workflows = [ 45 | { 46 | id: 'user/id', 47 | name: 'test', 48 | status: WorkflowState.RUNNING.name 49 | } 50 | ] 51 | }) 52 | 53 | it('should mount the component', async () => { 54 | const wrapper = mountFunction() 55 | await wrapper.vm.$nextTick() 56 | expect(wrapper.vm.$el).to.exist 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /tests/unit/mixins/graphql.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { shallowMount } from '@vue/test-utils' 19 | import { createStore } from 'vuex' 20 | import User from '@/model/User.model' 21 | import storeOptions from '@/store/options' 22 | import graphqlMixin from '@/mixins/graphql' 23 | 24 | describe('GraphQL mixin', () => { 25 | const store = createStore(storeOptions) 26 | it('should create the GraphQL Query variables', () => { 27 | const user = new User({ username: 'cylc', permissions: [], owner: 'owner' }) 28 | store.commit('user/SET_USER', user) 29 | const workflowName = 'test' 30 | const Component = { 31 | mixins: [graphqlMixin], 32 | render () {} 33 | } 34 | const component = shallowMount(Component, { 35 | global: { 36 | plugins: [store] 37 | }, 38 | props: { 39 | workflowName 40 | } 41 | }) 42 | const variables = component.vm.variables 43 | const expected = { 44 | workflowId: `~${user.owner}/${workflowName}` 45 | } 46 | expect(variables).to.deep.equal(expected) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /tests/unit/model/alert.model.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { Alert } from '@/model/Alert.model' 19 | 20 | describe('Alert model', () => { 21 | describe('constructor', () => { 22 | it('should be created', () => { 23 | const err = 'my error' 24 | const color = 'success' 25 | let alert = new Alert(err, color) 26 | expect(alert.err).to.equal(err) 27 | expect(alert.color).to.equal(color) 28 | expect(alert.text).to.equal(err) 29 | const msg = 'a custom messsage' 30 | alert = new Alert(err, color, msg) 31 | expect(alert.text).to.equal(msg) 32 | }) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /tests/unit/model/jobstate.model.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { getJobLogFileFromState } from '@/model/JobState.model' 19 | 20 | describe('Job state', () => { 21 | it.each([ 22 | ['submitted', 'job-activity.log'], 23 | ['submit-failed', 'job-activity.log'], 24 | ['running', 'job.out'], 25 | ['succeeded', 'job.out'], 26 | ['failed', 'job.err'], 27 | ['pontifex', undefined], 28 | [undefined, undefined], 29 | ])('getJobLogFileFromState(%s) -> %s', (state, expected) => { 30 | expect(getJobLogFileFromState(state)).toEqual(expected) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /tests/unit/model/subscriptionquery.model.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import gql from 'graphql-tag' 19 | import DeltasCallback from '@/services/callbacks' 20 | import SubscriptionQuery from '@/model/SubscriptionQuery.model' 21 | 22 | describe('SubscriptionQuery model', () => { 23 | describe('constructor', () => { 24 | it('should be created', () => { 25 | const query = gql`query { workflow { id } }` 26 | const variables = { 27 | workflowId: '~cylc/cylc' 28 | } 29 | const name = 'root' 30 | const callbacks = [ 31 | new DeltasCallback() 32 | ] 33 | const isDelta = true 34 | const isGlobalCallback = true 35 | const subscriptionQuery = new SubscriptionQuery( 36 | query, variables, name, callbacks, isDelta, isGlobalCallback) 37 | expect(subscriptionQuery.query).to.equal(query) 38 | expect(subscriptionQuery.variables).to.deep.equal(variables) 39 | expect(subscriptionQuery.name).to.equal(name) 40 | expect(subscriptionQuery.callbacks).to.deep.equal(callbacks) 41 | expect(subscriptionQuery.isDelta).to.deep.equal(isDelta) 42 | expect(subscriptionQuery.isGlobalCallback).to.deep.equal(isGlobalCallback) 43 | }) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /tests/unit/model/taskstate.model.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { 19 | TaskState, 20 | TaskStateUserOrder 21 | } from '@/model/TaskState.model' 22 | 23 | describe('TaskState model', () => { 24 | describe('TaskStateUserOrder', () => { 25 | it('contains the same states as TaskState in a different order', () => { 26 | expect([...TaskState].sort()).to.deep.equal(TaskStateUserOrder.sort()) 27 | }) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /tests/unit/router/index.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { getPageTitle } from '@/router' 19 | 20 | describe('getPageTitle()', () => { 21 | it.each([ 22 | { 23 | route: { 24 | meta: {}, 25 | }, 26 | expected: 'Cylc UI', 27 | id: 'default', 28 | }, 29 | { 30 | route: { 31 | meta: { title: 'Page Title' }, 32 | params: { foo: 22 }, 33 | }, 34 | expected: 'Cylc UI | Page Title', 35 | id: 'meta.title', 36 | }, 37 | { 38 | route: { 39 | meta: { getTitle: (params) => `Dynamic Page Title ${params.foo}` }, 40 | params: { foo: 22 }, 41 | }, 42 | expected: 'Cylc UI | Dynamic Page Title 22', 43 | id: 'meta.getTitle', 44 | }, 45 | ])('$id', ({ route, expected, id }) => { 46 | expect(getPageTitle(route)).toEqual(expected) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /tests/unit/setup.js: -------------------------------------------------------------------------------- 1 | /** Mock the browser ResizeObserver API as it is not currently included 2 | * in jsdom. 3 | * 4 | * @see https://github.com/jsdom/jsdom/issues/3368 5 | */ 6 | class ResizeObserverStub { 7 | observe () { } 8 | unobserve () { } 9 | disconnect () { } 10 | } 11 | 12 | window.ResizeObserver ??= ResizeObserverStub 13 | 14 | // Mock element scroll API as not currently included in jsdom: 15 | // https://github.com/jsdom/jsdom/issues/1422 16 | Element.prototype.scrollBy ??= function () { } 17 | Element.prototype.scrollIntoView ??= function () { } 18 | Element.prototype.scroll ??= function () { } 19 | Element.prototype.scrollTo ??= function () { } 20 | -------------------------------------------------------------------------------- /tests/unit/store/app.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { isReactive } from 'vue' 19 | import { createStore } from 'vuex' 20 | import storeOptions from '@/store/options' 21 | 22 | /** 23 | * Tests for the store/app module. 24 | */ 25 | describe('app', () => { 26 | let store 27 | beforeEach(() => { 28 | store = createStore(storeOptions) 29 | }) 30 | /** 31 | * Tests for store.app.title. 32 | */ 33 | describe('title', () => { 34 | it('should start with no title', () => { 35 | expect(store.state.app.title).to.equal(null) 36 | }) 37 | it('should set title', () => { 38 | const title = 'Cylc' 39 | store.commit('app/setTitle', title) 40 | expect(store.state.app.title).to.equal(title) 41 | }) 42 | }) 43 | 44 | describe('workspaceLayouts', () => { 45 | it('saves unproxied layout', () => { 46 | // Test that the Lumino layout saved in the store is not proxied, because 47 | // proxying the layout breaks some parts of the 3rd party Lumino backend 48 | const workflowName = 'zane' 49 | const fakeLayout = { a: { b: 'c' } } 50 | const fakeViews = new Map([[1, 2], [3, 4]]) 51 | store.commit('app/saveLayout', { workflowName, layout: fakeLayout, views: fakeViews }) 52 | const stored = store.state.app.workspaceLayouts.get(workflowName) 53 | expect(stored).toEqual({ layout: fakeLayout, views: fakeViews }) 54 | expect(isReactive(stored.layout)).toBe(false) 55 | }) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /tests/unit/store/user.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { createStore } from 'vuex' 19 | import storeOptions from '@/store/options' 20 | 21 | /** 22 | * Tests for the store/user module. 23 | */ 24 | describe('user', () => { 25 | const store = createStore(storeOptions) 26 | /** 27 | * Tests for store.user. 28 | */ 29 | describe('user', () => { 30 | const resetState = () => { 31 | store.state.user.user = null 32 | } 33 | beforeEach(resetState) 34 | it('should start with no user', () => { 35 | expect(store.state.user.user).to.equal(null) 36 | }) 37 | it('should set user', () => { 38 | const user = { 39 | id: 1, 40 | username: 'cylc' 41 | } 42 | store.commit('user/SET_USER', user) 43 | expect(store.state.user.user).to.deep.equal(user) 44 | }) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /tests/unit/utils/font-size.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { 19 | decreaseFontSize, getCurrentFontSize, increaseFontSize, INCREMENT, resetFontSize 20 | } from '@/utils/font-size' 21 | 22 | describe('Font Size', () => { 23 | // Won't have initialized the value, as there is no DOM and nothing was loaded 24 | const initialFontSize = 16 25 | 26 | beforeEach(() => { 27 | delete localStorage.fontSize 28 | document.documentElement.style.fontSize = `${initialFontSize}px` 29 | }) 30 | 31 | it('gets the current font size', () => { 32 | expect(getCurrentFontSize()).to.equal(initialFontSize) 33 | }) 34 | 35 | it('sets and gets a new font size', () => { 36 | const newVal = 31 37 | const newValPx = `${newVal}px` 38 | expect(localStorage.fontSize).to.not.equal(newValPx) 39 | expect(document.documentElement.style.fontSize).to.not.equal(newValPx) 40 | expect(getCurrentFontSize()).to.not.equal(newVal) 41 | resetFontSize(newValPx) 42 | expect(localStorage.fontSize).to.equal(newValPx) 43 | expect(document.documentElement.style.fontSize).to.equal(newValPx) 44 | expect(getCurrentFontSize()).to.equal(newVal) 45 | }) 46 | 47 | it('increases the font size', () => { 48 | increaseFontSize() 49 | expect(getCurrentFontSize()).to.equal(initialFontSize + INCREMENT) 50 | }) 51 | 52 | it('decreases the font size', () => { 53 | decreaseFontSize() 54 | expect(getCurrentFontSize()).to.equal(initialFontSize - INCREMENT) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /tests/unit/utils/graph-utils.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { 19 | posToPath, 20 | nonCryptoHash 21 | } from '@/utils/graph-utils' 22 | 23 | describe('Graph functionality', () => { 24 | describe('posToPath', () => { 25 | it('Converts GraphViz pos strings to SVG path strings', () => { 26 | // e,211.5,156.5 61.5,388.5 61.5,269.19 87.195,162.7 201.3,156.76 27 | expect(posToPath( 28 | 'e,211.5,156.5 61.5,388.5 61.5,269.19 87.195,162.7 201.3,156.76' 29 | )).to.equal( 30 | // the second point in the pos is the first point in the path 31 | // (because GraphViz, that's why) 32 | 'M61.5 -388.5 ' + 33 | // the following points in the pos are for the bézier curve 34 | 'C 61.5 -269.19, 87.195 -162.7, 201.3 -156.76, ' + 35 | // the first point in the pos is the last point in the path 36 | // (because GraphViz, that's why) 37 | // we use a straight line segment, it should be a curve, but, the 38 | // difference isn't noticeable 39 | 'L 211.5 -156.5' 40 | ) 41 | }) 42 | it('Handles negative coordinates', () => { 43 | expect(posToPath( 44 | 'e,1,1 -2,-2 3,-3 -1,-0' 45 | )).to.equal( 46 | 'M-2 2 C 3 3, -1 0, L 1 -1' 47 | ) 48 | }) 49 | }) 50 | 51 | describe('nonCryptoHash', () => { 52 | it.each([ 53 | ['foo', 101574], 54 | ['', 0], 55 | ])('Converts a string to a stable hash: %o -> %i', (str, expected) => { 56 | expect(nonCryptoHash(str)).to.equal(expected) 57 | }) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /tests/unit/utils/initialOptions.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { nextTick } from 'vue' 19 | import { vi } from 'vitest' 20 | import { 21 | updateInitialOptionsEvent, 22 | useInitialOptions 23 | } from '@/utils/initialOptions' 24 | 25 | describe('useInitialOptions', () => { 26 | it('updates the initialOptions prop when the ref changes', async () => { 27 | const emit = vi.fn() 28 | const initialOptions = { 29 | name: 'Isaac Clarke', 30 | ship: 'USG Ishimura', 31 | } 32 | const props = { initialOptions } 33 | const name = useInitialOptions('name', { props, emit }) 34 | // Should be called immediately 35 | expect(emit).toHaveBeenCalledWith( 36 | updateInitialOptionsEvent, 37 | initialOptions 38 | ) 39 | emit.mockClear() 40 | // Change the value of the ref 41 | name.value = 'Nicole Brennan' 42 | await nextTick() 43 | expect(emit).toHaveBeenCalledWith( 44 | updateInitialOptionsEvent, 45 | { 46 | name: 'Nicole Brennan', 47 | ship: 'USG Ishimura', 48 | } 49 | ) 50 | }) 51 | 52 | it('uses the default value when the property is not in initialOptions', () => { 53 | const ctx = { 54 | props: { 55 | initialOptions: { ship: 'In Amber Clad' } 56 | }, 57 | emit () {}, 58 | } 59 | 60 | const name = useInitialOptions('name', ctx, 'Miranda Keyes') 61 | expect(name.value).toBe('Miranda Keyes') 62 | const ship = useInitialOptions('ship', ctx, 'Forward Unto Dawn') 63 | expect(ship.value).toBe('In Amber Clad') 64 | const rank = useInitialOptions('rank', ctx) 65 | expect(rank.value).toBe(undefined) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /tests/unit/utils/viewToolbar.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { btnIconFontSize } from '@/utils/viewToolbar' 19 | 20 | describe('btnIconFontSize', () => { 21 | it.each([ 22 | { size: '28', expected: '0.75rem' }, 23 | { size: '40', expected: '1rem' }, 24 | { size: '48', expected: '1.25rem' }, 25 | { size: 'large', expected: undefined }, 26 | ])('btnIconFontSize($size) -> $expected', ({ size, expected }) => { 27 | expect(btnIconFontSize(size)).toBe(expected) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /tests/unit/views/views.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | import { TREE, useDefaultView } from '@/views/views.js' 19 | 20 | describe('useDefaultView composable', () => { 21 | it(`returns the ${TREE} view if not set in localStorage`, () => { 22 | delete localStorage.defaultView 23 | expect(useDefaultView().value).to.equal(TREE) 24 | }) 25 | 26 | it('returns the view that has been set in localStorage', () => { 27 | localStorage.defaultView = 'Table' 28 | expect(useDefaultView().value).to.equal('Table') 29 | localStorage.defaultView = 'Graph' 30 | expect(useDefaultView().value).to.equal('Graph') 31 | }) 32 | 33 | it(`returns the ${TREE} view if the view set in localStorage is not available`, () => { 34 | localStorage.defaultView = 'NotAView' 35 | expect(useDefaultView().value).to.equal(TREE) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /tests/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) NIWA & British Crown (Met Office) & Contributors. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | /** 19 | * Create a promise that can be manually resolved. 20 | * 21 | * Usage: 22 | * const deferred = new Deferred() 23 | * deferred.promise.then(doSomething) 24 | * deferred.resolve() 25 | */ 26 | export class Deferred { 27 | constructor () { 28 | this.promise = new Promise(resolve => { 29 | this.resolve = resolve 30 | }) 31 | } 32 | } 33 | 34 | /** 35 | * Convert filteredOutNodesCache to a simpler object for easier comparison. 36 | * 37 | * Used by Tree view & GScan tests. 38 | * 39 | * @param {Map} filteredOutNodesCache 40 | * @returns {Object} 41 | */ 42 | export function getIDMap (filteredOutNodesCache) { 43 | return Object.fromEntries( 44 | Array.from(filteredOutNodesCache.entries(), ([node, val]) => [node.id, val]) 45 | ) 46 | } 47 | --------------------------------------------------------------------------------