diff --git a/.github/workflows/deployment.yaml b/.github/workflows/deployment.yaml
new file mode 100644
index 00000000000..441f35e7167
--- /dev/null
+++ b/.github/workflows/deployment.yaml
@@ -0,0 +1,71 @@
+name: Deployment Tests
+on:
+ workflow_dispatch:
+ inputs:
+ target:
+ description: 'Galaxy Deployment to target'
+ required: true
+ default: 'usegalaxymain'
+ type: choice
+ options:
+ - usegalaxytest
+ - usegalaxymain
+ - usegalaxyeu
+ type:
+ description: 'Test type'
+ required: true
+ default: 'all'
+ type: choice
+ options:
+ - all
+ - api
+ - selenium
+ branch:
+ description: 'Branch of code to run from'
+ default: 'dev'
+ type: string
+ debug:
+ required: true
+ description: 'Run deployment tests with debug mode on'
+ type: boolean
+jobs:
+ testdeployment:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: ['3.7']
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ ref: ${{ inputs.branch }}
+ fetch-depth: 0
+ - uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Cache pip dir
+ uses: actions/cache@v3
+ with:
+ path: ~/.cache/pip
+ key: pip-cache-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }}
+ - uses: nanasess/setup-chromedriver@v1
+ - name: Run tests
+ run: bash ./test/deployment/usegalaxystar.bash
+ env:
+ GALAXY_TEST_DEPLOYMENT_TARGET: ${{ inputs.target }}
+ GALAXY_TEST_DEPLOYMENT_DEBUG: ${{ inputs.debug }}
+ GALAXY_TEST_DEPLOYMENT_TEST_TYPE: ${{ inputs.type }}
+ GALAXY_TEST_USEGALAXYMAIN_USER_EMAIL: "jmchilton+test@gmail.com"
+ GALAXY_TEST_USEGALAXYMAIN_USER_PASSWORD: ${{ secrets.USEGALAXYMAIN_USER_PASSWORD }}
+ GALAXY_TEST_USEGALAXYMAIN_USER_KEY: ${{ secrets.USEGALAXYMAIN_USER_KEY }}
+ GALAXY_TEST_USEGALAXYTEST_USER_EMAIL: "jmchilton+test@gmail.com"
+ GALAXY_TEST_USEGALAXYTEST_USER_PASSWORD: ${{ secrets.USEGALAXYTEST_USER_PASSWORD }}
+ GALAXY_TEST_USEGALAXYTEST_USER_KEY: ${{ secrets.USEGALAXYTEST_USER_KEY }}
+ GALAXY_TEST_USEGALAXYEU_USER_EMAIL: "jmchilton+test@gmail.com"
+ GALAXY_TEST_USEGALAXYEU_USER_PASSWORD: ${{ secrets.USEGALAXYEU_USER_PASSWORD }}
+ GALAXY_TEST_USEGALAXYEU_USER_KEY: ${{ secrets.USEGALAXYEU_USER_KEY }}
+ GALAXY_TEST_TIMEOUT_MULTIPLIER: 10
+ - uses: actions/upload-artifact@v2
+ if: always()
+ with:
+ name: Deployment test results (${{ inputs.target }}, ${{ inputs.type }})
+ path: 'deployment_tests.html'
diff --git a/.github/workflows/maintenance_bot.yaml b/.github/workflows/maintenance_bot.yaml
index e8e78c8d60d..aca4415ff55 100644
--- a/.github/workflows/maintenance_bot.yaml
+++ b/.github/workflows/maintenance_bot.yaml
@@ -8,7 +8,7 @@ jobs:
if: github.repository_owner == 'galaxyproject'
runs-on: ubuntu-latest
env:
- MILESTONE_NUMBER: 23
+ MILESTONE_NUMBER: 24
steps:
- name: Get latest pull request labels
id: get_pr_labels
diff --git a/.vscode/.test.env b/.vscode/.test.env
index 0fd4d77f2ed..8321bcadf92 100644
--- a/.vscode/.test.env
+++ b/.vscode/.test.env
@@ -1 +1 @@
-GALAXY_TEST_TOOL_CONF="lib/galaxy/config/sample/tool_conf.xml.sample,test/functional/tools/samples_tool_conf.xml"
\ No newline at end of file
+GALAXY_TEST_TOOL_CONF="lib/galaxy/config/sample/tool_conf.xml.sample,test/functional/tools/sample_tool_conf.xml"
diff --git a/.vscode/launch_gitpod.json b/.vscode/launch_gitpod.json
index c9bee582dcd..ebab8a9437a 100644
--- a/.vscode/launch_gitpod.json
+++ b/.vscode/launch_gitpod.json
@@ -10,7 +10,7 @@
"env": {
"GALAXY_CONFIG_FILE": "${workspaceFolder}/config/galaxy.yml",
"GALAXY_CONDA_AUTO_INIT": "false",
- "GALAXY_CONFIG_TOOL_CONFIG_FILE": "lib/galaxy/config/sample/tool_conf.xml.sample,test/functional/tools/samples_tool_conf.xml",
+ "GALAXY_CONFIG_TOOL_CONFIG_FILE": "lib/galaxy/config/sample/tool_conf.xml.sample,test/functional/tools/sample_tool_conf.xml",
"GALAXY_CONFIG_DATABASE_CONNECTION": "postgresql://localhost/galaxy"
}
},
diff --git a/Makefile b/Makefile
index d2748e0ff5d..8821c7445e1 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@
VENV?=.venv
# Source virtualenv to execute command (darker, sphinx, twine, etc...)
IN_VENV=if [ -f "$(VENV)/bin/activate" ]; then . "$(VENV)/bin/activate"; fi;
-RELEASE_CURR:=23.0
+RELEASE_CURR:=23.1
RELEASE_UPSTREAM:=upstream
CONFIG_MANAGE=$(IN_VENV) python lib/galaxy/config/config_manage.py
PROJECT_URL?=https://github.com/galaxyproject/galaxy
@@ -162,6 +162,14 @@ skip-client: ## Run only the server, skipping the client build.
GALAXY_SKIP_CLIENT_BUILD=1 sh run.sh
node-deps: ## Install NodeJS dependencies.
+ifndef YARN
+ @echo "Could not find yarn, which is required to install the Galaxy client.\nTo install yarn, please visit \033[0;34mhttps://yarnpkg.com/en/docs/install\033[0m for instructions, and package information for all platforms.\n"
+ false;
+else
+ yarn install $(YARN_INSTALL_OPTS)
+endif
+
+client-node-deps: ## Install NodeJS dependencies for the client.
ifndef YARN
@echo "Could not find yarn, which is required to build the Galaxy client.\nTo install yarn, please visit \033[0;34mhttps://yarnpkg.com/en/docs/install\033[0m for instructions, and package information for all platforms.\n"
false;
@@ -176,7 +184,7 @@ build-api-schema:
remove-api-schema:
rm _schema.yaml
-update-client-api-schema: node-deps build-api-schema
+update-client-api-schema: client-node-deps build-api-schema
$(IN_VENV) cd client && node openapi_to_schema.mjs ../_schema.yaml > src/schema/schema.ts && npx prettier --write src/schema/schema.ts
$(MAKE) remove-api-schema
@@ -185,34 +193,34 @@ lint-api-schema: build-api-schema
$(IN_VENV) codespell -I .ci/ignore-spelling.txt _schema.yaml
$(MAKE) remove-api-schema
-client: node-deps ## Rebuild client-side artifacts for local development.
+install-client: node-deps ## Install prebuilt client as defined in root package.json
+ yarn install && yarn run stage
+
+client: client-node-deps ## Rebuild client-side artifacts for local development.
cd client && yarn run build
-client-production: node-deps ## Rebuild client-side artifacts for a production deployment without sourcemaps.
+client-production: client-node-deps ## Rebuild client-side artifacts for a production deployment without sourcemaps.
cd client && yarn run build-production
-client-production-maps: node-deps ## Rebuild client-side artifacts for a production deployment with sourcemaps.
+client-production-maps: client-node-deps ## Rebuild client-side artifacts for a production deployment with sourcemaps.
cd client && yarn run build-production-maps
-client-format: node-deps ## Reformat client code
+client-format: client-node-deps ## Reformat client code
cd client && yarn run format
-client-watch: node-deps ## A useful target for parallel development building. See also client-dev-server.
- cd client && yarn run watch
+client-dev-server: client-node-deps ## Starts a webpack dev server for client development (HMR enabled)
+ cd client && yarn run develop
-client-dev-server: node-deps ## Starts a webpack dev server for client development (HMR enabled)
- cd client && yarn run serve
-
-client-test: node-deps ## Run JS unit tests
+client-test: client-node-deps ## Run JS unit tests
cd client && yarn run test
-client-eslint-precommit: node-deps # Client linting for pre-commit hook; skips glob input and takes specific paths
+client-eslint-precommit: client-node-deps # Client linting for pre-commit hook; skips glob input and takes specific paths
cd client && yarn run eslint-precommit
-client-eslint: node-deps # Run client linting
+client-eslint: client-node-deps # Run client linting
cd client && yarn run eslint
-client-format-check: node-deps # Run client formatting check
+client-format-check: client-node-deps # Run client formatting check
cd client && yarn run format-check
client-lint: client-eslint client-format-check ## ES lint and check format of client
diff --git a/client/.eslintrc.json b/client/.eslintrc.json
index f13b7808484..a02efdbb365 100644
--- a/client/.eslintrc.json
+++ b/client/.eslintrc.json
@@ -59,7 +59,7 @@
"vuejs-accessibility/no-autofocus": "error",
"vuejs-accessibility/tabindex-no-positive": "error"
},
- "ignorePatterns": ["src/qunit", "src/mocha", "src/libs", "src/nls", "src/legacy"],
+ "ignorePatterns": ["dist", "src/qunit", "src/mocha", "src/libs", "src/nls", "src/legacy"],
"overrides": [
{
"files": ["**/*.vue"],
diff --git a/client/.prettierignore b/client/.prettierignore
new file mode 100644
index 00000000000..143859dcc25
--- /dev/null
+++ b/client/.prettierignore
@@ -0,0 +1,14 @@
+# Standard ignores
+**/.git
+**/.svn
+**/.hg
+**/node_modules
+# Ignore the dist folder
+**/dist
+# We will always defer to package manager for formatting of package.json files.
+**/package.json
+# Skip formatting of explicitly included libs (to stay in sync with upstream, and they should go away eventually)
+src/libs
+# Don't reformat .json or .yml files included in the project
+*.json
+*.yml
diff --git a/client/README.md b/client/README.md
index 15fad423884..abb2741ce1b 100644
--- a/client/README.md
+++ b/client/README.md
@@ -1,19 +1,19 @@
# Client Build System
Installs, stages, and builds the client-side scripts necessary for running the
-Galaxy webapp. When started through `run.sh` or any other method that utilizes
-`scripts/common_startup.sh`, Galaxy will (since 18.09) _automatically_ build
-the client as a part of server startup, when it detects changes, unless that
+Galaxy web client. When started through `run.sh` or any other method that
+utilizes `scripts/common_startup.sh`, Galaxy will (since 18.09) _automatically_
+build the client as a part of server startup when it detects changes unless that
functionality is explicitly disabled.
-The base dependencies used are Node.js and Yarn. Galaxy now includes these in
-the virtual environment, and they can be accessed by activating that with `.
-.venv/bin/activate` from the Galaxy root directory.
+The base dependencies used are Node.js and Yarn. Galaxy includes appropriate
+versions of these in the virtual environment, and they can be accessed by
+activating that with `. .venv/bin/activate` from the Galaxy root directory.
-If you'd like to install your own dependencies, on OSX the easiest way to get
-set up is using `homebrew` and the command `brew install nodejs yarn`. More
-information, including instructions for other platforms, is available at
-[https://nodejs.org](https://nodejs.org) and
+If you'd like to install your dependencies external to Galaxy, on OSX the
+easiest way to get set up is using `homebrew` and the command `brew install
+nodejs yarn`. More information, including instructions for other platforms, is
+available at [https://nodejs.org](https://nodejs.org) and
[https://yarnpkg.com/](https://yarnpkg.com).
The Galaxy client build has necessarily grown more complex in the past several
@@ -27,11 +27,11 @@ directly on Gitter at
There are many moving parts to the client build system, but the entry point for
most people is the 'client' rule in the Makefile at the root of the Galaxy
-repository. Execute the following to perform a complete build suitable for
-local development, including dependency staging, style building, script
-processing, and bundling. This is a development-specific build which includes
-extra debugging features, and excludes several production optimizations made
-during the build process.
+repository. Execute the following to perform a complete build suitable for local
+development, including dependency staging, style building, script processing,
+and bundling. This is a development-specific build that includes extra debugging
+features and excludes several production optimizations made during the build
+process.
make client
@@ -53,39 +53,31 @@ and these should not be committed.
When you're actively developing, it is convenient to have the client
automatically rebuild every time you save a file. You can do this using:
- make client-watch
-
-This will first stage client dependencies, initiate a build, and then will
-watch for changes in any of the galaxy client source files. When a file is
-changed, the client will automatically rebuild, after which you can refresh
-your browser to see changes.
-
-For even more rapid development you can use the webpack development server,
-which takes advantage of hot module replacement (HMR). This technique allows
-swapping out of javascript modules while the application is running without
-requiring a full page reload most of the time, at least in the more modern
-parts of the application.
-
-The command below starts a special webpack dev server after a client
-build.
-
make client-dev-server
-This will start up an extra client development server running on port 8081.
-Open your browser to http://localhost:8081 (instead of the default 8080 that
-Galaxy would run on), and you should see Galaxy like normal. Except now, when
-you change client code it'll automatically rebuild *and* reload the relevant
-portion of the application for you. Lastly, if you
-are running Galaxy at a location other than the default, you can specify a
-different proxy target (in this example, port 8000) using the GALAXY_URL
-environment variable:
+Or, with the package scripts from this `client` directory:
+
+ yarn run develop
+
+This will start up an extra client development server running on port 8081. Open
+your browser to `http://localhost:8081` (instead of the default 8080 that Galaxy
+would run on), and you should see Galaxy like normal. Except now, when you
+change client code it'll automatically rebuild _and_ reload the relevant portion
+of the application for you. Lastly, if you are running Galaxy at a location
+other than the default, you can specify a different proxy target (in this
+example, port 8000) using the GALAXY_URL environment variable:
GALAXY_URL="http://localhost:8000" make client-dev-server
-Sometimes you want to run your local UI against a remote Galaxy server. This is also possible, if you enable `CHANGE_ORIGIN` flag
+Sometimes you want to run your local UI against a remote Galaxy server. This is
+also possible if you set the `CHANGE_ORIGIN` environment variable:
CHANGE_ORIGIN=true GALAXY_URL="https://usegalaxy.org/" make client-dev-server
+You can also specify a particular port to bind the dev server to:
+
+ WEBPACK_PORT=8083 yarn run develop
+
## Running a Separate Server
When developing the client it can be helpful to run a local server for the
@@ -94,17 +86,22 @@ commands. This command will run galaxy without building the client:
make skip-client
+Or by setting the following environment variable and running Galaxy however you
+prefer:
+
+ GALAXY_SKIP_CLIENT_BUILD=1 ./run.sh
+
## Changing Styles/CSS
-Galaxy uses Sass for its styling, which is a superset of CSS that compiles down
-to regular CSS. Most Galaxy styling source (.scss) files are kept in
-`client/src/style/scss`. There are additionally style blocks alongside some Vue
-components -- styles that are particular to that individual component and do
-not apply site-wide.
+Galaxy uses Sass for globally applied styling, which is a superset of CSS that
+compiles down to regular CSS. Most Galaxy styling source (.scss) files are kept
+in `client/src/style/scss`. Many components will also have local style blocks
+containing styles that are particular to that individual component and do not
+apply site-wide.
On build, the compiled css bundle is served at `/static/style/base.css`.
-As mentioned above, `make client` will rebuild styles, as a part of the webpack
+As mentioned above, `make client` will rebuild styles as a part of the webpack
build. For iterative development, "Watch Mode" rebuilds as described above do
include style changes.
@@ -122,7 +119,7 @@ framework.
For testing Vue components, we use the [Vue testing
utils](https://vue-test-utils.vuejs.org/) to mount individual components in a
-test bed and check them for rendered features. Please use jest-based mocking
+test bed and check them for rendered features. Please use jest-based mocking
for isolating test functionality.
A set of older qUnit tests also exist which will be phased-out as the code they
@@ -142,8 +139,8 @@ directory. This is what happens during a complete client build.
During client-side development, it is more convenient to have granular testing
options. The various testing scripts are defined inside package.json within the
-client folder, and are called with `yarn` as demonstrated in the
-following commands.
+client folder and are called with `yarn` as demonstrated in the following
+commands.
This is what CI is going to run, and also what 'make client-test' invokes,
executing all the client tests:
@@ -154,7 +151,7 @@ You can also bypass qunit and single-run all of the jest tests like so:
yarn run jest
-Or, if you really want to run just the qunit tests:
+Or, if you want to run just the qunit tests:
yarn run qunit
diff --git a/client/docs/avoid-using-global-Galaxy.md b/client/docs/avoid-using-global-Galaxy.md
index 2a19a2597c7..3092190ce58 100644
--- a/client/docs/avoid-using-global-Galaxy.md
+++ b/client/docs/avoid-using-global-Galaxy.md
@@ -9,17 +9,17 @@ you access to the Galaxy.config, current user, and current user histories. Pleas
retrieve your values, and bypass importing Galaxy altogether.
#### Sometimes you still need to update Vue from backbone as the legacy environment changes
+
There are definitely use-cases where the Backbone models update over time and we need to update some
value inside Vue. Instead of importing backbone models directly into Vue components, try building a
backbone event listener that updates some relevant Vuex store.
-* [Keeping Vuex in Sync with
- Galaxy](https://github.com/galaxyproject/galaxy/blob/dev/client/src/store/syncVuexToGalaxy.js)
+- [Keeping Vuex in Sync with
+ Galaxy](https://github.com/galaxyproject/galaxy/blob/dev/client/src/store/syncVuexToGalaxy.js)
These issues should disappear over time as the all of the old client is rebuilt in the new
ecosystem.
-
## Mount Functions
In what most people think of as a "standard" Vue application there would be only one place that Vue
@@ -30,7 +30,6 @@ However, we are incrementally replacing old Backbone views, so in its current st
several mounting functions for various components depending on where that component is intended to
fit into the existing Backbone layouts.
-
### Using the standard mount to pass in Galaxy variables as props
A standard mount function has been provided in src/utils. This mount function accepts a component
@@ -46,28 +45,26 @@ import MyComponent from "components/MyComponent";
import { mountVueComponent } from "utils/mountVueComponent";
const OldBackboneView = {
-
someInitMethodYouMake() {
const Galaxy = getGalaxyInstance();
const mounter = mountVueComponent(MyComponent);
// pass in required props
- const props = {
-
+ const props = {
// Something peeled off the global galaxy
- somePropVal: Galaxy.someDealie,
+ somePropVal: Galaxy.someDealie,
// ...or the current history
name: Galaxy.currentHistory.name,
// or maybe from the backbone model for this view
- shoeSize: this.model.shoeSize
+ shoeSize: this.model.shoeSize,
};
// VM is a Vue instance.
// this.$el is some jquery selection, first item is the actual DOM object
const container = this.$el[0];
const vm = mounter(props, container);
- }
-}
+ },
+};
```
diff --git a/client/docs/composables.md b/client/docs/composables.md
index a7a22b5c6d3..248e30f5575 100644
--- a/client/docs/composables.md
+++ b/client/docs/composables.md
@@ -6,9 +6,9 @@ Using them effectively can make your code more reusable, decoupled, and easier t
**More about Composables:**
-* [Composables Overview](https://vuejs.org/guide/reusability/composables.html)
-* [Composition API](https://vuejs.org/api/composition-api-setup.html)
-* [\
```
@@ -79,7 +79,7 @@ import { useCurrentUser } from "composables/user";
jest.mock("composables/user");
useCurrentUser.mockReturnValue({
- currentUser: {}
+ currentUser: {},
});
```
@@ -95,11 +95,7 @@ Usage:
```
diff --git a/client/docs/headings.md b/client/docs/headings.md
index a7cf82bfb14..87538b3d8a6 100644
--- a/client/docs/headings.md
+++ b/client/docs/headings.md
@@ -6,23 +6,23 @@ Heading Levels are used by screen readers, and other software, to get a rough id
## Heading Level best practices
- - Make sure every route has exactly one `
` element, which best describes the content of the current page.
+- Make sure every route has exactly one `` element, which best describes the content of the current page.
- - When increasing a heading level, do not skip Levels.
+- When increasing a heading level, do not skip Levels.
- - Do not use a headings level to determine it's size. Use one of the heading utility classes (e.g. `h-lg`) instead.
+- Do not use a headings level to determine it's size. Use one of the heading utility classes (e.g. `h-lg`) instead.
- - Do not use a heading tag to make a non-heading text large. Use a heading utility class on a `` instead.
+- Do not use a heading tag to make a non-heading text large. Use a heading utility class on a `` instead.
## Sizing Headings
There are several utility classes which size headings:
- - `h-xl` - Extra large headings
- - `h-lg` - Large headings (most main headings should have this size)
- - `h-md` - Medium headings
- - `h-sm` - Small headings (many sub-headings have this size)
- - `h-text` - Text sized headings
+- `h-xl` - Extra large headings
+- `h-lg` - Large headings (most main headings should have this size)
+- `h-md` - Medium headings
+- `h-sm` - Small headings (many sub-headings have this size)
+- `h-text` - Text sized headings
Galaxy uses `h-lg` for most top-level (``) headings.
@@ -45,8 +45,8 @@ Set the headings level, by setting aa `h1 ... h6` prop:
Following properties allow for further styling the component:
- - `size=["xl", "lg", "md", "sm", "text"]` - sets the headings size class
- - `bold` - makes a heading bold
- - `inline` - displays the heading inline
- - `separator` - draws a separating line, to better distinguish sections
- - `icon="..."` - adds a font-awesome icon decoration to the left of the heading. Make sure to also load the icon with `library.add(...)`.
+- `size=["xl", "lg", "md", "sm", "text"]` - sets the headings size class
+- `bold` - makes a heading bold
+- `inline` - displays the heading inline
+- `separator` - draws a separating line, to better distinguish sections
+- `icon="..."` - adds a font-awesome icon decoration to the left of the heading. Make sure to also load the icon with `library.add(...)`.
diff --git a/client/docs/never-use-jquery.md b/client/docs/never-use-jquery.md
index c1e07181928..df7a5370da5 100644
--- a/client/docs/never-use-jquery.md
+++ b/client/docs/never-use-jquery.md
@@ -1,6 +1,6 @@
Did you know, jQuery is old enough to drive? It's old enough to get a driver's license. jQuery is a
tool that was built to deal with inconsistencies in browsers that NO LONGER EXIST. In a couple
-years, jQuery will be voting, drinking, and capable of being tried as an adult.
+years, jQuery will be voting, drinking, and capable of being tried as an adult.
If you think you need jQuery, you are mistaken. Please seek help from somebody in the wg-ui-ux
workgroup. There is nothing jQuery can provide you that isn't already part of vanilla javascript or
@@ -14,5 +14,6 @@ One of our most important goals in redesigning Galaxy is the complete eliminatio
from our source, along with all its invasive plugins.
### References
-* [You Don't Need jQuery](https://github.com/nefe/You-Dont-Need-jQuery)
-* [document.querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
\ No newline at end of file
+
+- [You Don't Need jQuery](https://github.com/nefe/You-Dont-Need-jQuery)
+- [document.querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
diff --git a/client/docs/providers-and-renderers.md b/client/docs/providers-and-renderers.md
index 1eca93b3e13..2637fcbf9f9 100644
--- a/client/docs/providers-and-renderers.md
+++ b/client/docs/providers-and-renderers.md
@@ -22,34 +22,27 @@ component we previously made, but you are free to putput whatever you want in th
doodad and saveDoddad properties as desired, as well as any other local data with the only
restriction that Vue needs a single root element in which to render.
-
## The Renderer
```html static
-
+
```
@@ -66,7 +59,6 @@ binds](https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier)).
Whatever happens to that new object is somebody else's job. As soon as you tie the data management
to the rendering, the re-usability of your components craters.
-
## The Provider
As the opposite of the rendering component, a provider or renderless component, is pure logic. It
@@ -76,7 +68,6 @@ functionality available in Vue. Some others are [Mixins](https://vuejs.org/v2/gu
[Provide/Inject](https://v3.vuejs.org/guide/component-provide-inject.html) and (in Vue3) [the
composition API](https://v3.vuejs.org/guide/composition-api-introduction.html).
-
```js static
// DoodadProvider.js
@@ -134,7 +125,7 @@ mandatory markup, just one big empty slot.
// Testing a renderless component
import { shallowMount } from "@vue/test-utils";
-import { getLocalVue, waitForLifecyleEvent } from "tests/jest/helpers"
+import { getLocalVue, waitForLifecyleEvent } from "tests/jest/helpers";
import DoodadProvider from "./DoodadProvider";
describe("A renderless component", () => {
@@ -157,12 +148,12 @@ describe("A renderless component", () => {
// component. This is often good enough for waiting for
// an initial ajax load to finish, for example
await waitForLifecyleEvent(wrapper.vm, "updated");
- })
+ });
test("someProp", () => {
const { someProp } = slotProps;
expect(someProp).toExist();
// ...more tests
- })
-})
+ });
+});
```
diff --git a/client/docs/readme.md b/client/docs/readme.md
index dba0f97c1e1..e7442b5637b 100644
--- a/client/docs/readme.md
+++ b/client/docs/readme.md
@@ -5,7 +5,7 @@ I'm not talking about how webpack turns it into a rendering function. That's obv
I mean conceptually, props come in (like arguments) and events go out (like the return statements).
A component is a fancy kind of function that can keep emitting results and accept changing inputs
over time. In truth it more closely resembles an Observable, but an observable is ALSO a slightly
-fancier kind of function.
+fancier kind of function.
If you just think of a component as thing that takes input props and emits output events you're well
on your way to using them well.
@@ -19,22 +19,22 @@ problems of the old imperative class-based legacy code.
New vue programmers are ok at handing props to components, but they rarely use events effectively
(at first). As a result they end up using a lot of global state, a million little data props and
relying on imperfect globalized tools like Vuex or other imported dependencies for every little
-variable.
+variable.
Vuex definitely has its uses, but not as many as you might expect given the way it is
overly-emphasized in common tutorials. It's easy to walk away from an "Intro to Vue" video with the
-idea that all data must live in Vuex all the time. That's a really undesirable situation.
+idea that all data must live in Vuex all the time. That's a really undesirable situation.
Although vuex is a well-organized (many would say over-organized) state machine, it is important
to remember that it is still a kind of global injection and deserves to be considered as such.
-* [Should I Store This Data in
- Vuex](https://markus.oberlehner.net/blog/should-i-store-this-data-in-vuex/)
-* [Vuex getters are great, but don’t overuse
- them](https://codeburst.io/vuex-getters-are-great-but-dont-overuse-them-9c946689b414)
+- [Should I Store This Data in
+ Vuex](https://markus.oberlehner.net/blog/should-i-store-this-data-in-vuex/)
+- [Vuex getters are great, but don’t overuse
+ them](https://codeburst.io/vuex-getters-are-great-but-dont-overuse-them-9c946689b414)
Data persistence should be something that happens near the top of your component tree, not down in
-the guts.
+the guts.
Your first thought with a component should be: "How can I offload the handling of the results of
this component to my caller?" The answer is usually going to be events. A component that simply
@@ -46,7 +46,6 @@ global state to operate.
They're just fancy shorthands for a prop / event handler combination. They are fundamentally no
different from props and events, but the syntax is important to understand.
-
### Think carefully about what should really be in "data".
Most of good component design boils down to answering the following question: What do I want to put
diff --git a/client/docs/styleguide.md b/client/docs/styleguide.md
index 938ed22fc23..d158fd0a3a3 100644
--- a/client/docs/styleguide.md
+++ b/client/docs/styleguide.md
@@ -44,7 +44,7 @@ const myFunction = (param) => {
};
// anonymous functions
-const myFunction = function(param) {
+const myFunction = function (param) {
//do stuff
};
```
@@ -84,10 +84,9 @@ When possible, use arrow functions instead.
> export function myFunction(parameter) {
> const addOne = (value) => {
> return value + 1;
-> }
+> };
> // do more stuff...
> }
->
> ```
>
> **Don't**
@@ -96,12 +95,11 @@ When possible, use arrow functions instead.
> // in myModules.js
>
> export const myFunction = (parameter) => {
-> const addOne = function(value) {
+> const addOne = function (value) {
> return value + 1;
-> }
+> };
> // do more stuff...
-> }
->
+> };
> ```
## HTML Multi-Line Layout
@@ -115,9 +113,7 @@ Prettier tries to respect whitespace when formatting your HTML templates, even w
Might get turned into:
```vue
-A very Long Button Text
+A very Long Button Text
```
Notice the strange positioning of the `>` brackets.
@@ -277,7 +273,7 @@ Do not add space between elements connected by conditionals.
>
> condition met
>
->
+>
>
> condition not met
>
@@ -293,7 +289,7 @@ Add space between non-connected elements.
>
> First span.
>
->
+>
>
> Second span.
>
@@ -325,7 +321,7 @@ Add space between logical blocks of elements.
>
> condition 1 not met
>
->
+>
>
> condition 2 met
>
diff --git a/client/docs/unit-testing/debugging-unit-tests.md b/client/docs/unit-testing/debugging-unit-tests.md
index d7916cf6fa5..74097ef365b 100644
--- a/client/docs/unit-testing/debugging-unit-tests.md
+++ b/client/docs/unit-testing/debugging-unit-tests.md
@@ -9,7 +9,7 @@ code updates its test extensions again.
#### To debug a single Jest test:
-1. Open a jest test (a file that ends with *.test.js)
+1. Open a jest test (a file that ends with \*.test.js)
1. Make sure the test file is selected, especially if you have multiple files open. This process
will fail confusingly and without obvious error if you have not launched the debugger with the
@@ -34,8 +34,6 @@ code updates its test extensions again.
variables near your breakpoint and see their values in the "Variables" section of the Run and
Debug pane.
-
-
```json
// sample launch.json
{
@@ -45,16 +43,16 @@ code updates its test extensions again.
"type": "node",
"name": "debug selected jest test",
"request": "launch",
-
+
// launches version of jest from inside the node_modules
// this means you need to have run yarn first
"program": "${workspaceFolder}/client/node_modules/jest/bin/jest",
"args": [
- // Alias -i.
+ // Alias -i.
// Normally jest opens up a bunch of workers to run all your tests faster
// but we don't want that right now.
"--runInBand",
-
+
// finds jest config
"--config",
"${workspaceFolder}/client/tests/jest/jest.config.js",
@@ -68,9 +66,8 @@ code updates its test extensions again.
"console": "integratedTerminal",
// allows you to place breakpoints right in vscode's gutter
- "disableOptimisticBPs": true,
- },
-
+ "disableOptimisticBPs": true
+ }
]
}
```
diff --git a/client/docs/unit-testing/readme.md b/client/docs/unit-testing/readme.md
index 393c1a0c117..72c3ef2e582 100644
--- a/client/docs/unit-testing/readme.md
+++ b/client/docs/unit-testing/readme.md
@@ -1,20 +1,18 @@
-
[Galaxy uses Jest](https://jestjs.io/) for its client-side unit testing
framework.
For testing Vue components, we use the [Vue testing
utils](https://vue-test-utils.vuejs.org/) to mount individual components in a
-test bed and check them for rendered features. Please use jest-based mocking
+test bed and check them for rendered features. Please use jest-based mocking
for isolating test functionality.
-
### Specific test scenarios & examples
-* [Mocking an imported
-dependency](https://github.com/galaxyproject/galaxy/blob/dev/client/src/components/Tags/tagService.test.js)
-* [Testing async
-operations](https://github.com/galaxyproject/galaxy/blob/dev/client/src/components/Tags/tagService.test.js)
-* [Testing a Vue component for expected rendering
-output](https://github.com/galaxyproject/galaxy/blob/dev/client/src/components/Tags/StatelessTags.test.js)
-* [Firing an event against a shallow mounted vue
-component](https://github.com/galaxyproject/galaxy/blob/dev/client/src/components/Tags/StatelessTags.test.js)
+- [Mocking an imported
+ dependency](https://github.com/galaxyproject/galaxy/blob/dev/client/src/components/Tags/tagService.test.js)
+- [Testing async
+ operations](https://github.com/galaxyproject/galaxy/blob/dev/client/src/components/Tags/tagService.test.js)
+- [Testing a Vue component for expected rendering
+ output](https://github.com/galaxyproject/galaxy/blob/dev/client/src/components/Tags/StatelessTags.test.js)
+- [Firing an event against a shallow mounted vue
+ component](https://github.com/galaxyproject/galaxy/blob/dev/client/src/components/Tags/StatelessTags.test.js)
diff --git a/client/docs/unit-testing/strategies.md b/client/docs/unit-testing/strategies.md
index 64c343cc850..d06353193c3 100644
--- a/client/docs/unit-testing/strategies.md
+++ b/client/docs/unit-testing/strategies.md
@@ -1,4 +1,4 @@
-Part of making good code is making that code easy to test.
+Part of making good code is making that code easy to test.
### Implement logic in pure functions when possible
@@ -10,14 +10,13 @@ There is almost definitely no such thing as a well-written 1000 line function.
Most of whatever happened in that thing was probably deterministic and can be
broken up into easily testable chunks.
-
### Wrap native browser resources in a function so they can be easily mocked
If your javascript needs to talk to the window object, or navigator, etc. wrap
that in a function call so that it can be easily mocked during testing.
-
#### Your Module
+
```js static
// myModule.js
@@ -35,6 +34,7 @@ export function theThingYouReallyCareAbout() {
```
#### Your test file
+
```js static
// myModule.test.js
import { theThingYouReallyCareAbout, redirectTo } from "./myModule";
diff --git a/client/docs/unit-testing/writing-tests.md b/client/docs/unit-testing/writing-tests.md
index 8951b5797de..9a49db6c510 100644
--- a/client/docs/unit-testing/writing-tests.md
+++ b/client/docs/unit-testing/writing-tests.md
@@ -2,14 +2,13 @@
Please remember that these tests are not _for_ you. They're for the people who
come after you. It will be a lot easier to modify, repair and upgrade your code
-if they can figure out what you were originally hoping to accomplish. Try to
+if they can figure out what you were originally hoping to accomplish. Try to
use as detailed 'expect' statements as possible -- overuse of 'toBeTruthy()'
for example, can hide the intent of your test.
-Add a couple of comments. Use variable names that mean something. Nobody's
+Add a couple of comments. Use variable names that mean something. Nobody's
code is as self-documenting as they believe it to be.
-
### Only test the public API that you define
Internal implementations come and go with library upgrades and new tech. But
@@ -20,17 +19,16 @@ Separate your concerns and identify the developer-facing methods and functions
you expect them to use. Test THOSE. Everything else should probably be
considered an implementation detail.
-The other side of the same coin is to test *only* the unit in question. If your
+The other side of the same coin is to test _only_ the unit in question. If your
component has a model that uses a service that touches Vuex, which then uses
-Axios to fetch some data -- don't test all that at once. Break things apart and
-mock functionality to isolate testing to units. End to end testing is a
+Axios to fetch some data -- don't test all that at once. Break things apart and
+mock functionality to isolate testing to units. End to end testing is a
separate thing that shouldn't be attempted using spec tests in Jest.
Assume nobody cares _how_ your code works, we just need to know that the public
API you designed _does_ work. If performance problems or new tech necessitate a
re-write, these tests become a guide for the next implementation.
-
### Writing a test file
Jest will try to test any file ending in "\*.test.js". Please place your test
@@ -65,9 +63,7 @@ describe("some module you wrote", () => {
});
```
-
### Check out the Jest helper functions
We have created some [common helpers for common testing
-scenarios](https://github.com/galaxyproject/galaxy/blob/dev/client/tests/jest/helpers.js).
-
+scenarios](https://github.com/galaxyproject/galaxy/blob/dev/client/tests/jest/helpers.js).
diff --git a/client/package.json b/client/package.json
index dbcf569c61f..6e3f76bfc7e 100644
--- a/client/package.json
+++ b/client/package.json
@@ -1,5 +1,5 @@
{
- "name": "galaxy-client",
+ "name": "@galaxyproject/galaxy-client",
"version": "0.1.0",
"description": "Galaxy client application build system",
"keywords": [
@@ -9,7 +9,10 @@
"type": "git",
"url": "https://github.com/galaxyproject/galaxy.git"
},
- "license": "AFL-3.0",
+ "license": "MIT",
+ "files": [
+ "dist"
+ ],
"browserslist": [
"defaults",
"not ie <= 11",
@@ -110,20 +113,18 @@
"xml-beautifier": "^0.5.0"
},
"scripts": {
- "watch": "gulp && yarn run save-build-hash && yarn run webpack-watch",
- "serve": "NODE_OPTIONS=--max-old-space-size=4096 && NODE_ENV=development gulp && webpack serve",
- "build": "NODE_ENV=development gulp && webpack && yarn run save-build-hash",
- "build-production": "NODE_ENV=production gulp && yarn run webpack-production && yarn run save-build-hash",
- "build-production-maps": "NODE_ENV=production gulp && yarn run webpack-production-maps && yarn run save-build-hash",
+ "develop": "NODE_OPTIONS=--max-old-space-size=4096 NODE_ENV=development gulp && webpack-dev-server",
+ "build": "NODE_ENV=development gulp && webpack && yarn run stage-build",
+ "build-production": "NODE_ENV=production gulp && yarn run webpack-production && yarn run stage-build",
+ "build-production-maps": "NODE_ENV=production gulp && yarn run webpack-production-maps && yarn run stage-build",
"build-stats": "NODE_ENV=production webpack --profile --json=webpack-stats.json",
"view-stats": "webpack-bundle-analyzer webpack-stats.json ../static/dist/",
- "webpack-watch": "webpack --watch",
"webpack-production": "NODE_ENV=production webpack",
"webpack-production-maps": "GXY_BUILD_SOURCEMAPS=1 NODE_ENV=production webpack",
"gulp": "gulp",
- "save-build-hash": "(git rev-parse HEAD 2>/dev/null || echo '') >../static/client_build_hash.txt",
- "format": "prettier --write 'src/style/scss/**/*.scss' 'src/**/{*.ts,*.js,*.vue}' 'tests/jest/standalone/{*.ts,*.js}' '!src/libs/**'",
- "format-check": "prettier --check 'src/style/scss/**/*.scss' 'src/**/{*.js,*.vue}' 'tests/jest/standalone/{*.ts,*.js}' '!src/libs/**'",
+ "stage-build": "cpy 'dist/*' '../static/dist' && (git rev-parse HEAD 2>/dev/null || echo '') >../static/client_build_hash.txt",
+ "format": "prettier --write .",
+ "format-check": "prettier --check .",
"prettier": "yarn run format",
"test": "yarn run qunit && yarn run jest",
"jest": "jest --config tests/jest/jest.config.js",
@@ -151,6 +152,7 @@
"axios-mock-adapter": "^1.20.0",
"babel-jest": "^29.3.1",
"buffer": "^6.0.3",
+ "cpy-cli": "^4.2.0",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.0.0",
"del": "^6.0.0",
diff --git a/client/prettier.config.js b/client/prettier.config.js
index df792433737..d9cc69f4d8c 100644
--- a/client/prettier.config.js
+++ b/client/prettier.config.js
@@ -1,5 +1,5 @@
module.exports = {
tabWidth: 4,
printWidth: 120,
- bracketSameLine: true
+ bracketSameLine: true,
};
diff --git a/client/src/components/Alert.md b/client/src/components/Alert.md
index 021efd67e56..61038ebc71a 100644
--- a/client/src/components/Alert.md
+++ b/client/src/components/Alert.md
@@ -8,7 +8,7 @@
```js
-
A {{variant}} message
+
A {{ variant }} message
```
diff --git a/client/src/components/Dataset/DatasetName.vue b/client/src/components/Dataset/DatasetName.vue
index 1625cd497ac..9751155ea3d 100644
--- a/client/src/components/Dataset/DatasetName.vue
+++ b/client/src/components/Dataset/DatasetName.vue
@@ -22,11 +22,11 @@
diff --git a/client/src/components/DatasetInformation/DatasetAttributes.vue b/client/src/components/DatasetInformation/DatasetAttributes.vue
index dd942966dd0..73874c5f601 100644
--- a/client/src/components/DatasetInformation/DatasetAttributes.vue
+++ b/client/src/components/DatasetInformation/DatasetAttributes.vue
@@ -20,12 +20,16 @@
@click="submit('attribute', 'attributes')">
{{ "Save" | l }}
-
+
{{ "Auto-detect" | l }}
-
+
{{ "Convert" | l }}
diff --git a/client/src/components/DatasetInformation/DatasetSource.vue b/client/src/components/DatasetInformation/DatasetSource.vue
index d7c210976c5..009614ef94e 100644
--- a/client/src/components/DatasetInformation/DatasetSource.vue
+++ b/client/src/components/DatasetInformation/DatasetSource.vue
@@ -1,13 +1,15 @@
-
+
{{ source.source_uri }}
-
+
{{ source.source_uri }}
-
+
diff --git a/client/src/components/Form/Elements/FormUpload.test.js b/client/src/components/Form/Elements/FormUpload.test.js
new file mode 100644
index 00000000000..b140b31d0e5
--- /dev/null
+++ b/client/src/components/Form/Elements/FormUpload.test.js
@@ -0,0 +1,30 @@
+import { mount } from "@vue/test-utils";
+import { getLocalVue } from "tests/jest/helpers";
+import FormUpload from "./FormUpload";
+
+const localVue = getLocalVue();
+
+describe("FormUpload", () => {
+ const mountFormUpload = (props) =>
+ mount(FormUpload, {
+ propsData: props,
+ localVue,
+ });
+
+ it("should display selected file in disabled textarea", async () => {
+ const v = "H1, H2, H3\nv1, v2, v3";
+ const wrapper = mountFormUpload({ value: v });
+ const el = wrapper.find("textarea");
+ expect(el.element.value).toEqual(v);
+ expect(el.element.disabled).toBe(true);
+ });
+
+ it("should not display text box if file has not been selected", async () => {
+ const wrapper = mountFormUpload({ value: null });
+ const el = wrapper.find("textarea");
+ expect(el.isVisible()).toBe(false);
+ expect(el.text()).toBe("");
+ const noInput = wrapper.find("label");
+ expect(noInput.text()).toBe("No file chosen");
+ });
+});
diff --git a/client/src/components/Form/Elements/FormUpload.vue b/client/src/components/Form/Elements/FormUpload.vue
new file mode 100644
index 00000000000..bd1d46d820c
--- /dev/null
+++ b/client/src/components/Form/Elements/FormUpload.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+ Uploading File...
+
+
+
+
diff --git a/client/src/components/Form/FormElement.vue b/client/src/components/Form/FormElement.vue
index 9e74614f742..ecc6fa340a7 100644
--- a/client/src/components/Form/FormElement.vue
+++ b/client/src/components/Form/FormElement.vue
@@ -10,6 +10,7 @@ import FormNumber from "./Elements/FormNumber.vue";
import FormText from "./Elements/FormText.vue";
import FormOptionalText from "./Elements/FormOptionalText.vue";
import FormRulesEdit from "./Elements/FormRulesEdit.vue";
+import FormUpload from "./Elements/FormUpload.vue";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { ref, computed, useAttrs } from "vue";
import { library } from "@fortawesome/fontawesome-svg-core";
@@ -265,6 +266,7 @@ const isOptional = computed(() => !isRequired.value && attrs.value["optional"] !
:multiple="attrs.multiple" />
+
import HistoryList from "./history-list";
export default {
- props: {
- actionId: {
- type: String,
- default: null,
- },
- },
mounted() {
- new HistoryList.View({
- action_id: this.actionId,
- }).$el.appendTo(this.$refs.target);
+ new HistoryList.View().$el.appendTo(this.$refs.target);
},
};
diff --git a/client/src/components/Grid/history-list.js b/client/src/components/Grid/history-list.js
index e8a768413af..42b8c21ff2c 100644
--- a/client/src/components/Grid/history-list.js
+++ b/client/src/components/Grid/history-list.js
@@ -62,15 +62,10 @@ var View = Backbone.View.extend({
initialize: function (options) {
const Galaxy = getGalaxyInstance();
LoadingIndicator.markViewAsLoading(this);
-
- if (options.action_id == "list_published") {
- this.active_tab = "shared";
- } else if (options.action_id == "list") {
- this.active_tab = "user";
- }
+ this.active_tab = "user";
this.model = new Backbone.Model();
Utils.get({
- url: `${getAppRoot()}history/${options.action_id}?${$.param(Galaxy.params)}`,
+ url: `${getAppRoot()}history/list?${$.param(Galaxy.params)}`,
success: (response) => {
this.model.set(response);
this.render();
diff --git a/client/src/components/History/Content/Dataset/DatasetActions.vue b/client/src/components/History/Content/Dataset/DatasetActions.vue
index 46fce47e359..118010babe4 100644
--- a/client/src/components/History/Content/Dataset/DatasetActions.vue
+++ b/client/src/components/History/Content/Dataset/DatasetActions.vue
@@ -44,7 +44,7 @@
{
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = mount(GenericElement, {
+ propsData: {
+ dsc: {
+ elements: [
+ {
+ element_index: 1,
+ element_identifier: "element-1",
+ element_type: "hda",
+ object: {
+ id: "item-1",
+ },
+ },
+ {
+ element_index: 2,
+ element_identifier: "element-2",
+ element_type: "hdca",
+ object: {
+ id: "item-2",
+ collection_type: "list",
+ element_count: 2,
+ elements_datatypes: ["txt"],
+ elements: [
+ {
+ element_index: 3,
+ element_identifier: "element-3",
+ element_type: "hda",
+ object: {
+ id: "item_3",
+ },
+ },
+ {
+ element_index: 4,
+ element_identifier: "element-4",
+ element_type: "hda",
+ object: {
+ id: "item_4",
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ localVue,
+ });
+ });
+
+ it("check basics", async () => {
+ const contentItems = wrapper.findAll(".content-item");
+ expect(contentItems.length).toBe(2);
+ expect(contentItems.at(0).attributes("data-hid")).toBe("1");
+ expect(contentItems.at(1).attributes("data-hid")).toBe("2");
+ await contentItems.at(1).find(".cursor-pointer").trigger("click");
+ const contentExpanded = wrapper.findAll(".content-item");
+ expect(contentExpanded.length).toBe(4);
+ expect(contentExpanded.at(2).attributes("data-hid")).toBe("3");
+ expect(contentExpanded.at(3).attributes("data-hid")).toBe("4");
+ });
+});
diff --git a/client/src/components/History/Content/GenericElement.vue b/client/src/components/History/Content/GenericElement.vue
index 7b8f4068501..d12d9f1de86 100644
--- a/client/src/components/History/Content/GenericElement.vue
+++ b/client/src/components/History/Content/GenericElement.vue
@@ -1,36 +1,40 @@
+
+
-
-
diff --git a/client/src/components/History/Content/GenericItem.vue b/client/src/components/History/Content/GenericItem.vue
index 4b93fe579b0..adda51b27de 100644
--- a/client/src/components/History/Content/GenericItem.vue
+++ b/client/src/components/History/Content/GenericItem.vue
@@ -15,9 +15,7 @@
@undelete="onUndelete(item)"
@unhide="onUnhide(item)" />
diff --git a/client/src/components/History/Content/model/states.js b/client/src/components/History/Content/model/states.js
index 150d990b228..f37800d8282 100644
--- a/client/src/components/History/Content/model/states.js
+++ b/client/src/components/History/Content/model/states.js
@@ -33,6 +33,7 @@ export const STATES = {
/** metadata discovery/setting failed or errored (but otherwise ok) */
failed_metadata: {
status: "danger",
+ text: "Metadata generation failed. Please retry.",
icon: "exclamation-triangle",
},
/** was created without a tool */
diff --git a/client/src/components/History/CurrentHistory/HistoryNavigation.test.js b/client/src/components/History/CurrentHistory/HistoryNavigation.test.js
index e0486579328..3fffa8e50ec 100644
--- a/client/src/components/History/CurrentHistory/HistoryNavigation.test.js
+++ b/client/src/components/History/CurrentHistory/HistoryNavigation.test.js
@@ -56,6 +56,11 @@ describe("History Navigation", () => {
provide: { store },
});
+ const createButton = wrapper.find("*[data-description='create new history']");
+ expect(createButton.attributes().disabled).toBeFalsy();
+ const switchButton = wrapper.find("*[data-description='switch to another history']");
+ expect(switchButton.attributes().disabled).toBeFalsy();
+
const dropDown = wrapper.find("*[data-description='history options']");
const optionElements = dropDown.findAll("b-dropdown-item-stub");
const optionTexts = optionElements.wrappers.map((el) => el.text());
@@ -76,8 +81,12 @@ describe("History Navigation", () => {
provide: { store },
});
- const dropDown = wrapper.find("*[data-description='history options']");
+ const createButton = wrapper.find("*[data-description='create new history']");
+ expect(createButton.attributes().disabled).toBeTruthy();
+ const switchButton = wrapper.find("*[data-description='switch to another history']");
+ expect(switchButton.attributes().disabled).toBeTruthy();
+ const dropDown = wrapper.find("*[data-description='history options']");
const enabledOptionElements = dropDown.findAll("b-dropdown-item-stub:not([disabled])");
const enabledOptionTexts = enabledOptionElements.wrappers.map((el) => el.text());
expect(enabledOptionTexts).toStrictEqual(anonymousOptions);
diff --git a/client/src/components/History/CurrentHistory/HistoryNavigation.vue b/client/src/components/History/CurrentHistory/HistoryNavigation.vue
index 9d73f41cf6a..adc4d45a35e 100644
--- a/client/src/components/History/CurrentHistory/HistoryNavigation.vue
+++ b/client/src/components/History/CurrentHistory/HistoryNavigation.vue
@@ -12,7 +12,8 @@
data-description="create new history"
size="sm"
variant="link"
- title="Create new history"
+ :disabled="currentUser.isAnonymous"
+ :title="userTitle('Create new history')"
@click="$emit('createNewHistory')">
@@ -23,7 +24,8 @@
data-description="switch to another history"
size="sm"
variant="link"
- title="Switch to history">
+ :disabled="currentUser.isAnonymous"
+ :title="userTitle('Switch to history')">
diff --git a/client/src/components/History/Modals/SelectorModal.test.js b/client/src/components/History/Modals/SelectorModal.test.js
index bc026247c84..121e911f63b 100644
--- a/client/src/components/History/Modals/SelectorModal.test.js
+++ b/client/src/components/History/Modals/SelectorModal.test.js
@@ -5,7 +5,7 @@ import SelectorModal from "./SelectorModal";
const localVue = getLocalVue();
-const SELECTED_HISTORY_ID = "COOL_ID";
+const CURRENT_HISTORY_ID = "COOL_ID";
const getFakeHistorySummaries = (num, selectedIndex = 0) => {
const result = Array.from({ length: num }, (_, index) => ({
id: `ID-${index}`,
@@ -13,11 +13,11 @@ const getFakeHistorySummaries = (num, selectedIndex = 0) => {
tags: [],
update_time: new Date().toISOString(),
}));
- result[selectedIndex].id = SELECTED_HISTORY_ID;
+ result[selectedIndex].id = CURRENT_HISTORY_ID;
return result;
};
const PROPS_WITH_10_HISTORIES = {
- currentHistoryId: SELECTED_HISTORY_ID,
+ currentHistoryId: CURRENT_HISTORY_ID,
histories: getFakeHistorySummaries(10),
perPage: 3,
static: true, // Force the modal visible for testing
@@ -27,6 +27,8 @@ const PROPS_WITH_10_HISTORY_MULTIPLE_SELECT = {
multiple: true,
};
+const CURRENT_HISTORY_INDICATION_TEXT = "(Current)";
+
describe("History SelectorModal.vue", () => {
let wrapper;
@@ -38,12 +40,11 @@ describe("History SelectorModal.vue", () => {
await flushPromises();
}
- it("should highlight the currently selected history", async () => {
+ it("should indicate the currently selected history", async () => {
await mountWith(PROPS_WITH_10_HISTORIES);
- const selectedRows = wrapper.findAll(".table-info");
- expect(selectedRows.length).toBe(1);
- expect(selectedRows.at(0).attributes("data-pk")).toBe(SELECTED_HISTORY_ID);
+ const currentHistoryRow = wrapper.find(`[data-pk="${CURRENT_HISTORY_ID}"]`);
+ expect(currentHistoryRow.html()).toContain(CURRENT_HISTORY_INDICATION_TEXT);
});
it("paginates the histories", async () => {
@@ -67,25 +68,29 @@ describe("History SelectorModal.vue", () => {
expect(wrapper.emitted()["selectHistory"][0][0].id).toBe(targetHistoryId);
});
- it("select multiple histories", async () => {
- await mountWith(PROPS_WITH_10_HISTORY_MULTIPLE_SELECT);
+ describe("Multi-selection Mode", () => {
+ it("should select multiple histories", async () => {
+ await mountWith(PROPS_WITH_10_HISTORY_MULTIPLE_SELECT);
- expect(wrapper.emitted()["selectHistories"]).toBeUndefined();
+ expect(wrapper.emitted()["selectHistories"]).toBeUndefined();
- const targetHistoryId1 = "ID-1";
- const targetRow1 = wrapper.find(`[data-pk="${targetHistoryId1}"]`);
- await targetRow1.trigger("click");
+ const targetHistoryId1 = "ID-1";
+ const targetRow1 = wrapper.find(`[data-pk="${targetHistoryId1}"]`);
+ await targetRow1.trigger("click");
- const targetHistoryId2 = "ID-2";
- const targetRow2 = wrapper.find(`[data-pk="${targetHistoryId2}"]`);
- await targetRow2.trigger("click");
+ const targetHistoryId2 = "ID-2";
+ const targetRow2 = wrapper.find(`[data-pk="${targetHistoryId2}"]`);
+ await targetRow2.trigger("click");
- expect(wrapper.vm.selectedHistories.length).toBe(2);
+ expect(wrapper.vm.selectedHistories.length).toBe(2);
- const button = wrapper.find(".btn-primary");
+ const button = wrapper.find(".btn-primary");
- await button.trigger("click");
+ await button.trigger("click");
- expect(wrapper.emitted()["selectHistories"][0][0][0].id).toBe(targetHistoryId1);
+ expect(wrapper.emitted()["selectHistories"][0][0][0].id).toBe(targetHistoryId1);
+
+ console.debug(wrapper.html());
+ });
});
});
diff --git a/client/src/components/History/Modals/SelectorModal.vue b/client/src/components/History/Modals/SelectorModal.vue
index 76dded0a196..5fdd0440798 100644
--- a/client/src/components/History/Modals/SelectorModal.vue
+++ b/client/src/components/History/Modals/SelectorModal.vue
@@ -11,7 +11,7 @@
primary-key="id"
:fields="fields"
:filter="filter"
- :items="formattedItems"
+ :items="histories"
:per-page="perPage"
:current-page="currentPage"
:selectable="true"
@@ -22,6 +22,9 @@
selected-variant="success"
@row-selected="rowSelected"
@filtered="onFiltered">
+
+ {{ row.item.name }} (Current)
+
@@ -31,7 +34,9 @@
- Add Selected
+
+ Add Selected
+
@@ -69,13 +74,8 @@ export default {
};
},
computed: {
- formattedItems() {
- return this.histories.map((item) => {
- if (item.id === this.currentHistoryId) {
- item._rowVariant = "info";
- }
- return item;
- });
+ isEmptySelection() {
+ return this.selectedHistories.length === 0;
},
},
watch: {
diff --git a/client/src/components/History/README.md b/client/src/components/History/README.md
index 3bf90443ce9..9388c5b3f6e 100644
--- a/client/src/components/History/README.md
+++ b/client/src/components/History/README.md
@@ -1,13 +1,12 @@
### History Panel Component Tree
+
This is not intended to be a complete listing, but a general idea of how the components are intended
to interact with each other.
```html static
-
-
@@ -18,7 +17,7 @@ to interact with each other.
-
+
-
+
()
-```
\ No newline at end of file
+```
diff --git a/client/src/components/HistoryExport/ExportLink.vue b/client/src/components/HistoryExport/ExportLink.vue
index fece9bbe0ce..b4bbdb95b82 100644
--- a/client/src/components/HistoryExport/ExportLink.vue
+++ b/client/src/components/HistoryExport/ExportLink.vue
@@ -3,13 +3,9 @@
{{ link }}
-
+
+
+
(view job details)
diff --git a/client/src/components/Indices/SharingIndicators.vue b/client/src/components/Indices/SharingIndicators.vue
index 38d754eb33c..98031f5f07f 100644
--- a/client/src/components/Indices/SharingIndicators.vue
+++ b/client/src/components/Indices/SharingIndicators.vue
@@ -1,19 +1,17 @@
-
-
+
+
+
+
+
+
diff --git a/client/src/components/Libraries/LibraryFolder/library-folder-table.css b/client/src/components/Libraries/LibraryFolder/library-folder-table.css
index ec0b864166e..ba412838b8e 100644
--- a/client/src/components/Libraries/LibraryFolder/library-folder-table.css
+++ b/client/src/components/Libraries/LibraryFolder/library-folder-table.css
@@ -3,15 +3,15 @@ th:focus {
}
.pagination-input-field {
- max-width: 60px
+ max-width: 60px;
}
.pagination-total-pages-text {
- margin-left: .25rem;
+ margin-left: 0.25rem;
}
.more-text-btn {
- margin-left: .25rem;
+ margin-left: 0.25rem;
font-size: 60%;
color: grey;
}
@@ -20,7 +20,6 @@ th:focus {
width: 40rem;
}
-
.empty-folder-message {
text-align: center;
}
@@ -34,5 +33,5 @@ th:focus {
}
.lib-btn {
- margin-bottom: 2%
+ margin-bottom: 2%;
}
diff --git a/client/src/components/License/LicenseSelector.vue b/client/src/components/License/LicenseSelector.vue
index 0d549e1a43d..a8b40612b78 100644
--- a/client/src/components/License/LicenseSelector.vue
+++ b/client/src/components/License/LicenseSelector.vue
@@ -4,8 +4,10 @@
-
-
+
+
@@ -16,7 +18,9 @@
-
+
diff --git a/client/src/components/LoadingSpan.vue b/client/src/components/LoadingSpan.vue
index 85e79a25c7e..c4c3b9d26b5 100644
--- a/client/src/components/LoadingSpan.vue
+++ b/client/src/components/LoadingSpan.vue
@@ -1,7 +1,7 @@
-
- {{ message }}...
+
+ {{ message }}...
+
+
diff --git a/client/src/components/User/UserPreferences.vue b/client/src/components/User/UserPreferences.vue
index 8a1e02dec11..09afa1e0532 100644
--- a/client/src/components/User/UserPreferences.vue
+++ b/client/src/components/User/UserPreferences.vue
@@ -82,6 +82,11 @@
description="Click here to make all data private."
@click="makeDataPrivate" />
+
+ >
+
+
-
+
{{ error }}
-
+
-
-
+
+
|
- {{ plugin.html }}
- {{ plugin.description }}
+ {{ plugin.html }}
+ {{ plugin.description }}
|
@@ -155,3 +151,38 @@ export default {
},
};
+
+
diff --git a/client/src/components/Workflow/Editor/ConnectionMenu.vue b/client/src/components/Workflow/Editor/ConnectionMenu.vue
index 31dac4eb940..e3be1d641de 100644
--- a/client/src/components/Workflow/Editor/ConnectionMenu.vue
+++ b/client/src/components/Workflow/Editor/ConnectionMenu.vue
@@ -24,7 +24,12 @@
+
+
+
+
+
diff --git a/client/src/components/Workflow/Editor/Forms/FormDefault.test.js b/client/src/components/Workflow/Editor/Forms/FormDefault.test.js
index 7ab61f364dd..c5e10e5a3bd 100644
--- a/client/src/components/Workflow/Editor/Forms/FormDefault.test.js
+++ b/client/src/components/Workflow/Editor/Forms/FormDefault.test.js
@@ -40,7 +40,7 @@ describe("FormDefault", () => {
const title = wrapper.find(".portlet-title-text").text();
expect(title).toBe("label");
const inputCount = wrapper.findAll("input").length;
- expect(inputCount).toBe(3);
+ expect(inputCount).toBe(4);
const outputLabelCount = wrapper.findAll("#__label__output-name").length;
expect(outputLabelCount).toBe(1);
const otherLabelCount = wrapper.findAll("#__label__other-name").length;
diff --git a/client/src/components/Workflow/Editor/Forms/FormDefault.vue b/client/src/components/Workflow/Editor/Forms/FormDefault.vue
index 9f3f77f78c4..6e2a47cbd0d 100644
--- a/client/src/components/Workflow/Editor/Forms/FormDefault.vue
+++ b/client/src/components/Workflow/Editor/Forms/FormDefault.vue
@@ -39,6 +39,7 @@
:area="true"
help="Add an annotation or notes to this step. Annotations are available when a workflow is viewed."
@input="onAnnotation" />
+
(label?.value || contentId?.value || name.value)!);
+const stepTitle = computed(() => {
+ if (label?.value) {
+ return label.value;
+ }
+ if (isSubworkflow.value) {
+ return name.value;
+ } else {
+ return contentId?.value || name.value;
+ }
+});
const nodeIcon = computed(() => WorkflowIcons[type.value]);
const formDisplayId = computed(() => stepId.value.toString());
const isSubworkflow = computed(() => type.value === "subworkflow");
diff --git a/client/src/components/Workflow/Editor/Forms/FormTool.test.js b/client/src/components/Workflow/Editor/Forms/FormTool.test.js
index 2db5af07c0e..01bdcfb973c 100644
--- a/client/src/components/Workflow/Editor/Forms/FormTool.test.js
+++ b/client/src/components/Workflow/Editor/Forms/FormTool.test.js
@@ -11,9 +11,7 @@ import { createTestingPinia } from "@pinia/testing";
const localVue = getLocalVue();
describe("FormTool", () => {
- let wrapper;
-
- beforeEach(() => {
+ function mountTarget() {
const store = new Vuex.Store({
modules: {
user: mockModule(userStore),
@@ -21,7 +19,7 @@ describe("FormTool", () => {
},
});
- wrapper = mount(FormTool, {
+ return mount(FormTool, {
propsData: {
id: "input",
datatypes: [],
@@ -32,7 +30,7 @@ describe("FormTool", () => {
name: "tool_name",
version: "1.0",
description: "description",
- inputs: [],
+ inputs: [{ name: "input", label: "input", type: "text", value: "value" }],
help: "help_text",
versions: ["1.0", "2.0", "3.0"],
citations: false,
@@ -46,15 +44,16 @@ describe("FormTool", () => {
stubs: {
CurrentUser: MockCurrentUser({ id: "fakeuser" }),
ConfigProvider: MockConfigProvider({ id: "fakeconfig" }),
- FormElement: { template: "form-element
" },
ToolFooter: { template: "tool-footer
" },
},
pinia: createTestingPinia(),
provide: { store },
});
- });
+ }
it("changes between different versions", async () => {
+ const wrapper = mountTarget();
+
const dropdowns = wrapper.findAll(".tool-versions .dropdown-item");
let version = dropdowns.at(1);
expect(version.text()).toBe("Switch to 2.0");
diff --git a/client/src/components/Workflow/Editor/Forms/FormTool.vue b/client/src/components/Workflow/Editor/Forms/FormTool.vue
index 09e76c5de96..d56446087be 100644
--- a/client/src/components/Workflow/Editor/Forms/FormTool.vue
+++ b/client/src/components/Workflow/Editor/Forms/FormTool.vue
@@ -27,6 +27,7 @@
:area="true"
help="Add an annotation or notes to this step. Annotations are available when a workflow is viewed."
@input="onAnnotation" />
+
Tool Parameters
diff --git a/client/src/components/Workflow/Editor/TerminalConnector.vue b/client/src/components/Workflow/Editor/TerminalConnector.vue
index 20dfc4a2540..5830982e5d0 100644
--- a/client/src/components/Workflow/Editor/TerminalConnector.vue
+++ b/client/src/components/Workflow/Editor/TerminalConnector.vue
@@ -4,14 +4,16 @@
:id="connectionId"
:position="position"
:output-is-mapped-over="outputIsMappedOver"
- :input-is-mapped-over="inputIsMappedOver">
+ :input-is-mapped-over="inputIsMappedOver"
+ :connection-is-valid="connectionIsValid"
+ :nullable="outputIsOptional">
@@ -35,7 +39,8 @@ const dragStyle = computed(() => {
v-if="draggingTerminal && draggingConnection"
:position="draggingConnection"
:input-is-mapped-over="false"
- :output-is-mapped-over="draggingTerminal.mapOver.isCollection">
+ :output-is-mapped-over="draggingTerminal.mapOver.isCollection"
+ :nullable="dragIsOptional">
= ref(null);
const elementBounding = useElementBounding(canvas, { windowResize: false, windowScroll: false });
const scroll = useScroll(canvas);
-const { transform, panBy, setZoom, moveTo } = useZoom(1, 0.2, 5, canvas, scroll);
+const { transform, panBy, setZoom, moveTo } = useD3Zoom(1, minZoom, maxZoom, canvas, scroll);
const isDragging = ref(false);
provide("isDragging", isDragging);
diff --git a/client/src/components/Workflow/Editor/ZoomControl.vue b/client/src/components/Workflow/Editor/ZoomControl.vue
index 61df33f52f6..081ec21fa7a 100644
--- a/client/src/components/Workflow/Editor/ZoomControl.vue
+++ b/client/src/components/Workflow/Editor/ZoomControl.vue
@@ -1,46 +1,29 @@
+
+
+
+
+ {{ infoString }}
+
+
+ Problem occurred at this step:
+
+
+
+ {{ stepDescription }}
+
+
+
+ This dataset failed:
+
+
+
+ This dataset collection failed:
+
+
+
+ This job failed:
+
+
+
+
diff --git a/client/src/components/WorkflowInvocationState/WorkflowInvocationDetails.vue b/client/src/components/WorkflowInvocationState/WorkflowInvocationDetails.vue
index fcf9af8baba..c8977d2b5c0 100644
--- a/client/src/components/WorkflowInvocationState/WorkflowInvocationDetails.vue
+++ b/client/src/components/WorkflowInvocationState/WorkflowInvocationDetails.vue
@@ -1,8 +1,5 @@
diff --git a/client/src/components/WorkflowInvocationState/WorkflowInvocationSummary.vue b/client/src/components/WorkflowInvocationState/WorkflowInvocationSummary.vue
index 1949dc2661b..5853c90edd1 100644
--- a/client/src/components/WorkflowInvocationState/WorkflowInvocationSummary.vue
+++ b/client/src/components/WorkflowInvocationState/WorkflowInvocationSummary.vue
@@ -12,18 +12,26 @@
title="Download PDF" />
-
+
+
+
+
+
getRootFromIndexLink() + path;
export default {
components: {
+ InvocationMessage,
ProgressBar,
LoadingSpan,
},
diff --git a/client/src/components/WorkflowInvocationState/invocationMessageModel.ts b/client/src/components/WorkflowInvocationState/invocationMessageModel.ts
new file mode 100644
index 00000000000..7c86d43ad17
--- /dev/null
+++ b/client/src/components/WorkflowInvocationState/invocationMessageModel.ts
@@ -0,0 +1,242 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+/* This file was automatically generated from pydantic models by running pydantic2ts.
+/* Do not modify it by hand - just update the pydantic models and then re-run the script
+*/
+
+export type InvocationMessageResponseModel =
+ | GenericInvocationCancellationReviewFailedEncodedDatabaseIdField
+ | GenericInvocationCancellationHistoryDeletedEncodedDatabaseIdField
+ | GenericInvocationCancellationUserRequestEncodedDatabaseIdField
+ | GenericInvocationFailureDatasetFailedEncodedDatabaseIdField
+ | GenericInvocationFailureCollectionFailedEncodedDatabaseIdField
+ | GenericInvocationFailureJobFailedEncodedDatabaseIdField
+ | GenericInvocationFailureOutputNotFoundEncodedDatabaseIdField
+ | GenericInvocationFailureExpressionEvaluationFailedEncodedDatabaseIdField
+ | GenericInvocationFailureWhenNotBooleanEncodedDatabaseIdField
+ | GenericInvocationUnexpectedFailureEncodedDatabaseIdField
+ | GenericInvocationEvaluationWarningWorkflowOutputNotFoundEncodedDatabaseIdField;
+
+export interface GenericInvocationCancellationHistoryDeletedEncodedDatabaseIdField {
+ reason: "history_deleted";
+ /**
+ * History ID of history that was deleted.
+ */
+ history_id: string;
+}
+export interface GenericInvocationCancellationHistoryDeletedInt {
+ reason: "history_deleted";
+ /**
+ * History ID of history that was deleted.
+ */
+ history_id: number;
+}
+export interface GenericInvocationCancellationReviewFailedEncodedDatabaseIdField {
+ reason: "cancelled_on_review";
+ /**
+ * Workflow step id of paused step that did not pass review.
+ */
+ workflow_step_id: number;
+}
+export interface GenericInvocationCancellationReviewFailedInt {
+ reason: "cancelled_on_review";
+ /**
+ * Workflow step id of paused step that did not pass review.
+ */
+ workflow_step_id: number;
+}
+export interface GenericInvocationCancellationUserRequestEncodedDatabaseIdField {
+ reason: "user_request";
+}
+export interface GenericInvocationCancellationUserRequestInt {
+ reason: "user_request";
+}
+export interface GenericInvocationEvaluationWarningWorkflowOutputNotFoundEncodedDatabaseIdField {
+ reason: "workflow_output_not_found";
+ workflow_step_id: number;
+ /**
+ * Output that was designated as workflow output but that has not been found
+ */
+ output_name: string;
+}
+export interface GenericInvocationEvaluationWarningWorkflowOutputNotFoundInt {
+ reason: "workflow_output_not_found";
+ workflow_step_id: number;
+ /**
+ * Output that was designated as workflow output but that has not been found
+ */
+ output_name: string;
+}
+export interface GenericInvocationFailureCollectionFailedEncodedDatabaseIdField {
+ reason: "collection_failed";
+ /**
+ * Workflow step id of step that failed.
+ */
+ workflow_step_id: number;
+ /**
+ * HistoryDatasetCollectionAssociation ID that relates to failure.
+ */
+ hdca_id?: string;
+ /**
+ * Workflow step id of step that caused failure.
+ */
+ dependent_workflow_step_id: number;
+}
+export interface GenericInvocationFailureCollectionFailedInt {
+ reason: "collection_failed";
+ /**
+ * Workflow step id of step that failed.
+ */
+ workflow_step_id: number;
+ /**
+ * HistoryDatasetCollectionAssociation ID that relates to failure.
+ */
+ hdca_id?: number;
+ /**
+ * Workflow step id of step that caused failure.
+ */
+ dependent_workflow_step_id: number;
+}
+export interface GenericInvocationFailureDatasetFailedEncodedDatabaseIdField {
+ reason: "dataset_failed";
+ /**
+ * Workflow step id of step that failed.
+ */
+ workflow_step_id: number;
+ /**
+ * HistoryDatasetAssociation ID that relates to failure.
+ */
+ hda_id: string;
+ /**
+ * Workflow step id of step that caused failure.
+ */
+ dependent_workflow_step_id?: number;
+}
+export interface GenericInvocationFailureDatasetFailedInt {
+ reason: "dataset_failed";
+ /**
+ * Workflow step id of step that failed.
+ */
+ workflow_step_id: number;
+ /**
+ * HistoryDatasetAssociation ID that relates to failure.
+ */
+ hda_id: number;
+ /**
+ * Workflow step id of step that caused failure.
+ */
+ dependent_workflow_step_id?: number;
+}
+export interface GenericInvocationFailureExpressionEvaluationFailedEncodedDatabaseIdField {
+ reason: "expression_evaluation_failed";
+ /**
+ * Workflow step id of step that failed.
+ */
+ workflow_step_id: number;
+ /**
+ * May contain details to help troubleshoot this problem.
+ */
+ details?: string;
+}
+export interface GenericInvocationFailureExpressionEvaluationFailedInt {
+ reason: "expression_evaluation_failed";
+ /**
+ * Workflow step id of step that failed.
+ */
+ workflow_step_id: number;
+ /**
+ * May contain details to help troubleshoot this problem.
+ */
+ details?: string;
+}
+export interface GenericInvocationFailureJobFailedEncodedDatabaseIdField {
+ reason: "job_failed";
+ /**
+ * Workflow step id of step that failed.
+ */
+ workflow_step_id: number;
+ /**
+ * Job ID that relates to failure.
+ */
+ job_id?: string;
+ /**
+ * Workflow step id of step that caused failure.
+ */
+ dependent_workflow_step_id: number;
+}
+export interface GenericInvocationFailureJobFailedInt {
+ reason: "job_failed";
+ /**
+ * Workflow step id of step that failed.
+ */
+ workflow_step_id: number;
+ /**
+ * Job ID that relates to failure.
+ */
+ job_id?: number;
+ /**
+ * Workflow step id of step that caused failure.
+ */
+ dependent_workflow_step_id: number;
+}
+export interface GenericInvocationFailureOutputNotFoundEncodedDatabaseIdField {
+ reason: "output_not_found";
+ /**
+ * Workflow step id of step that failed.
+ */
+ workflow_step_id: number;
+ output_name: string;
+ /**
+ * Workflow step id of step that caused failure.
+ */
+ dependent_workflow_step_id: number;
+}
+export interface GenericInvocationFailureOutputNotFoundInt {
+ reason: "output_not_found";
+ /**
+ * Workflow step id of step that failed.
+ */
+ workflow_step_id: number;
+ output_name: string;
+ /**
+ * Workflow step id of step that caused failure.
+ */
+ dependent_workflow_step_id: number;
+}
+export interface GenericInvocationFailureWhenNotBooleanEncodedDatabaseIdField {
+ reason: "when_not_boolean";
+ /**
+ * Workflow step id of step that failed.
+ */
+ workflow_step_id: number;
+ /**
+ * Contains details to help troubleshoot this problem.
+ */
+ details: string;
+}
+export interface GenericInvocationFailureWhenNotBooleanInt {
+ reason: "when_not_boolean";
+ /**
+ * Workflow step id of step that failed.
+ */
+ workflow_step_id: number;
+ /**
+ * Contains details to help troubleshoot this problem.
+ */
+ details: string;
+}
+export interface GenericInvocationUnexpectedFailureEncodedDatabaseIdField {
+ reason: "unexpected_failure";
+ /**
+ * May contains details to help troubleshoot this problem.
+ */
+ details?: string;
+}
+export interface GenericInvocationUnexpectedFailureInt {
+ reason: "unexpected_failure";
+ /**
+ * May contains details to help troubleshoot this problem.
+ */
+ details?: string;
+}
diff --git a/client/src/composables/datatypes.ts b/client/src/composables/datatypes.ts
index 88a38f045f4..cb29633e4ec 100644
--- a/client/src/composables/datatypes.ts
+++ b/client/src/composables/datatypes.ts
@@ -29,7 +29,7 @@ export function useDetailedDatatypes() {
async function getDatatypes() {
try {
- const datatypesPromise = datatypesFetcher({ extension_only: true });
+ const datatypesPromise = datatypesFetcher({ extension_only: false });
const datatypeEDAMFormatsPromise = edamFormatsFetcher({});
const datatypeEDAMDataPromise = edamDataFetcher({});
diff --git a/client/src/composables/fileDrop.js b/client/src/composables/fileDrop.ts
similarity index 52%
rename from client/src/composables/fileDrop.js
rename to client/src/composables/fileDrop.ts
index 76e8ed1609f..0d71b92425b 100644
--- a/client/src/composables/fileDrop.js
+++ b/client/src/composables/fileDrop.ts
@@ -1,5 +1,8 @@
-import { ref, unref } from "vue";
-import { useEventListener } from "@vueuse/core";
+import { ref, unref, type Ref } from "vue";
+import { useEventListener, type MaybeComputedRef } from "@vueuse/core";
+import { wait } from "@/utils/wait";
+
+export type FileDropHandler = (event: DragEvent) => void;
/**
* Custom File-Drop composable
@@ -7,18 +10,26 @@ import { useEventListener } from "@vueuse/core";
* @param onDrop callback function called when drop occurs
* @param solo when true, only reacts if no modal is open
*/
-export function useFileDrop(dropZone, onDrop, solo) {
+export function useFileDrop(
+ dropZone: MaybeComputedRef,
+ onDrop: Ref | FileDropHandler,
+ solo: MaybeComputedRef
+) {
const isFileOverDocument = ref(false);
const isFileOverDropZone = ref(false);
- const dragBlocked = ref(false);
+ // blocks drag events in this composable, to avoid drag events in unwanted situations
+ let dragBlocked = false;
+
+ // keeps track if the drag has exited, to avoid premature drag canceling
+ let hasExited = true;
// Don't react to page-internal drag events
useEventListener(
document.body,
"dragstart",
() => {
- dragBlocked.value = true;
+ dragBlocked = true;
},
true
);
@@ -27,9 +38,10 @@ export function useFileDrop(dropZone, onDrop, solo) {
document.body,
"dragover",
(event) => {
- if (!dragBlocked.value) {
+ if (!dragBlocked) {
// prevent the browser from opening the file
event.preventDefault();
+ hasExited = false;
}
},
true
@@ -39,40 +51,54 @@ export function useFileDrop(dropZone, onDrop, solo) {
document.body,
"drop",
(event) => {
- if (!dragBlocked.value) {
+ if (!dragBlocked) {
// prevent the browser from opening the file
event.preventDefault();
if (isFileOverDropZone.value && isFileOverDocument.value) {
- unref(onDrop)(event);
+ const dropHandler = unref(onDrop);
+ dropHandler(event as DragEvent);
}
}
isFileOverDocument.value = false;
- dragBlocked.value = false;
+ dragBlocked = false;
+ hasExited = true;
},
true
);
- useEventListener(
- document.body,
- "dragend",
- () => {
- // reset on drag end
- isFileOverDocument.value = false;
- isFileOverDropZone.value = false;
- dragBlocked.value = false;
- },
- true
- );
+ /** Reset all variables */
+ const reset = () => {
+ isFileOverDocument.value = false;
+ isFileOverDropZone.value = false;
+ dragBlocked = false;
+ hasExited = true;
+ };
+
+ useEventListener(document.body, "dragend", reset, true);
+
+ useEventListener(document.body, "dragleave", async () => {
+ hasExited = true;
+
+ // This event may have been triggered by components
+ // which have not been properly childed to the body yet.
+ // Wait a bit, and check if hasExited is still true.
+ await wait(100);
+
+ if (hasExited) {
+ reset();
+ }
+ });
useEventListener(
document.body,
"dragenter",
(event) => {
// init values if drag is possible
- if (!dragBlocked.value && !(unref(solo) && isAnyModalOpen())) {
+ if (!dragBlocked && !(unref(solo) && isAnyModalOpen())) {
isFileOverDocument.value = true;
isFileOverDropZone.value = false;
+ hasExited = false;
event.preventDefault();
}
@@ -90,6 +116,7 @@ export function useFileDrop(dropZone, onDrop, solo) {
"dragenter",
() => {
isFileOverDropZone.value = true;
+ hasExited = false;
},
true
);
@@ -99,6 +126,7 @@ export function useFileDrop(dropZone, onDrop, solo) {
"dragleave",
() => {
isFileOverDropZone.value = false;
+ hasExited = false;
},
true
);
diff --git a/client/src/composables/useWorkflowInstance.ts b/client/src/composables/useWorkflowInstance.ts
new file mode 100644
index 00000000000..4e1a376397b
--- /dev/null
+++ b/client/src/composables/useWorkflowInstance.ts
@@ -0,0 +1,23 @@
+import { useWorkflowStore } from "@/stores/workflowStore";
+import { ref } from "vue";
+
+export function useWorkflowInstance(workflowId: string) {
+ const workflowStore = useWorkflowStore();
+ const workflow = ref(workflowStore.getWorkflowByInstanceId(workflowId));
+ const loading = ref(false);
+
+ async function getWorkflowInstance() {
+ if (!workflow.value) {
+ loading.value = true;
+ try {
+ await workflowStore.fetchWorkflowForInstanceId(workflowId);
+ } catch (e) {
+ loading.value = false;
+ console.error("unable to fetch workflow \n", e);
+ }
+ }
+ }
+ getWorkflowInstance();
+
+ return { workflow, loading };
+}
diff --git a/client/src/entry/analysis/router.js b/client/src/entry/analysis/router.js
index 56e2bdac7e9..558e962322f 100644
--- a/client/src/entry/analysis/router.js
+++ b/client/src/entry/analysis/router.js
@@ -235,6 +235,12 @@ export function getRouter(Galaxy) {
component: HistoryPublishedList,
props: true,
},
+ {
+ path: "histories/list",
+ component: GridHistory,
+ props: true,
+ redirect: redirectAnon(),
+ },
{
path: "histories/:historyId/export",
get component() {
@@ -242,11 +248,6 @@ export function getRouter(Galaxy) {
},
props: true,
},
- {
- path: "histories/:actionId",
- component: GridHistory,
- props: true,
- },
{
path: "interactivetool_entry_points/list",
component: InteractiveTools,
diff --git a/client/src/mvc/dataset/data.js b/client/src/mvc/dataset/data.js
index 2705f3a6960..78cadb2c434 100644
--- a/client/src/mvc/dataset/data.js
+++ b/client/src/mvc/dataset/data.js
@@ -3,7 +3,7 @@ import Backbone from "backbone";
import { getAppRoot } from "onload/loadConfig";
//temporary
-import { appendVueComponent } from "utils/mountVueComponent";
+import { replaceChildrenWithComponent } from "utils/mountVueComponent";
import TabularChunkedView from "components/Visualizations/Tabular/TabularChunkedView.vue";
/**
@@ -72,5 +72,5 @@ export var DatasetCollection = Backbone.Collection.extend({
export const createTabularDatasetChunkedView = (options) => {
// We'll always have a parent_elt in options, so create a div and insert it into that.
- return appendVueComponent(options.parent_elt, TabularChunkedView, { options });
+ return replaceChildrenWithComponent(options.parent_elt, TabularChunkedView, { options });
};
diff --git a/client/src/mvc/visualization/chart/chart-client.js b/client/src/mvc/visualization/chart/chart-client.js
index 3b94a33dda4..4a030f5c10b 100644
--- a/client/src/mvc/visualization/chart/chart-client.js
+++ b/client/src/mvc/visualization/chart/chart-client.js
@@ -31,12 +31,7 @@ export default Backbone.View.extend({
this.$buttons = this.$(".charts-buttons");
this.chart = new Chart({}, options);
this.chart.plugin = options.visualization_plugin;
- this.chart.plugin.specs = this.chart.plugin.specs || {};
- if (Object.keys(this.chart.plugin.specs).length === 0) {
- this.chart.requiresConfirmation = false;
- } else {
- this.chart.requiresConfirmation = asBoolean(this.chart.plugin.specs.confirm);
- }
+ this.chart.requiresConfirmation = asBoolean(this.chart.plugin.specs?.confirm);
this.chart_load = options.chart_load;
this.message = new Ui.Message();
this.deferred = new Deferred();
diff --git a/client/src/mvc/visualization/chart/views/groups.js b/client/src/mvc/visualization/chart/views/groups.js
index 8fdaafdaa9c..150e13ea2b5 100644
--- a/client/src/mvc/visualization/chart/views/groups.js
+++ b/client/src/mvc/visualization/chart/views/groups.js
@@ -8,7 +8,7 @@ import Utils from "utils/utils";
import Repeat from "./repeat";
import { visitInputs } from "components/Form/utilities";
import FormDisplay from "components/Form/FormDisplay";
-import { appendVueComponent } from "utils/mountVueComponent";
+import { replaceChildrenWithComponent } from "utils/mountVueComponent";
var GroupView = Backbone.View.extend({
initialize: function (app, options) {
@@ -75,7 +75,7 @@ var GroupView = Backbone.View.extend({
params[name] = input.value;
});
self.redraw(params);
- const instance = appendVueComponent(self.$el, FormDisplay, {
+ const instance = replaceChildrenWithComponent(self.el, FormDisplay, {
inputs: inputs,
});
instance.$on("onChange", (data) => {
diff --git a/client/src/mvc/visualization/chart/views/menu.js b/client/src/mvc/visualization/chart/views/menu.js
index e1f259704cb..283c329767f 100644
--- a/client/src/mvc/visualization/chart/views/menu.js
+++ b/client/src/mvc/visualization/chart/views/menu.js
@@ -134,14 +134,14 @@ export default Backbone.View.extend({
},
render: function () {
- var visible = this.model.get("visible");
+ const visible = this.model.get("visible");
this.app.$el[visible ? "removeClass" : "addClass"]("charts-fullscreen");
- this.execute_button.model.set("visible", visible && !!this.app.chart.plugin.specs.confirm);
+ this.execute_button.model.set("visible", visible && !!this.app.chart.plugin.specs?.confirm);
this.save_button.model.set("visible", visible);
this.export_button.model.set("visible", visible);
this.right_button.model.set("visible", visible);
this.left_button.model.set("visible", !visible);
- var exports = this.app.chart.plugin.specs.exports || [];
+ const exports = this.app.chart.plugin.specs?.exports ?? [];
this.export_button.collection.each((model) => {
model.set("visible", exports.indexOf(model.get("key")) !== -1);
});
diff --git a/client/src/mvc/visualization/chart/views/settings.js b/client/src/mvc/visualization/chart/views/settings.js
index db0475bebdc..80f6e03e042 100644
--- a/client/src/mvc/visualization/chart/views/settings.js
+++ b/client/src/mvc/visualization/chart/views/settings.js
@@ -3,7 +3,7 @@ import Backbone from "backbone";
import Utils from "utils/utils";
import { visitInputs } from "components/Form/utilities";
import FormDisplay from "components/Form/FormDisplay";
-import { appendVueComponent } from "utils/mountVueComponent";
+import { replaceChildrenWithComponent } from "utils/mountVueComponent";
export default Backbone.View.extend({
initialize: function (app) {
@@ -27,7 +27,7 @@ export default Backbone.View.extend({
self.chart.settings.set(name, input.value);
}
});
- const instance = appendVueComponent(this.$el, FormDisplay, {
+ const instance = replaceChildrenWithComponent(this.el, FormDisplay, {
inputs: inputs,
});
instance.$on("onChange", (data) => {
diff --git a/client/src/schema/schema.ts b/client/src/schema/schema.ts
index 29886efe1fb..4b0b6e8dca8 100644
--- a/client/src/schema/schema.ts
+++ b/client/src/schema/schema.ts
@@ -1169,6 +1169,18 @@ export interface paths {
/** Return the user's API key with extra information. */
get: operations["get_api_key_detailed_api_users__user_id__api_key_detailed_get"];
};
+ "/api/users/{user_id}/beacon": {
+ /**
+ * Returns information about beacon share settings
+ * @description **Warning**: This endpoint is experimental and might change or disappear in future versions.
+ */
+ get: operations["get_beacon_api_users__user_id__beacon_get"];
+ /**
+ * Changes beacon setting
+ * @description **Warning**: This endpoint is experimental and might change or disappear in future versions.
+ */
+ post: operations["set_beacon_api_users__user_id__beacon_post"];
+ };
"/api/version": {
/**
* Return Galaxy version information: major/minor version, optional extra info
@@ -4087,6 +4099,12 @@ export interface components {
* @default 0
*/
running?: number;
+ /**
+ * Skipped jobs
+ * @description Number of jobs that were skipped due to conditional workflow step execution.
+ * @default 0
+ */
+ skipped?: number;
/**
* Upload jobs
* @description Number of jobs in the `upload` state.
@@ -7188,6 +7206,17 @@ export interface components {
*/
url: string;
};
+ /**
+ * UserBeaconSetting
+ * @description Base model definition with common configuration used by all derived models.
+ */
+ UserBeaconSetting: {
+ /**
+ * Enabled
+ * @description True if beacon sharing is enabled
+ */
+ enabled: boolean;
+ };
/**
* UserEmail
* @description Base model definition with common configuration used by all derived models.
@@ -7493,7 +7522,8 @@ export interface components {
| "deleted"
| "deleted_new"
| "stop"
- | "stopped";
+ | "stopped"
+ | "skipped";
/**
* populated_states
* @description An enumeration.
@@ -13916,6 +13946,71 @@ export interface operations {
};
};
};
+ get_beacon_api_users__user_id__beacon_get: {
+ /**
+ * Returns information about beacon share settings
+ * @description **Warning**: This endpoint is experimental and might change or disappear in future versions.
+ */
+ parameters: {
+ /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
+ header?: {
+ "run-as"?: string;
+ };
+ /** @description The ID of the user to get. */
+ path: {
+ user_id: string;
+ };
+ };
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ content: {
+ "application/json": components["schemas"]["UserBeaconSetting"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
+ set_beacon_api_users__user_id__beacon_post: {
+ /**
+ * Changes beacon setting
+ * @description **Warning**: This endpoint is experimental and might change or disappear in future versions.
+ */
+ parameters: {
+ /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
+ header?: {
+ "run-as"?: string;
+ };
+ /** @description The ID of the user to get. */
+ path: {
+ user_id: string;
+ };
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["UserBeaconSetting"];
+ };
+ };
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ content: {
+ "application/json": components["schemas"]["UserBeaconSetting"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
version_api_version_get: {
/**
* Return Galaxy version information: major/minor version, optional extra info
diff --git a/client/src/stores/workflowConnectionStore.ts b/client/src/stores/workflowConnectionStore.ts
index d2730db36f1..04a46a09f2c 100644
--- a/client/src/stores/workflowConnectionStore.ts
+++ b/client/src/stores/workflowConnectionStore.ts
@@ -1,8 +1,15 @@
import { defineStore } from "pinia";
import { useWorkflowStepStore } from "@/stores/workflowStepStore";
+import { state } from "@/store/tagStore";
+import Vue from "vue";
+
+interface InvalidConnections {
+ [index: string]: string | undefined;
+}
export interface State {
connections: Connection[];
+ invalidConnections: InvalidConnections;
}
export class Connection {
@@ -27,6 +34,7 @@ export interface BaseTerminal {
export interface InputTerminal extends BaseTerminal {
connectorType: "input";
+ input_subworkflow_step_id?: number;
}
export interface OutputTerminal extends BaseTerminal {
@@ -58,6 +66,7 @@ function pushOrSet(object: { [key: string | number]: Array }, key: string
export const useConnectionStore = defineStore("workflowConnectionStore", {
state: (): State => ({
connections: [] as Connection[],
+ invalidConnections: {} as InvalidConnections,
}),
getters: {
getOutputTerminalsForInputTerminal(state: State) {
@@ -111,12 +120,19 @@ export const useConnectionStore = defineStore("workflowConnectionStore", {
const stepStore = useWorkflowStepStore();
stepStore.addConnection(connection);
},
+ markInvalidConnection(this: State, connectionId: string, reason: string) {
+ Vue.set(this.invalidConnections, connectionId, reason);
+ },
+ dropFromInvalidConnections(this: State, connectionId: string) {
+ Vue.delete(this.invalidConnections, connectionId);
+ },
removeConnection(this: State, terminal: InputTerminal | OutputTerminal | Connection["id"]) {
const stepStore = useWorkflowStepStore();
this.connections = this.connections.filter((connection) => {
if (typeof terminal === "string") {
if (connection.id == terminal) {
stepStore.removeConnection(connection);
+ Vue.delete(this.invalidConnections, connection.id);
return false;
} else {
return true;
@@ -124,6 +140,7 @@ export const useConnectionStore = defineStore("workflowConnectionStore", {
} else if (terminal.connectorType === "input") {
if (connection.input.stepId == terminal.stepId && connection.input.name == terminal.name) {
stepStore.removeConnection(connection);
+ Vue.delete(this.invalidConnections, connection.id);
return false;
} else {
return true;
@@ -131,6 +148,7 @@ export const useConnectionStore = defineStore("workflowConnectionStore", {
} else {
if (connection.output.stepId == terminal.stepId && connection.output.name == terminal.name) {
stepStore.removeConnection(connection);
+ Vue.delete(this.invalidConnections, connection.id);
return false;
} else {
return true;
diff --git a/client/src/stores/workflowStepStore.ts b/client/src/stores/workflowStepStore.ts
index 262fe91271c..9bf5e39cb94 100644
--- a/client/src/stores/workflowStepStore.ts
+++ b/client/src/stores/workflowStepStore.ts
@@ -9,6 +9,7 @@ interface State {
steps: { [index: string]: Step };
stepIndex: number;
stepMapOver: { [index: number]: CollectionTypeDescriptor };
+ stepInputMapOver: StepInputMapOver;
}
interface StepPosition {
@@ -63,6 +64,7 @@ interface BaseStepInput {
extensions: string[];
optional: boolean;
input_type: string;
+ input_subworkflow_step_id?: number;
}
export interface DataStepInput extends BaseStepInput {
@@ -79,14 +81,14 @@ export interface ParameterStepInput extends Omit {
type: typeof ParameterTypes;
}
-type InputTerminalSource = DataStepInput | DataCollectionStepInput | ParameterStepInput;
-type OutputTerminalSource = DataOutput | CollectionOutput | ParameterOutput;
+export type InputTerminalSource = DataStepInput | DataCollectionStepInput | ParameterStepInput;
+export type OutputTerminalSource = DataOutput | CollectionOutput | ParameterOutput;
export type TerminalSource = InputTerminalSource | OutputTerminalSource;
interface WorkflowOutput {
output_name: string;
- label?: string;
- uuid?: string;
+ label?: string | null;
+ uuid?: string | null;
}
export interface NewStep {
@@ -97,7 +99,7 @@ export interface NewStep {
errors?: string[] | null;
input_connections: StepInputConnection;
inputs: Array;
- label?: string;
+ label?: string | null;
name: string;
outputs: Array;
position?: StepPosition;
@@ -106,6 +108,7 @@ export interface NewStep {
tooltip?: string;
type: "tool" | "data_input" | "data_collection_input" | "subworkflow" | "parameter_input" | "pause";
uuid?: string;
+ when?: string | null;
workflow_outputs?: WorkflowOutput[];
}
@@ -118,12 +121,13 @@ export interface Steps {
}
export interface StepInputConnection {
- [index: string]: ConnectionOutputLink | ConnectionOutputLink[];
+ [index: string]: ConnectionOutputLink | ConnectionOutputLink[] | undefined;
}
export interface ConnectionOutputLink {
output_name: string;
id: number;
+ input_subworkflow_step_id?: number;
}
interface WorkflowOutputs {
@@ -133,10 +137,15 @@ interface WorkflowOutputs {
};
}
+interface StepInputMapOver {
+ [index: number]: { [index: string]: CollectionTypeDescriptor };
+}
+
export const useWorkflowStepStore = defineStore("workflowStepStore", {
state: (): State => ({
steps: {} as Steps,
stepMapOver: {} as { [index: number]: CollectionTypeDescriptor },
+ stepInputMapOver: {} as StepInputMapOver,
stepIndex: -1,
}),
getters: {
@@ -145,6 +154,32 @@ export const useWorkflowStepStore = defineStore("workflowStepStore", {
return state.steps[stepId.toString()];
};
},
+ getStepExtraInputs(state: State) {
+ const extraInputs: { [index: number]: InputTerminalSource[] } = {};
+ Object.values(state.steps).forEach((step) => {
+ if (step?.when !== undefined) {
+ Object.keys(step.input_connections).forEach((inputName) => {
+ if (!step.inputs.find((input) => input.name === inputName) && step.when?.includes(inputName)) {
+ const terminalSource = {
+ name: inputName,
+ optional: false,
+ input_type: "parameter" as const,
+ type: "boolean" as const,
+ multiple: false,
+ label: inputName,
+ extensions: [],
+ };
+ if (extraInputs[step.id]) {
+ extraInputs[step.id].push(terminalSource);
+ } else {
+ extraInputs[step.id] = [terminalSource];
+ }
+ }
+ });
+ }
+ });
+ return (stepId: number) => extraInputs[stepId] || [];
+ },
getStepIndex(state: State) {
return Math.max(...Object.values(state.steps).map((step) => step.id), state.stepIndex);
},
@@ -185,7 +220,17 @@ export const useWorkflowStepStore = defineStore("workflowStepStore", {
this.steps[step.id.toString()] = step;
},
changeStepMapOver(stepId: number, mapOver: CollectionTypeDescriptor) {
- this.stepMapOver[stepId] = mapOver;
+ Vue.set(this.stepMapOver, stepId, mapOver);
+ },
+ resetStepInputMapOver(stepId: number) {
+ Vue.set(this.stepInputMapOver, stepId, {});
+ },
+ changeStepInputMapOver(stepId: number, inputName: string, mapOver: CollectionTypeDescriptor) {
+ if (this.stepInputMapOver[stepId]) {
+ Vue.set(this.stepInputMapOver[stepId], inputName, mapOver);
+ } else {
+ Vue.set(this.stepInputMapOver, stepId, { [inputName]: mapOver });
+ }
},
addConnection(connection: Connection) {
const inputStep = this.getStep(connection.input.stepId);
@@ -193,11 +238,19 @@ export const useWorkflowStepStore = defineStore("workflowStepStore", {
inputStep,
`Failed to add connection, because step with id ${connection.input.stepId} is undefined`
);
+ const input = inputStep.inputs.find((input) => input.name === connection.input.name);
+ const connectionLink: ConnectionOutputLink = {
+ output_name: connection.output.name,
+ id: connection.output.stepId,
+ };
+ if (input && "input_subworkflow_step_id" in input && input.input_subworkflow_step_id !== undefined) {
+ connectionLink["input_subworkflow_step_id"] = input.input_subworkflow_step_id;
+ }
const updatedStep = {
...inputStep,
input_connections: {
...inputStep.input_connections,
- [connection.input.name]: { output_name: connection.output.name, id: connection.output.stepId },
+ [connection.input.name]: connectionLink,
},
};
this.updateStep(updatedStep);
@@ -224,7 +277,10 @@ export const useWorkflowStepStore = defineStore("workflowStepStore", {
export function stepToConnections(step: Step): Connection[] {
const connections: Connection[] = [];
if (step.input_connections) {
- Object.entries(step?.input_connections).forEach(([input_name, outputArray]) => {
+ Object.entries(step?.input_connections).forEach(([inputName, outputArray]) => {
+ if (outputArray === undefined) {
+ return;
+ }
if (!Array.isArray(outputArray)) {
outputArray = [outputArray];
}
@@ -232,7 +288,7 @@ export function stepToConnections(step: Step): Connection[] {
const connection = new Connection(
{
stepId: step.id,
- name: input_name,
+ name: inputName,
connectorType: "input",
},
{
@@ -241,6 +297,10 @@ export function stepToConnections(step: Step): Connection[] {
connectorType: "output",
}
);
+ const connectionInput = step.inputs.find((input) => input.name == inputName);
+ if (connectionInput && "input_subworkflow_step_id" in connectionInput) {
+ connection.input.input_subworkflow_step_id = connectionInput.input_subworkflow_step_id;
+ }
connections.push(connection);
});
});
diff --git a/client/src/stores/workflowStore.js b/client/src/stores/workflowStore.ts
similarity index 65%
rename from client/src/stores/workflowStore.js
rename to client/src/stores/workflowStore.ts
index 5ba6400e1fe..5cf2b8b8d9d 100644
--- a/client/src/stores/workflowStore.js
+++ b/client/src/stores/workflowStore.ts
@@ -1,20 +1,25 @@
import { defineStore } from "pinia";
import axios from "axios";
+import type { Steps } from "@/stores/workflowStepStore";
+import { getAppRoot } from "@/onload/loadConfig";
-import { getAppRoot } from "onload/loadConfig";
+interface Workflow {
+ [index: string]: any;
+ steps: Steps;
+}
export const useWorkflowStore = defineStore("workflowStore", {
state: () => ({
- workflowsByInstanceId: {},
+ workflowsByInstanceId: {} as { [index: string]: Workflow },
}),
getters: {
getWorkflowByInstanceId: (state) => {
- return (workflowId) => {
- state.workflowsByInstanceId[workflowId];
+ return (workflowId: string) => {
+ return state.workflowsByInstanceId[workflowId];
};
},
getWorkflowNameByInstanceId: (state) => {
- return (workflowId) => {
+ return (workflowId: string) => {
const details = state.workflowsByInstanceId[workflowId];
if (details && details.name) {
return details.name;
@@ -24,19 +29,19 @@ export const useWorkflowStore = defineStore("workflowStore", {
};
},
getStoredWorkflowIdByInstanceId: (state) => {
- return (workflowId) => {
+ return (workflowId: string) => {
const storedWorkflow = state.workflowsByInstanceId[workflowId];
return storedWorkflow?.id;
};
},
},
actions: {
- async fetchWorkflowForInstanceId(workflowId) {
+ async fetchWorkflowForInstanceId(workflowId: string) {
console.debug("Fetching workflow details for", workflowId);
const params = { instance: "true" };
const { data } = await axios.get(`${getAppRoot()}api/workflows/${workflowId}`, { params });
this.$patch((state) => {
- state.workflowsByInstanceId[workflowId] = data;
+ state.workflowsByInstanceId[workflowId] = data as Workflow;
});
},
},
diff --git a/client/src/style/scss/ui.scss b/client/src/style/scss/ui.scss
index 9726e862dfa..8f2e316092d 100644
--- a/client/src/style/scss/ui.scss
+++ b/client/src/style/scss/ui.scss
@@ -102,37 +102,6 @@ $ui-margin-horizontal-large: $margin-v * 2;
cursor: pointer;
}
-// thumbnails
-.ui-thumbnails {
- .ui-thumbnails-item {
- cursor: pointer;
- .ui-thumbnails-image {
- padding: 1rem;
- width: 5rem;
- height: 4.3rem;
- }
- .ui-thumbnails-icon {
- padding: 1rem;
- width: 5rem;
- height: 4.3rem;
- font-size: 2em;
- text-align: center;
- color: $text-color;
- }
- .ui-thumbnails-text {
- word-wrap: break-word;
- }
- }
- .ui-thumbnails-item:hover {
- .ui-thumbnails-title {
- text-decoration: underline;
- }
- .ui-thumbnails-text {
- text-decoration: underline;
- }
- }
-}
-
.ui-form-composite {
height: 100%;
flex-direction: column;
diff --git a/client/src/style/scss/workflow.scss b/client/src/style/scss/workflow.scss
index eb94d9c3dfa..a3ca99cf01d 100644
--- a/client/src/style/scss/workflow.scss
+++ b/client/src/style/scss/workflow.scss
@@ -117,6 +117,9 @@
.ribbon-inner-invalid {
stroke: $brand-warning;
}
+ &.dashed {
+ stroke-dasharray: 5, 3;
+ }
&:hover .ribbon-outer {
stroke: $brand-success;
}
diff --git a/client/src/utils/mountVueComponent.js b/client/src/utils/mountVueComponent.js
index 02efe8e2e5b..c7b1f240f07 100644
--- a/client/src/utils/mountVueComponent.js
+++ b/client/src/utils/mountVueComponent.js
@@ -32,10 +32,9 @@ export const mountVueComponent = (ComponentDefinition) => {
return (propsData, el) => new component({ store, propsData, el });
};
-export const appendVueComponent = ($el, ComponentDefinition, propsData = {}) => {
- // TODO: this doesn't append, it wipes out the element and replaces contents?
+export const replaceChildrenWithComponent = (el, ComponentDefinition, propsData = {}) => {
const container = document.createElement("div");
- $el.replaceChildren(container);
+ el.replaceChildren(container);
const component = Vue.extend(ComponentDefinition);
const mountFn = (propsData, el) => new component({ propsData, el });
return mountFn(propsData, container);
diff --git a/client/src/utils/navigation/navigation.yml b/client/src/utils/navigation/navigation.yml
index 5b8a7a8120c..7114c497544 100644
--- a/client/src/utils/navigation/navigation.yml
+++ b/client/src/utils/navigation/navigation.yml
@@ -183,6 +183,7 @@ history_panel:
edit_button: '${_} .edit-btn'
delete_button: '${_} .delete-btn'
rerun_button: '${_} .rerun-btn'
+ visualize_button: '${_} .visualize-btn'
collection_job_details_button: '${_} .collection-job-details-btn'
# Action buttons...
@@ -523,6 +524,11 @@ workflows:
annotation: '#workflow_annotation'
submit: '#submit'
+visualization:
+ selectors:
+ _: '#center div.plugin-list'
+ plugin_item: '[data-plugin-name*="${id}"]'
+
trs_search:
selectors:
search: "#trs-search-query"
@@ -643,6 +649,7 @@ workflow_editor:
//div[@data-label='Remove Tags']//input
tool_version_button: ".tool-versions"
connector_for: "#connection-${sink_id}-${source_id}"
+ connector_invalid_for: "#connection-${sink_id}-${source_id} .ribbon-inner-invalid"
connector_destroy_callout: '.delete-terminal'
save_button: '.editor-button-save'
state_modal_body: '.state-upgrade-modal'
diff --git a/client/src/utils/wait.ts b/client/src/utils/wait.ts
new file mode 100644
index 00000000000..9c9930b5488
--- /dev/null
+++ b/client/src/utils/wait.ts
@@ -0,0 +1,5 @@
+export function wait(milliseconds: number) {
+ return new Promise((resolve) => {
+ setTimeout(() => resolve(), milliseconds);
+ });
+}
diff --git a/client/tests/jest/__mocks__/config.js b/client/tests/jest/__mocks__/config.js
index 0aaa35c1744..61763815697 100644
--- a/client/tests/jest/__mocks__/config.js
+++ b/client/tests/jest/__mocks__/config.js
@@ -5,6 +5,6 @@ export default {
caching: {
adapter: "memory",
revs_limit: 1,
- pageSize: 50
- }
-}
+ pageSize: 50,
+ },
+};
diff --git a/client/tests/jest/__mocks__/trackster.js b/client/tests/jest/__mocks__/trackster.js
index 34f0f96bfab..ce75d703b2b 100644
--- a/client/tests/jest/__mocks__/trackster.js
+++ b/client/tests/jest/__mocks__/trackster.js
@@ -8,4 +8,4 @@ export class TracksterUI extends Backbone.Model {
initialize(baseURL) {
this.baseURL = baseURL;
}
-}
\ No newline at end of file
+}
diff --git a/client/tests/jest/jest-raw-loader.js b/client/tests/jest/jest-raw-loader.js
index 3ef7c744106..087224e0497 100644
--- a/client/tests/jest/jest-raw-loader.js
+++ b/client/tests/jest/jest-raw-loader.js
@@ -1,3 +1,5 @@
module.exports = {
- process: content => { return { code: "module.exports = " + JSON.stringify(content)}}
+ process: (content) => {
+ return { code: "module.exports = " + JSON.stringify(content) };
+ },
};
diff --git a/client/tests/jest/jest.setup.js b/client/tests/jest/jest.setup.js
index 474a5845a15..3dda52917b4 100644
--- a/client/tests/jest/jest.setup.js
+++ b/client/tests/jest/jest.setup.js
@@ -1,5 +1,5 @@
import "@testing-library/jest-dom";
-import Vue from 'vue';
+import Vue from "vue";
// Set Vue to suppress production / devtools / etc. warnings
Vue.config.productionTip = false;
diff --git a/client/tests/jest/yaml-jest.js b/client/tests/jest/yaml-jest.js
index 8e84063fe5b..2034043ff0e 100644
--- a/client/tests/jest/yaml-jest.js
+++ b/client/tests/jest/yaml-jest.js
@@ -1,13 +1,13 @@
-const transformer = require("yaml-jest").default
+const transformer = require("yaml-jest").default;
const newTransformer = {
- ...transformer,
- process: function (...params) {
- return {
- code: transformer?.process(...params),
- map: null,
- }
- },
-}
+ ...transformer,
+ process: function (...params) {
+ return {
+ code: transformer?.process(...params),
+ map: null,
+ };
+ },
+};
-module.exports = newTransformer
+module.exports = newTransformer;
diff --git a/client/tests/qunit/test-data/paired-collection-creator.data.js b/client/tests/qunit/test-data/paired-collection-creator.data.js
index a6c3c954568..1e2638c8a75 100644
--- a/client/tests/qunit/test-data/paired-collection-creator.data.js
+++ b/client/tests/qunit/test-data/paired-collection-creator.data.js
@@ -19,23 +19,23 @@ var datasets1 = [
];
var datasets2 = [
- { name: "DP134_1_FS_PSII_FSB_42C_A10.1.fastq", state: STATES.OK},
- { name: "DP134_1_FS_PSII_FSB_42C_A10.2.fastq", state: STATES.OK}
-]
+ { name: "DP134_1_FS_PSII_FSB_42C_A10.1.fastq", state: STATES.OK },
+ { name: "DP134_1_FS_PSII_FSB_42C_A10.2.fastq", state: STATES.OK },
+];
var datasets3 = [
- {name: "UII_moo_1.1.fastq", state: STATES.OK},
- {name: "UII_moo_1.2.fastq", state: STATES.OK}
-]
+ { name: "UII_moo_1.1.fastq", state: STATES.OK },
+ { name: "UII_moo_1.2.fastq", state: STATES.OK },
+];
-var datasets4= [
+var datasets4 = [
{ name: "SET1-01_R1.fastq", state: STATES.OK },
{ name: "SET1-01_R2.fastq", state: STATES.OK },
{ name: "SET1-02_R1.fastq", state: STATES.OK },
{ name: "SET1-02_R2.fastq", state: STATES.OK },
{ name: "SET1-03_R1.fastq", state: STATES.OK },
{ name: "SET1-03_R2.fastq", state: STATES.OK },
-]
+];
var datasets1CreateRequestJSON = {
type: "dataset_collection",
@@ -171,5 +171,5 @@ export default {
_1requestJSON: datasets1CreateRequestJSON,
_2: datasets2,
_3: datasets3,
- _4: datasets4
+ _4: datasets4,
};
diff --git a/client/types/globals.d.ts b/client/types/globals.d.ts
index abf750be2e1..97ec1d6ccf9 100644
--- a/client/types/globals.d.ts
+++ b/client/types/globals.d.ts
@@ -1,4 +1,4 @@
// Webpack-injected globals.
-declare const __webpack_public_path__:string;
+declare const __webpack_public_path__: string;
declare const __buildTimestamp__: string;
-declare const __license__: string;
\ No newline at end of file
+declare const __license__: string;
diff --git a/client/types/shim-vue.d.ts b/client/types/shim-vue.d.ts
index 0660bd67a54..d6e6b864e50 100644
--- a/client/types/shim-vue.d.ts
+++ b/client/types/shim-vue.d.ts
@@ -1,4 +1,4 @@
declare module "*.vue" {
- import Vue from "vue";
- export default Vue;
+ import Vue from "vue";
+ export default Vue;
}
diff --git a/client/types/types.d.ts b/client/types/types.d.ts
index efe9231ef21..423222e511c 100644
--- a/client/types/types.d.ts
+++ b/client/types/types.d.ts
@@ -1,4 +1,4 @@
-declare module '*.yml' {
- const data: any
- export default data
+declare module "*.yml" {
+ const data: any;
+ export default data;
}
diff --git a/client/webpack.config.js b/client/webpack.config.js
index 9a892104993..f9493e6ec7c 100644
--- a/client/webpack.config.js
+++ b/client/webpack.config.js
@@ -40,8 +40,9 @@ module.exports = (env = {}, argv = {}) => {
toolshed: ["polyfills", "bundleToolshed", "entry/generic"],
},
output: {
- path: path.join(__dirname, "../", "/static/dist"),
+ path: path.join(__dirname, "dist"),
filename: "[name].bundled.js",
+ clean: true,
},
resolve: {
plugins: [new TsconfigPathsPlugin({ extensions: [".ts", ".js", ".json", ".vue", ".scss"] })],
@@ -249,16 +250,20 @@ module.exports = (env = {}, argv = {}) => {
allowedHosts: process.env.GITPOD_WORKSPACE_ID ? "all" : "auto",
devMiddleware: {
publicPath: "/static/dist",
+ writeToDisk: true,
},
hot: true,
- port: 8081,
+ port: process.env.WEBPACK_PORT || 8081,
host: "0.0.0.0",
// proxy *everything* to the galaxy server.
// someday, when we have a fully API-driven independent client, this
// can be a more limited set -- e.g. `/api`, `/auth`
proxy: {
"**": {
- target: process.env.GALAXY_URL || "http://localhost:8080",
+ // We explicitly use ipv4 loopback instead of localhost to
+ // avoid ipv6/ipv4 resolution order issues; this should
+ // align with Galaxy's default.
+ target: process.env.GALAXY_URL || "http://127.0.0.1:8080",
secure: process.env.CHANGE_ORIGIN ? !process.env.CHANGE_ORIGIN : true,
changeOrigin: !!process.env.CHANGE_ORIGIN,
logLevel: "debug",
diff --git a/client/yarn.lock b/client/yarn.lock
index ecccf5a872b..d7f18e64d3a 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -2295,11 +2295,21 @@
resolved "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz"
integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==
+"@types/minimist@^1.2.2":
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c"
+ integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==
+
"@types/node@*", "@types/node@>=10.0.0":
version "18.6.5"
resolved "https://registry.npmjs.org/@types/node/-/node-18.6.5.tgz"
integrity sha512-Xjt5ZGUa5WusGZJ4WJPbOT8QOqp6nDynVFRKcUt32bOgvXEoc6o085WNkYTMO7ifAj2isEfQQ2cseE+wT6jsRw==
+"@types/normalize-package-data@^2.4.0":
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
+ integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==
+
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz"
@@ -2788,6 +2798,14 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0"
indent-string "^4.0.0"
+aggregate-error@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-4.0.1.tgz#25091fe1573b9e0be892aeda15c7c66a545f758e"
+ integrity sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==
+ dependencies:
+ clean-stack "^4.0.0"
+ indent-string "^5.0.0"
+
ajv-formats@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz"
@@ -3035,6 +3053,16 @@ array-unique@^0.3.2:
resolved "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz"
integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==
+arrify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
+ integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==
+
+arrify@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/arrify/-/arrify-3.0.0.tgz#ccdefb8eaf2a1d2ab0da1ca2ce53118759fd46bc"
+ integrity sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==
+
assert@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz"
@@ -3506,6 +3534,16 @@ callsites@^3.0.0:
resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+camelcase-keys@^7.0.0:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-7.0.2.tgz#d048d8c69448745bb0de6fc4c1c52a30dfbe7252"
+ integrity sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==
+ dependencies:
+ camelcase "^6.3.0"
+ map-obj "^4.1.0"
+ quick-lru "^5.1.1"
+ type-fest "^1.2.1"
+
camelcase@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz"
@@ -3516,7 +3554,7 @@ camelcase@^5.3.1:
resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
-camelcase@^6.2.0:
+camelcase@^6.2.0, camelcase@^6.3.0:
version "6.3.0"
resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
@@ -3662,6 +3700,13 @@ clean-stack@^2.0.0:
resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz"
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
+clean-stack@^4.0.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-4.2.0.tgz#c464e4cde4ac789f4e0735c5d75beb49d7b30b31"
+ integrity sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==
+ dependencies:
+ escape-string-regexp "5.0.0"
+
cliui@^3.2.0:
version "3.2.0"
resolved "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz"
@@ -4012,6 +4057,38 @@ cosmiconfig@^7.0.0:
path-type "^4.0.0"
yaml "^1.10.0"
+cp-file@^9.1.0:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/cp-file/-/cp-file-9.1.0.tgz#e98e30db72d57d47b5b1d444deb70d05e5684921"
+ integrity sha512-3scnzFj/94eb7y4wyXRWwvzLFaQp87yyfTnChIjlfYrVqp5lVO3E2hIJMeQIltUT0K2ZAB3An1qXcBmwGyvuwA==
+ dependencies:
+ graceful-fs "^4.1.2"
+ make-dir "^3.0.0"
+ nested-error-stacks "^2.0.0"
+ p-event "^4.1.0"
+
+cpy-cli@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/cpy-cli/-/cpy-cli-4.2.0.tgz#d60bf9ac776486af20aac2fa4fb95f8f10b35ce8"
+ integrity sha512-b04b+cbdr29CdpREPKw/itrfjO43Ty0Aj7wRM6M6LoE4GJxZJCk9Xp+Eu1IqztkKh3LxIBt1tDplENsa6KYprg==
+ dependencies:
+ cpy "^9.0.0"
+ meow "^10.1.2"
+
+cpy@^9.0.0:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/cpy/-/cpy-9.0.1.tgz#7f3ad0ad5bafe0bc70645c4bb567969927cadb9f"
+ integrity sha512-D9U0DR5FjTCN3oMTcFGktanHnAG5l020yvOCR1zKILmAyPP7I/9pl6NFgRbDcmSENtbK1sQLBz1p9HIOlroiNg==
+ dependencies:
+ arrify "^3.0.0"
+ cp-file "^9.1.0"
+ globby "^13.1.1"
+ junk "^4.0.0"
+ micromatch "^4.0.4"
+ nested-error-stacks "^2.1.0"
+ p-filter "^3.0.0"
+ p-map "^5.3.0"
+
cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"
@@ -4489,11 +4566,24 @@ debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, d
dependencies:
ms "2.1.2"
-decamelize@^1.1.1:
+decamelize-keys@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8"
+ integrity sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==
+ dependencies:
+ decamelize "^1.1.0"
+ map-obj "^1.0.0"
+
+decamelize@^1.1.0, decamelize@^1.1.1:
version "1.2.0"
resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz"
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
+decamelize@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-5.0.1.tgz#db11a92e58c741ef339fb0a2868d8a06a9a7b1e9"
+ integrity sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==
+
decimal.js@^10.4.2:
version "10.4.3"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
@@ -5052,6 +5142,11 @@ escape-html@~1.0.3:
resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz"
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
+escape-string-regexp@5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
+ integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
+
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
@@ -5409,7 +5504,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
-fast-glob@^3.2.12:
+fast-glob@^3.2.11, fast-glob@^3.2.12:
version "3.2.12"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
@@ -5926,6 +6021,17 @@ globby@^11.0.1, globby@^11.1.0:
merge2 "^1.4.1"
slash "^3.0.0"
+globby@^13.1.1:
+ version "13.1.3"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.3.tgz#f62baf5720bcb2c1330c8d4ef222ee12318563ff"
+ integrity sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==
+ dependencies:
+ dir-glob "^3.0.1"
+ fast-glob "^3.2.11"
+ ignore "^5.2.0"
+ merge2 "^1.4.1"
+ slash "^4.0.0"
+
globrex@^0.1.2:
version "0.1.2"
resolved "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz"
@@ -6010,6 +6116,11 @@ handsontable@^4.0.0:
numbro "^2.0.6"
pikaday "1.5.1"
+hard-rejection@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883"
+ integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==
+
has-bigints@^1.0.1, has-bigints@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz"
@@ -6104,6 +6215,13 @@ hosted-git-info@^2.1.4:
resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz"
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
+hosted-git-info@^4.0.1:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224"
+ integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==
+ dependencies:
+ lru-cache "^6.0.0"
+
hpack.js@^2.1.6:
version "2.1.6"
resolved "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz"
@@ -6300,6 +6418,11 @@ indent-string@^4.0.0:
resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
+indent-string@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5"
+ integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==
+
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz"
@@ -6434,6 +6557,13 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.4:
resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz"
integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==
+is-core-module@^2.5.0:
+ version "2.11.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144"
+ integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==
+ dependencies:
+ has "^1.0.3"
+
is-core-module@^2.9.0:
version "2.10.0"
resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz"
@@ -6597,6 +6727,11 @@ is-path-inside@^3.0.2, is-path-inside@^3.0.3:
resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz"
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+is-plain-obj@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
+ integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==
+
is-plain-obj@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz"
@@ -7425,6 +7560,11 @@ jspdf@^2.5.1:
dompurify "^2.2.0"
html2canvas "^1.0.0-rc.5"
+junk@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/junk/-/junk-4.0.0.tgz#9b1104ddf5281cd24ffa3c8a7443d19ce192b37f"
+ integrity sha512-ojtSU++zLJ3jQG9bAYjg94w+/DOJtRyD7nPaerMFrBhmdVmiV5/exYH5t4uHga4G/95nT6hr1OJoKIFbYbrW5w==
+
just-debounce@^1.0.0:
version "1.1.0"
resolved "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz"
@@ -7510,7 +7650,7 @@ kind-of@^5.0.0, kind-of@^5.0.2:
resolved "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz"
integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==
-kind-of@^6.0.0, kind-of@^6.0.2:
+kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3:
version "6.0.3"
resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
@@ -7832,6 +7972,16 @@ map-cache@^0.2.0, map-cache@^0.2.2:
resolved "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz"
integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==
+map-obj@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
+ integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==
+
+map-obj@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a"
+ integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==
+
map-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz"
@@ -7892,6 +8042,24 @@ memfs@^3.4.3:
dependencies:
fs-monkey "^1.0.3"
+meow@^10.1.2:
+ version "10.1.5"
+ resolved "https://registry.yarnpkg.com/meow/-/meow-10.1.5.tgz#be52a1d87b5f5698602b0f32875ee5940904aa7f"
+ integrity sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==
+ dependencies:
+ "@types/minimist" "^1.2.2"
+ camelcase-keys "^7.0.0"
+ decamelize "^5.0.0"
+ decamelize-keys "^1.1.0"
+ hard-rejection "^2.1.0"
+ minimist-options "4.1.0"
+ normalize-package-data "^3.0.2"
+ read-pkg-up "^8.0.0"
+ redent "^4.0.0"
+ trim-newlines "^4.0.2"
+ type-fest "^1.2.2"
+ yargs-parser "^20.2.9"
+
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz"
@@ -7973,7 +8141,7 @@ mimic-fn@^2.1.0:
resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
-min-indent@^1.0.0:
+min-indent@^1.0.0, min-indent@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
@@ -8004,6 +8172,15 @@ minimatch@^5.0.1:
dependencies:
brace-expansion "^2.0.1"
+minimist-options@4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619"
+ integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==
+ dependencies:
+ arrify "^1.0.1"
+ is-plain-obj "^1.1.0"
+ kind-of "^6.0.3"
+
minimist@^1.2.0, minimist@^1.2.6:
version "1.2.6"
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz"
@@ -8109,6 +8286,11 @@ neo-async@^2.6.2:
resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
+nested-error-stacks@^2.0.0, nested-error-stacks@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz#26c8a3cee6cc05fbcf1e333cd2fc3e003326c0b5"
+ integrity sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==
+
next-tick@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz"
@@ -8177,6 +8359,16 @@ normalize-package-data@^2.3.2:
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
+normalize-package-data@^3.0.2:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e"
+ integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==
+ dependencies:
+ hosted-git-info "^4.0.1"
+ is-core-module "^2.5.0"
+ semver "^7.3.4"
+ validate-npm-package-license "^3.0.1"
+
normalize-path@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz"
@@ -8436,6 +8628,25 @@ os-locale@^1.4.0:
dependencies:
lcid "^1.0.0"
+p-event@^4.1.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5"
+ integrity sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==
+ dependencies:
+ p-timeout "^3.1.0"
+
+p-filter@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/p-filter/-/p-filter-3.0.0.tgz#ce50e03b24b23930e11679ab8694bd09a2d7ed35"
+ integrity sha512-QtoWLjXAW++uTX67HZQz1dbTpqBfiidsB6VtQUC9iR85S120+s0T5sO6s+B5MLzFcZkrEd/DGMmCjR+f2Qpxwg==
+ dependencies:
+ p-map "^5.1.0"
+
+p-finally@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
+ integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==
+
p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz"
@@ -8471,6 +8682,13 @@ p-map@^4.0.0:
dependencies:
aggregate-error "^3.0.0"
+p-map@^5.1.0, p-map@^5.3.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/p-map/-/p-map-5.5.0.tgz#054ca8ca778dfa4cf3f8db6638ccb5b937266715"
+ integrity sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==
+ dependencies:
+ aggregate-error "^4.0.0"
+
p-retry@^4.5.0:
version "4.6.2"
resolved "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz"
@@ -8479,6 +8697,13 @@ p-retry@^4.5.0:
"@types/retry" "0.12.0"
retry "^0.13.1"
+p-timeout@^3.1.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe"
+ integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==
+ dependencies:
+ p-finally "^1.0.0"
+
p-try@^2.0.0:
version "2.2.0"
resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz"
@@ -9137,6 +9362,11 @@ queue-microtask@^1.2.2:
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+quick-lru@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
+ integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
+
qunit@^2.17.2:
version "2.19.1"
resolved "https://registry.npmjs.org/qunit/-/qunit-2.19.1.tgz"
@@ -9201,6 +9431,15 @@ read-pkg-up@^1.0.1:
find-up "^1.0.0"
read-pkg "^1.0.0"
+read-pkg-up@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-8.0.0.tgz#72f595b65e66110f43b052dd9af4de6b10534670"
+ integrity sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==
+ dependencies:
+ find-up "^5.0.0"
+ read-pkg "^6.0.0"
+ type-fest "^1.0.1"
+
read-pkg@^1.0.0:
version "1.1.0"
resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz"
@@ -9210,6 +9449,16 @@ read-pkg@^1.0.0:
normalize-package-data "^2.3.2"
path-type "^1.0.0"
+read-pkg@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-6.0.0.tgz#a67a7d6a1c2b0c3cd6aa2ea521f40c458a4a504c"
+ integrity sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==
+ dependencies:
+ "@types/normalize-package-data" "^2.4.0"
+ normalize-package-data "^3.0.2"
+ parse-json "^5.2.0"
+ type-fest "^1.0.1"
+
readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
version "2.3.7"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz"
@@ -9261,6 +9510,14 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
+redent@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/redent/-/redent-4.0.0.tgz#0c0ba7caabb24257ab3bb7a4fd95dd1d5c5681f9"
+ integrity sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==
+ dependencies:
+ indent-string "^5.0.0"
+ strip-indent "^4.0.0"
+
regenerate-unicode-properties@^10.0.1:
version "10.0.1"
resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz"
@@ -9856,6 +10113,11 @@ slash@^3.0.0:
resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
+slash@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7"
+ integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
+
slugify@^1.6.5:
version "1.6.5"
resolved "https://registry.npmjs.org/slugify/-/slugify-1.6.5.tgz"
@@ -10258,6 +10520,13 @@ strip-indent@^3.0.0:
dependencies:
min-indent "^1.0.0"
+strip-indent@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-4.0.0.tgz#b41379433dd06f5eae805e21d631e07ee670d853"
+ integrity sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==
+ dependencies:
+ min-indent "^1.0.1"
+
strip-json-comments@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
@@ -10537,6 +10806,11 @@ tr46@~0.0.3:
resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
+trim-newlines@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-4.0.2.tgz#d6aaaf6a0df1b4b536d183879a6b939489808c7c"
+ integrity sha512-GJtWyq9InR/2HRiLZgpIKv+ufIKrVrvjQWEj7PxAXNc5dwbNJkqhAUoAGgzRmULAnoOM5EIpveYd3J2VeSAIew==
+
ts-jest@^29.0.3:
version "29.0.3"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.0.3.tgz#63ea93c5401ab73595440733cefdba31fcf9cb77"
@@ -10648,6 +10922,11 @@ type-fest@^0.21.3:
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz"
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
+type-fest@^1.0.1, type-fest@^1.2.1, type-fest@^1.2.2:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1"
+ integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==
+
type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz"
@@ -10682,9 +10961,9 @@ typescript@^4.9.3:
integrity sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==
ua-parser-js@^0.7.30:
- version "0.7.31"
- resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz"
- integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==
+ version "0.7.33"
+ resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532"
+ integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
@@ -11499,7 +11778,7 @@ yaml@^2.0.0:
resolved "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz"
integrity sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==
-yargs-parser@^20.2.2:
+yargs-parser@^20.2.2, yargs-parser@^20.2.9:
version "20.2.9"
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
diff --git a/config/plugins/tours/core.windows.yaml b/config/plugins/tours/core.windows.yaml
index 226150dadf6..d2318ce3c5d 100644
--- a/config/plugins/tours/core.windows.yaml
+++ b/config/plugins/tours/core.windows.yaml
@@ -6,15 +6,14 @@ tags:
- "UI"
- "visualization"
+requirements:
+ - new_history
+
steps:
- content: "This short tour will walk you through Galaxy's Window Manager feature"
- - element: "#current-history-panel .create-hist-btn"
- intro: "Before using the Window Manager, we will create a new history for this tour."
- postclick: true
-
- element: "#tool-panel-upload-button"
- intro: "Now, we will upload some tabular data into our new history."
+ intro: "First, we will upload some tabular data into our new history."
postclick: true
- element: "#btn-new"
@@ -78,7 +77,7 @@ steps:
- element: "#right"
intro: "This is your history. It contains our two tables being uploaded."
-
+
- component: history_panel.item(hid=1,state=ok).display_button
intro: "Clicking the eye-icon usually displays a dataset in the center panel."
postclick: true
diff --git a/config/plugins/visualizations/annotate_image/src/script.js b/config/plugins/visualizations/annotate_image/src/script.js
index e8167257e4f..d97fe758420 100644
--- a/config/plugins/visualizations/annotate_image/src/script.js
+++ b/config/plugins/visualizations/annotate_image/src/script.js
@@ -5,6 +5,12 @@ import _ from "underscore";
// Use lighter weight 'core' version of paper since we don't need paperscript
import paper from "../node_modules/paper/dist/paper-core.js";
+/* This will be part of the charts/viz standard lib in 23.1 */
+const slashCleanup = /(\/)+/g;
+function prefixedDownloadUrl(root, path) {
+ return `${root}/${path}`.replace(slashCleanup, "/");
+}
+
const CommandManager = (function() {
function CommandManager() {}
@@ -437,12 +443,12 @@ window.bundleEntries.load = function (opt) {
});
};
- const safe_download_url = `${options.root}${dataset.download_url}`;
+ const downloadUrl = prefixedDownloadUrl(opt.root, dataset.download_url);
$.ajax({
- url: safe_download_url,
+ url: downloadUrl,
success: function(content) {
const $chartViewer = $("#" + opt.target);
- $chartViewer.html("
");
+ $chartViewer.html("
");
$chartViewer.css("overflow", "auto");
$chartViewer.css("position", "relative");
const $image = $chartViewer.find("img");
diff --git a/config/plugins/visualizations/cytoscape/src/script.js b/config/plugins/visualizations/cytoscape/src/script.js
index fbcb9510c7d..a2d0fdd2d01 100644
--- a/config/plugins/visualizations/cytoscape/src/script.js
+++ b/config/plugins/visualizations/cytoscape/src/script.js
@@ -1,5 +1,11 @@
import Cytoscape from 'cytoscape';
+/* This will be part of the charts/viz standard lib in 23.1 */
+const slashCleanup = /(\/)+/g;
+function prefixedDownloadUrl(root, path) {
+ return `${root}/${path}`.replace(slashCleanup, "/");
+}
+
// Public method. Return graph data as JSON
var parse_sif = function( text ) {
// Private variables and methods
@@ -166,9 +172,8 @@ window.bundleEntries.load = function (options) {
cytoscape = null,
sif_file_ext = "sif",
highlighted_color = settings.get( 'color_picker_highlighted' );
- const safe_download_url = `${options.root}${dataset.download_url}`;
$.ajax({
- url : safe_download_url,
+ url : prefixedDownloadUrl(options.root, dataset.download_url),
success : function( content ) {
// Select data for the graph
if( dataset.file_ext === sif_file_ext ) {
diff --git a/config/plugins/visualizations/drawrna/src/script.js b/config/plugins/visualizations/drawrna/src/script.js
index 2b0ac8faf20..ce081c07266 100644
--- a/config/plugins/visualizations/drawrna/src/script.js
+++ b/config/plugins/visualizations/drawrna/src/script.js
@@ -1,11 +1,17 @@
import DrawRNA from "./drawrnajs/drawrna";
window.bundleEntries = window.bundleEntries || {};
+
+/* This will be part of the charts/viz standard lib in 23.1 */
+const slashCleanup = /(\/)+/g;
+function prefixedDownloadUrl(root, path) {
+ return `${root}/${path}`.replace(slashCleanup, "/");
+}
+
window.bundleEntries.load = function (options) {
var chart = options.chart;
var dataset = options.dataset;
- const safe_download_url = `${options.root}${dataset.download_url}`;
$.ajax({
- url: safe_download_url,
+ url: prefixedDownloadUrl(options.root, dataset.download_url),
success: function(response) {
var input = response.split('\n');
var app = new DrawRNA({
diff --git a/config/plugins/visualizations/msa/src/script.js b/config/plugins/visualizations/msa/src/script.js
index a89020f424b..72dabe78577 100755
--- a/config/plugins/visualizations/msa/src/script.js
+++ b/config/plugins/visualizations/msa/src/script.js
@@ -1,5 +1,11 @@
import "./msa.min.js";
+/* This will be part of the charts/viz standard lib in 23.1 */
+const slashCleanup = /(\/)+/g;
+function prefixedDownloadUrl(root, path) {
+ return `${root}/${path}`.replace(slashCleanup, "/");
+}
+
Object.assign(window.bundleEntries || {}, {
load: function (options) {
const chart = options.chart;
@@ -14,8 +20,7 @@ Object.assign(window.bundleEntries || {}, {
menu: "small",
bootstrapMenu: "true" == settings.get("menu"),
});
- const safe_download_url = `${options.root}${dataset.download_url}`;
- msaViz.u.file.importURL(safe_download_url, () => {
+ msaViz.u.file.importURL(prefixedDownloadUrl(options.root, dataset.download_url), () => {
msaViz.render();
chart.state("ok", "Chart drawn.");
options.process.resolve();
diff --git a/config/plugins/visualizations/ngl/src/ngl.js b/config/plugins/visualizations/ngl/src/ngl.js
index b9d92f73bec..5d9ee9cfd6f 100644
--- a/config/plugins/visualizations/ngl/src/ngl.js
+++ b/config/plugins/visualizations/ngl/src/ngl.js
@@ -1,31 +1,37 @@
import * as ngl from "./viewer";
/** Get boolean as string */
-function asBoolean (value) {
+function asBoolean(value) {
return String(value).toLowerCase() == "true";
}
+/* This will be part of the charts/viz standard lib in 23.1 */
+const slashCleanup = /(\/)+/g;
+function prefixedDownloadUrl(root, path) {
+ return `${root}/${path}`.replace(slashCleanup, "/");
+}
+
window.bundleEntries = window.bundleEntries || {};
window.bundleEntries.load = function (options) {
- var dataset = options.dataset,
- settings = options.chart.settings,
- stage = new ngl.Stage(options.target, { backgroundColor: settings.get("backcolor") }),
- representation_parameters = {},
- stage_parameters = {};
- representation_parameters = {
+ const dataset = options.dataset;
+ const settings = options.chart.settings;
+ const stage = new ngl.Stage(options.target, { backgroundColor: settings.get("backcolor") });
+ const representationParameters = {
radius: settings.get("radius"),
assembly: settings.get("assembly"),
color: settings.get("colorscheme"),
opacity: settings.get("opacity"),
};
- stage_parameters = { ext: dataset.extension, defaultRepresentation: true };
- const safe_download_url = `${options.root}${dataset.download_url}`;
+ const stageParameters = { ext: dataset.extension, defaultRepresentation: true };
+
try {
- stage.loadFile(safe_download_url, stage_parameters).then(function (component) {
- component.addRepresentation(settings.get("mode"), representation_parameters);
- options.chart.state("ok", "Chart drawn.");
- options.process.resolve();
- });
+ stage
+ .loadFile(prefixedDownloadUrl(options.root, dataset.download_url), stageParameters)
+ .then(function (component) {
+ component.addRepresentation(settings.get("mode"), representationParameters);
+ options.chart.state("ok", "Chart drawn.");
+ options.process.resolve();
+ });
} catch (e) {
options.chart.state("failed", "Could not load PDB file.");
options.process.resolve();
diff --git a/config/plugins/visualizations/openlayers/src/script.js b/config/plugins/visualizations/openlayers/src/script.js
index b4ca4af179f..90922403fa7 100644
--- a/config/plugins/visualizations/openlayers/src/script.js
+++ b/config/plugins/visualizations/openlayers/src/script.js
@@ -13,6 +13,12 @@ import { saveAs } from "file-saver";
import shp from "shpjs";
import axios from "axios";
+/* This will be part of the charts/viz standard lib in 23.1 */
+const slashCleanup = /(\/)+/g;
+function prefixedDownloadUrl(root, path) {
+ return `${root}/${path}`.replace(slashCleanup, "/");
+}
+
var MapViewer = (function(mv) {
mv.gMap = null;
@@ -240,11 +246,11 @@ window.bundleEntries = window.bundleEntries || {};
window.bundleEntries.load = function (options) {
const chart = options.chart;
const dataset = options.dataset;
- const safe_download_url = `${options.root}${dataset.download_url}`;
+ const downloadUrl = prefixedDownloadUrl(options.root, dataset.download_url);
$.ajax({
- url: safe_download_url,
+ url: downloadUrl,
success: () => {
- MapViewer.loadFile(safe_download_url, dataset.extension, options, chart);
+ MapViewer.loadFile(downloadUrl, dataset.extension, options, chart);
},
error: () => {
chart.state("failed", "Failed to access dataset.");
diff --git a/config/plugins/visualizations/phylocanvas/src/script.js b/config/plugins/visualizations/phylocanvas/src/script.js
index d437bfe6942..f2b3c6a60e3 100644
--- a/config/plugins/visualizations/phylocanvas/src/script.js
+++ b/config/plugins/visualizations/phylocanvas/src/script.js
@@ -1,12 +1,18 @@
import * as Phylocanvas from "phylocanvas";
+
+/* This will be part of the charts/viz standard lib in 23.1 */
+const slashCleanup = /(\/)+/g;
+function prefixedDownloadUrl(root, path) {
+ return `${root}/${path}`.replace(slashCleanup, "/");
+}
+
_.extend(window.bundleEntries || {}, {
load: function(options) {
var chart = options.chart;
var dataset = options.dataset;
var settings = options.chart.settings;
- const safe_download_url = `${options.root}${dataset.download_url}`;
$.ajax( {
- url : safe_download_url,
+ url : prefixedDownloadUrl(options.root, dataset.download_url),
success : function( content ) {
try {
var tree = Phylocanvas.default.createTree( options.target ),
diff --git a/config/plugins/visualizations/pv/src/pv.js b/config/plugins/visualizations/pv/src/pv.js
index 14a9bdf7db3..6dc41a292a5 100644
--- a/config/plugins/visualizations/pv/src/pv.js
+++ b/config/plugins/visualizations/pv/src/pv.js
@@ -1,5 +1,11 @@
import * as pv from "bio-pv";
+/* This will be part of the charts/viz standard lib in 23.1 */
+const slashCleanup = /(\/)+/g;
+function prefixedDownloadUrl(root, path) {
+ return `${root}/${path}`.replace(slashCleanup, "/");
+}
+
window.bundleEntries = window.bundleEntries || {};
window.bundleEntries.load = function (options) {
var settings = options.chart.settings;
@@ -10,9 +16,8 @@ window.bundleEntries.load = function (options) {
antialias: true,
outline: true,
});
- const safe_download_url = `${options.root}${dataset.download_url}`;
var xhr = new XMLHttpRequest();
- xhr.open("GET", safe_download_url);
+ xhr.open("GET", prefixedDownloadUrl(options.root, options.dataset.download_url));
xhr.onload = function () {
if (xhr.status === 200) {
var structure = pv.io.pdb(xhr.response);
diff --git a/doc/source/admin/galaxy_options.rst b/doc/source/admin/galaxy_options.rst
index 7253a266130..f36e3162429 100644
--- a/doc/source/admin/galaxy_options.rst
+++ b/doc/source/admin/galaxy_options.rst
@@ -5077,3 +5077,13 @@
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+``enable_beacon_integration``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description:
+ Enables user preferences and api endpoint for the beacon integration.
+:Default: ``false``
+:Type: bool
+
+
diff --git a/doc/source/dev/run_tests_help.txt b/doc/source/dev/run_tests_help.txt
index 4e002913339..5e646326027 100644
--- a/doc/source/dev/run_tests_help.txt
+++ b/doc/source/dev/run_tests_help.txt
@@ -75,8 +75,8 @@ Run a specific selenium test (under Linux or Mac OS X after installing geckodriv
Run a selenium test against a running server while watching client (fastest iterating on client tests):
./run.sh & # run Galaxy on 8080
- make client-watch & # watch for client changes
- export GALAXY_TEST_EXTERNAL=http://localhost:8080/ # Target tests at server.
+ make client-dev-server & # watch for client changes
+ export GALAXY_TEST_EXTERNAL=http://localhost:8081/ # Target tests at server.
. .venv/bin/activate # source the virtualenv so can skip run_tests.sh.
pytest lib/galaxy_test/selenium/test_workflow_editor.py::TestWorkflowEditor::test_data_input
diff --git a/lib/galaxy/config/sample/datatypes_conf.xml.sample b/lib/galaxy/config/sample/datatypes_conf.xml.sample
index 2efd24f4af4..49aab75ddbb 100644
--- a/lib/galaxy/config/sample/datatypes_conf.xml.sample
+++ b/lib/galaxy/config/sample/datatypes_conf.xml.sample
@@ -418,7 +418,6 @@
-
@@ -445,6 +444,7 @@
+
@@ -946,6 +946,10 @@
+
+
+
+
+
+
+
+
+
+
+
+
- pysam
- coreutils
+ coreutils
+ htslib
'$output1'
]]>
-
+
@@ -29,27 +40,55 @@
-
+
+
+
+
+
+
+
-
+
+
+
+
+
-
+
+
+
+
-
+
+
+
+
@@ -58,6 +97,10 @@
+
+
+
+
diff --git a/lib/galaxy/datatypes/converters/interval_to_tabix_converter.py b/lib/galaxy/datatypes/converters/interval_to_tabix_converter.py
deleted file mode 100644
index 29e66b518a0..00000000000
--- a/lib/galaxy/datatypes/converters/interval_to_tabix_converter.py
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env python
-
-"""
-Uses pysam to index a bgzipped interval file with tabix
-Supported presets: bed, gff, vcf
-
-usage: %prog in_file out_file
-"""
-import optparse
-import os
-import sys
-
-import pysam
-
-
-def main():
- # Read options, args.
- parser = optparse.OptionParser()
- parser.add_option("-c", "--chr-col", type="int", dest="chrom_col")
- parser.add_option("-s", "--start-col", type="int", dest="start_col")
- parser.add_option("-e", "--end-col", type="int", dest="end_col")
- parser.add_option("-P", "--preset", dest="preset")
- (options, args) = parser.parse_args()
- _, bgzip_fname, out_fname = args
- to_tabix(
- bgzip_fname=bgzip_fname,
- out_fname=out_fname,
- preset=options.preset,
- chrom_col=options.chrom_col,
- start_col=options.start_col,
- end_col=options.end_col,
- )
-
-
-def to_tabix(bgzip_fname, out_fname, preset=None, chrom_col=None, start_col=None, end_col=None):
- # Create index.
- if preset:
- # Preset type.
- bgzip_fname = pysam.tabix_index(
- filename=bgzip_fname, preset=preset, keep_original=True, index=out_fname, force=True
- )
- else:
- # For interval files; column indices are 0-based.
- bgzip_fname = pysam.tabix_index(
- filename=bgzip_fname,
- seq_col=(chrom_col - 1),
- start_col=(start_col - 1),
- end_col=(end_col - 1),
- keep_original=True,
- index=out_fname,
- force=True,
- )
- if os.path.getsize(out_fname) == 0:
- sys.exit("The converted tabix index file is empty, meaning the input data is invalid.")
- return bgzip_fname
-
-
-if __name__ == "__main__":
- main()
diff --git a/lib/galaxy/datatypes/converters/interval_to_tabix_converter.xml b/lib/galaxy/datatypes/converters/interval_to_tabix_converter.xml
index 54dbd1ad28e..2f94c32737c 100644
--- a/lib/galaxy/datatypes/converters/interval_to_tabix_converter.xml
+++ b/lib/galaxy/datatypes/converters/interval_to_tabix_converter.xml
@@ -1,18 +1,31 @@
-
+
- pysam
+ htslib
&2 echo "The converted tabix index file is empty, meaning the input data is invalid.";
+ exit 1;
+ fi
+ &&
+ tabix -l input.bgz
]]>
@@ -23,12 +36,22 @@
-
+
-
+
+
+
+
+
+
+
@@ -37,19 +60,66 @@
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/galaxy/datatypes/converters/tabular_to_dbnsfp.py b/lib/galaxy/datatypes/converters/tabular_to_dbnsfp.py
deleted file mode 100644
index a8c7c06ee5a..00000000000
--- a/lib/galaxy/datatypes/converters/tabular_to_dbnsfp.py
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/env python
-"""
-Uses pysam to bgzip a file
-
-usage: %prog in_file out_file
-"""
-
-import optparse
-import os.path
-
-import pysam
-
-
-def main():
- # Read options, args.
- usage = "Usage: %prog [options] tabular_input_file bgzip_output_file"
- parser = optparse.OptionParser(usage=usage)
- parser.add_option("-c", "--chr-col", type="int", default=0, dest="chrom_col")
- parser.add_option("-s", "--start-col", type="int", default=1, dest="start_col")
- parser.add_option("-e", "--end-col", type="int", default=1, dest="end_col")
- (options, args) = parser.parse_args()
- if len(args) != 2:
- parser.print_usage()
- exit(1)
- input_fname, output_fname = args
- output_dir = os.path.dirname(output_fname)
- if not os.path.exists(output_dir):
- os.makedirs(output_dir)
- pysam.tabix_compress(input_fname, output_fname, force=True)
- # Column indices are 0-based.
- pysam.tabix_index(output_fname, seq_col=options.chrom_col, start_col=options.start_col, end_col=options.end_col)
-
-
-if __name__ == "__main__":
- main()
diff --git a/lib/galaxy/datatypes/converters/tabular_to_dbnsfp.xml b/lib/galaxy/datatypes/converters/tabular_to_dbnsfp.xml
index a0b7c255736..7727d107dc7 100644
--- a/lib/galaxy/datatypes/converters/tabular_to_dbnsfp.xml
+++ b/lib/galaxy/datatypes/converters/tabular_to_dbnsfp.xml
@@ -1,9 +1,13 @@
-
+
- pysam
+ htslib
- python '$__tool_directory__/tabular_to_dbnsfp.py' '$input' '$dbnsfp.extra_files_path/dbNSFP.gz'
+ '$dbnsfp.extra_files_path/dbNSFP.gz' &&
+ tabix -s 1 -b 2 -e 2 '$dbnsfp.extra_files_path/dbNSFP.gz'
+ ]]>
diff --git a/lib/galaxy/datatypes/converters/vcf_bgzip_to_tabix_converter.xml b/lib/galaxy/datatypes/converters/vcf_bgzip_to_tabix_converter.xml
index 0d459aa011e..b6bdcd0c53a 100644
--- a/lib/galaxy/datatypes/converters/vcf_bgzip_to_tabix_converter.xml
+++ b/lib/galaxy/datatypes/converters/vcf_bgzip_to_tabix_converter.xml
@@ -1,9 +1,14 @@
-
+
- pysam
+ htslib
- python '$__tool_directory__/interval_to_tabix_converter.py' -P 'vcf' '' '$input1' '$output1'
+
@@ -13,7 +18,14 @@
-
+
+
+
+
diff --git a/lib/galaxy/datatypes/converters/vcf_to_vcf_bgzip.py b/lib/galaxy/datatypes/converters/vcf_to_vcf_bgzip.py
deleted file mode 100644
index 91ca2fe88bc..00000000000
--- a/lib/galaxy/datatypes/converters/vcf_to_vcf_bgzip.py
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/usr/bin/env python
-
-"""
-Uses pysam to bgzip a vcf file as-is.
-Headers, which are important, are kept.
-Original ordering, which may be specifically needed by tools or external display applications, is also maintained.
-
-usage: %prog in_file out_file
-"""
-import optparse
-
-import pysam
-
-
-def main():
- # Read options, args.
- parser = optparse.OptionParser()
- (options, args) = parser.parse_args()
- input_fname, output_fname = args
-
- pysam.tabix_compress(input_fname, output_fname, force=True)
-
-
-if __name__ == "__main__":
- main()
diff --git a/lib/galaxy/datatypes/converters/vcf_to_vcf_bgzip_converter.xml b/lib/galaxy/datatypes/converters/vcf_to_vcf_bgzip_converter.xml
index b680b973a48..41617707e2b 100644
--- a/lib/galaxy/datatypes/converters/vcf_to_vcf_bgzip_converter.xml
+++ b/lib/galaxy/datatypes/converters/vcf_to_vcf_bgzip_converter.xml
@@ -1,10 +1,11 @@
-
+
- pysam
- coreutils
+ htslib
- python '$__tool_directory__/vcf_to_vcf_bgzip.py' '$input1' '$output1'
+ '$output1'
+ ]]>
@@ -14,7 +15,11 @@
-
+
diff --git a/lib/galaxy/datatypes/data.py b/lib/galaxy/datatypes/data.py
index d8ff7f93e53..636e5c398bc 100644
--- a/lib/galaxy/datatypes/data.py
+++ b/lib/galaxy/datatypes/data.py
@@ -20,12 +20,21 @@ from typing import (
)
from markupsafe import escape
-from typing_extensions import (
- Literal,
- Protocol,
-)
+from typing_extensions import Literal
from galaxy import util
+from galaxy.datatypes._protocols import (
+ DatasetHasHidProtocol,
+ DatasetProtocol,
+ HasClearAssociatedFiles,
+ HasCreatingJob,
+ HasExt,
+ HasExtraFilesAndMetadata,
+ HasFileName,
+ HasInfo,
+ HasMetadata,
+ HasName,
+)
from galaxy.datatypes.metadata import (
MetadataElement, # import directly to maintain ease of use in Datatype class definitions
)
@@ -58,11 +67,6 @@ from . import (
if TYPE_CHECKING:
from galaxy.datatypes.display_applications.application import DisplayApplication
from galaxy.datatypes.registry import Registry
- from galaxy.model import (
- DatasetInstance,
- HistoryDatasetAssociation,
- HistoryDatasetCollectionAssociation,
- )
XSS_VULNERABLE_MIME_TYPES = [
"image/svg+xml", # Unfiltered by Galaxy and may contain JS that would be executed by some browsers.
@@ -83,11 +87,6 @@ DEFAULT_MAX_PEEK_SIZE = 1000000 # 1 MB
Headers = Dict[str, Any]
-class GeneratePrimaryFileDataset(Protocol):
- extra_files_path: str
- metadata: Any
-
-
class DatatypeConverterNotFoundException(Exception):
pass
@@ -113,7 +112,7 @@ class DatatypeValidation:
return f"DatatypeValidation[state={self.state},message={self.message}]"
-def validate(dataset_instance: "DatasetInstance") -> DatatypeValidation:
+def validate(dataset_instance: DatasetProtocol) -> DatatypeValidation:
try:
datatype_validation = dataset_instance.datatype.validate(dataset_instance)
except Exception as e:
@@ -237,7 +236,7 @@ class Data(metaclass=DataMeta):
def groom_dataset_content(self, file_name: str) -> None:
"""This function is called on an output dataset file if dataset_content_needs_grooming returns True."""
- def init_meta(self, dataset: "DatasetInstance", copy_from: Optional["DatasetInstance"] = None) -> None:
+ def init_meta(self, dataset: HasMetadata, copy_from: Optional[HasMetadata] = None) -> None:
# Metadata should be left mostly uninitialized. Dataset will
# handle returning default values when metadata is not set.
# copy_from allows metadata to be passed in that will be
@@ -247,12 +246,10 @@ class Data(metaclass=DataMeta):
if copy_from:
dataset.metadata = copy_from.metadata
- def set_meta(self, dataset: "DatasetInstance", *, overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, *, overwrite: bool = True, **kwd) -> None:
"""Unimplemented method, allows guessing of metadata from contents of file"""
- def missing_meta(
- self, dataset: "DatasetInstance", check: Optional[List] = None, skip: Optional[List] = None
- ) -> bool:
+ def missing_meta(self, dataset: HasMetadata, check: Optional[List] = None, skip: Optional[List] = None) -> bool:
"""
Checks for empty metadata values.
Returns False if no non-optional metadata is missing and the missing metadata key otherwise.
@@ -294,7 +291,7 @@ class Data(metaclass=DataMeta):
max_optional_metadata_filesize = property(get_max_optional_metadata_filesize, set_max_optional_metadata_filesize)
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
"""
Set the peek and blurb text
"""
@@ -305,7 +302,7 @@ class Data(metaclass=DataMeta):
dataset.peek = "file does not exist"
dataset.blurb = "file purged from disk"
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
"""Create HTML table, used for displaying peek"""
out = ['']
try:
@@ -346,7 +343,7 @@ class Data(metaclass=DataMeta):
return error, msg, messagetype
def _archive_composite_dataset(
- self, trans, data: "DatasetInstance", headers: Headers, do_action: str = "zip"
+ self, trans, data: DatasetHasHidProtocol, headers: Headers, do_action: str = "zip"
) -> Tuple[Union[ZipstreamWrapper, str], Headers]:
# save a composite object into a compressed archive for downloading
outfname = data.name[0:150]
@@ -392,7 +389,7 @@ class Data(metaclass=DataMeta):
yield fpath, rpath
def _serve_raw(
- self, dataset: "HistoryDatasetAssociation", to_ext: Optional[str], headers: Headers, **kwd
+ self, dataset: DatasetHasHidProtocol, to_ext: Optional[str], headers: Headers, **kwd
) -> Tuple[IO, Headers]:
headers["Content-Length"] = str(os.stat(dataset.file_name).st_size)
headers[
@@ -408,7 +405,7 @@ class Data(metaclass=DataMeta):
headers["Content-Disposition"] = f'attachment; filename="{filename}"'
return open(dataset.file_name, mode="rb"), headers
- def to_archive(self, dataset: "DatasetInstance", name: str = "") -> Iterable:
+ def to_archive(self, dataset: DatasetProtocol, name: str = "") -> Iterable:
"""
Collect archive paths and file handles that need to be exported when archiving `dataset`.
@@ -433,7 +430,7 @@ class Data(metaclass=DataMeta):
def display_data(
self,
trans,
- dataset: "HistoryDatasetAssociation",
+ dataset: DatasetHasHidProtocol,
preview: bool = False,
filename: Optional[str] = None,
to_ext: Optional[str] = None,
@@ -547,7 +544,7 @@ class Data(metaclass=DataMeta):
headers,
)
- def display_as_markdown(self, dataset_instance: "DatasetInstance") -> str:
+ def display_as_markdown(self, dataset_instance: DatasetProtocol) -> str:
"""Prepare for embedding dataset into a basic Markdown document.
This is a somewhat experimental interface and should not be implemented
@@ -575,7 +572,7 @@ class Data(metaclass=DataMeta):
result += indicate_data_truncated()
return result
- def _yield_user_file_content(self, trans, from_dataset: "DatasetInstance", filename: str, headers: Headers) -> IO:
+ def _yield_user_file_content(self, trans, from_dataset: HasCreatingJob, filename: str, headers: Headers) -> IO:
"""This method is responsible for sanitizing the HTML if needed."""
if trans.app.config.sanitize_all_html and headers.get("content-type", None) == "text/html":
# Sanitize anytime we respond with plain text/html content.
@@ -596,9 +593,9 @@ class Data(metaclass=DataMeta):
def _download_filename(
self,
- dataset: "HistoryDatasetAssociation",
+ dataset: DatasetHasHidProtocol,
to_ext: Optional[str] = None,
- hdca: Optional["HistoryDatasetCollectionAssociation"] = None,
+ hdca: Optional[DatasetHasHidProtocol] = None,
element_identifier: Optional[str] = None,
filename_pattern: Optional[str] = None,
) -> str:
@@ -630,14 +627,14 @@ class Data(metaclass=DataMeta):
return string.Template(filename_pattern).substitute(**template_values)
- def display_name(self, dataset: "DatasetInstance") -> str:
+ def display_name(self, dataset: HasName) -> str:
"""Returns formatted html of dataset name"""
try:
return escape(unicodify(dataset.name, "utf-8"))
except Exception:
return "name unavailable"
- def display_info(self, dataset: "DatasetInstance") -> str:
+ def display_info(self, dataset: HasInfo) -> str:
"""Returns formatted html of dataset info"""
try:
# Change new line chars to html
@@ -697,7 +694,7 @@ class Data(metaclass=DataMeta):
def get_display_application(self, key: str, default: Optional["DisplayApplication"] = None) -> "DisplayApplication":
return self.display_applications.get(key, default)
- def get_display_applications_by_dataset(self, dataset: "DatasetInstance", trans) -> Dict[str, "DisplayApplication"]:
+ def get_display_applications_by_dataset(self, dataset: DatasetProtocol, trans) -> Dict[str, "DisplayApplication"]:
rval = {}
for key, value in self.display_applications.items():
value = value.filter_by_dataset(dataset, trans)
@@ -716,7 +713,7 @@ class Data(metaclass=DataMeta):
except Exception:
return "unknown"
- def as_display_type(self, dataset: "DatasetInstance", type: str, **kwd) -> Union[FileObjType, str]:
+ def as_display_type(self, dataset: DatasetProtocol, type: str, **kwd) -> Union[FileObjType, str]:
"""Returns modified file contents for a particular display type"""
try:
if type in self.get_display_types():
@@ -731,7 +728,7 @@ class Data(metaclass=DataMeta):
return f"This display type ({type}) is not implemented for this datatype ({dataset.ext})."
def get_display_links(
- self, dataset: "DatasetInstance", type: str, app, base_url: str, target_frame: str = "_blank", **kwd
+ self, dataset: DatasetProtocol, type: str, app, base_url: str, target_frame: str = "_blank", **kwd
):
"""
Returns a list of tuples of (name, link) for a particular display type. No check on
@@ -753,15 +750,13 @@ class Data(metaclass=DataMeta):
)
return target_frame, []
- def get_converter_types(
- self, original_dataset: "DatasetInstance", datatypes_registry: "Registry"
- ) -> Dict[str, Dict]:
+ def get_converter_types(self, original_dataset: HasExt, datatypes_registry: "Registry") -> Dict[str, Dict]:
"""Returns available converters by type for this dataset"""
return datatypes_registry.get_converters_by_datatype(original_dataset.ext)
def find_conversion_destination(
- self, dataset, accepted_formats: List[str], datatypes_registry, **kwd
- ) -> Tuple[bool, Optional[str], Optional["DatasetInstance"]]:
+ self, dataset: DatasetProtocol, accepted_formats: List[str], datatypes_registry, **kwd
+ ) -> Tuple[bool, Optional[str], Any]:
"""Returns ( direct_match, converted_ext, existing converted dataset )"""
return datatypes_registry.find_conversion_destination_for_dataset_by_extensions(
dataset, accepted_formats, **kwd
@@ -770,7 +765,7 @@ class Data(metaclass=DataMeta):
def convert_dataset(
self,
trans,
- original_dataset: "HistoryDatasetAssociation",
+ original_dataset: DatasetHasHidProtocol,
target_type: str,
return_output: bool = False,
visible: bool = True,
@@ -806,11 +801,11 @@ class Data(metaclass=DataMeta):
# We need to clear associated files before we set metadata
# so that as soon as metadata starts to be set, e.g. implicitly converted datasets are deleted and no longer available 'while' metadata is being set, not just after
# We'll also clear after setting metadata, for backwards compatibility
- def after_setting_metadata(self, dataset: "DatasetInstance") -> None:
+ def after_setting_metadata(self, dataset: HasClearAssociatedFiles) -> None:
"""This function is called on the dataset after metadata is set."""
dataset.clear_associated_files(metadata_safe=True)
- def before_setting_metadata(self, dataset: "DatasetInstance") -> None:
+ def before_setting_metadata(self, dataset: HasClearAssociatedFiles) -> None:
"""This function is called on the dataset before metadata is set."""
dataset.clear_associated_files(metadata_safe=True)
@@ -851,7 +846,7 @@ class Data(metaclass=DataMeta):
files[key] = value
return files
- def get_writable_files_for_dataset(self, dataset: "DatasetInstance") -> Dict:
+ def get_writable_files_for_dataset(self, dataset: Optional[HasMetadata]) -> Dict:
files = {}
if self.composite_type != "auto_primary_file":
files[self.primary_file_name] = self.__new_composite_file(self.primary_file_name)
@@ -859,7 +854,7 @@ class Data(metaclass=DataMeta):
files[key] = value
return files
- def get_composite_files(self, dataset=None):
+ def get_composite_files(self, dataset: Optional[HasMetadata] = None):
def substitute_composite_key(key, composite_file):
if composite_file.substitute_name_with_metadata:
if dataset:
@@ -874,7 +869,7 @@ class Data(metaclass=DataMeta):
files[substitute_composite_key(key, value)] = value
return files
- def generate_primary_file(self, dataset: GeneratePrimaryFileDataset) -> str:
+ def generate_primary_file(self, dataset: HasExtraFilesAndMetadata) -> str:
raise Exception("generate_primary_file is not implemented for this datatype.")
@property
@@ -911,7 +906,7 @@ class Data(metaclass=DataMeta):
"""
return data_format in self.dataproviders
- def dataprovider(self, dataset: "DatasetInstance", data_format: str, **settings):
+ def dataprovider(self, dataset: DatasetProtocol, data_format: str, **settings):
"""
Base dataprovider factory for all datatypes that returns the proper provider
for the given `data_format` or raises a `NoProviderAvailable`.
@@ -920,22 +915,22 @@ class Data(metaclass=DataMeta):
return self.dataproviders[data_format](self, dataset, **settings)
raise p_dataproviders.exceptions.NoProviderAvailable(self, data_format)
- def validate(self, dataset: "DatasetInstance", **kwd) -> DatatypeValidation:
+ def validate(self, dataset: DatasetProtocol, **kwd) -> DatatypeValidation:
return DatatypeValidation.unvalidated()
@p_dataproviders.decorators.dataprovider_factory("base")
- def base_dataprovider(self, dataset: "DatasetInstance", **settings) -> p_dataproviders.base.DataProvider:
+ def base_dataprovider(self, dataset: DatasetProtocol, **settings) -> p_dataproviders.base.DataProvider:
dataset_source = p_dataproviders.dataset.DatasetDataProvider(dataset)
return p_dataproviders.base.DataProvider(dataset_source, **settings)
@p_dataproviders.decorators.dataprovider_factory("chunk", p_dataproviders.chunk.ChunkDataProvider.settings)
- def chunk_dataprovider(self, dataset: "DatasetInstance", **settings) -> p_dataproviders.chunk.ChunkDataProvider:
+ def chunk_dataprovider(self, dataset: DatasetProtocol, **settings) -> p_dataproviders.chunk.ChunkDataProvider:
dataset_source = p_dataproviders.dataset.DatasetDataProvider(dataset)
return p_dataproviders.chunk.ChunkDataProvider(dataset_source, **settings)
@p_dataproviders.decorators.dataprovider_factory("chunk64", p_dataproviders.chunk.Base64ChunkDataProvider.settings)
def chunk64_dataprovider(
- self, dataset: "DatasetInstance", **settings
+ self, dataset: DatasetProtocol, **settings
) -> p_dataproviders.chunk.Base64ChunkDataProvider:
dataset_source = p_dataproviders.dataset.DatasetDataProvider(dataset)
return p_dataproviders.chunk.Base64ChunkDataProvider(dataset_source, **settings)
@@ -946,7 +941,7 @@ class Data(metaclass=DataMeta):
mime = DEFAULT_MIME_TYPE
headers["content-type"] = mime
- def handle_dataset_as_image(self, hda: "DatasetInstance") -> str:
+ def handle_dataset_as_image(self, hda: DatasetProtocol) -> str:
raise Exception("Unimplemented Method")
def __getstate__(self) -> Dict[str, Any]:
@@ -978,13 +973,13 @@ class Text(Data):
"""Returns the mime type of the datatype"""
return "text/plain"
- def set_meta(self, dataset: "DatasetInstance", *, overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, *, overwrite: bool = True, **kwd) -> None:
"""
Set the number of lines of data in dataset.
"""
dataset.metadata.data_lines = self.count_data_lines(dataset)
- def estimate_file_lines(self, dataset: "DatasetInstance") -> Optional[int]:
+ def estimate_file_lines(self, dataset: DatasetProtocol) -> Optional[int]:
"""
Perform a rough estimate by extrapolating number of lines from a small read.
"""
@@ -998,7 +993,7 @@ class Text(Data):
log.error(f"Unable to estimate lines in file {dataset.file_name}")
return None
- def count_data_lines(self, dataset: "DatasetInstance") -> Optional[int]:
+ def count_data_lines(self, dataset: HasFileName) -> Optional[int]:
"""
Count the number of lines of data in dataset,
skipping all blank lines and comments.
@@ -1019,7 +1014,7 @@ class Text(Data):
return None
return data_lines
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
"""
Set the peek. This method is used by various subclasses of Text.
"""
@@ -1132,9 +1127,7 @@ class Text(Data):
# ------------- Dataproviders
@p_dataproviders.decorators.dataprovider_factory("line", p_dataproviders.line.FilteredLineDataProvider.settings)
- def line_dataprovider(
- self, dataset: "DatasetInstance", **settings
- ) -> p_dataproviders.line.FilteredLineDataProvider:
+ def line_dataprovider(self, dataset: DatasetProtocol, **settings) -> p_dataproviders.line.FilteredLineDataProvider:
"""
Returns an iterator over the dataset's lines (that have been stripped)
optionally excluding blank lines and lines that start with a comment character.
@@ -1144,7 +1137,7 @@ class Text(Data):
@p_dataproviders.decorators.dataprovider_factory("regex-line", p_dataproviders.line.RegexLineDataProvider.settings)
def regex_line_dataprovider(
- self, dataset: "DatasetInstance", **settings
+ self, dataset: DatasetProtocol, **settings
) -> p_dataproviders.line.RegexLineDataProvider:
"""
Returns an iterator over the dataset's lines
diff --git a/lib/galaxy/datatypes/flow.py b/lib/galaxy/datatypes/flow.py
index e520be5a1ea..6f43d8316cf 100644
--- a/lib/galaxy/datatypes/flow.py
+++ b/lib/galaxy/datatypes/flow.py
@@ -3,8 +3,8 @@ Flow analysis datatypes.
"""
import logging
-from typing import TYPE_CHECKING
+from galaxy.datatypes._protocols import DatasetProtocol
from galaxy.datatypes.binary import Binary
from galaxy.datatypes.sniff import (
build_sniff_from_prefix,
@@ -12,9 +12,6 @@ from galaxy.datatypes.sniff import (
)
from . import data
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
-
log = logging.getLogger(__name__)
@@ -24,7 +21,7 @@ class FCS(Binary):
file_ext = "fcs"
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
dataset.peek = "Binary FCS file"
dataset.blurb = data.nice_size(dataset.get_size())
@@ -32,7 +29,7 @@ class FCS(Binary):
dataset.peek = "file does not exist"
dataset.blurb = "file purged from disk"
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
try:
return dataset.peek
except Exception:
diff --git a/lib/galaxy/datatypes/genetics.py b/lib/galaxy/datatypes/genetics.py
index 1ebb8398cb6..bfb2ad2ac14 100644
--- a/lib/galaxy/datatypes/genetics.py
+++ b/lib/galaxy/datatypes/genetics.py
@@ -20,7 +20,6 @@ from typing import (
IO,
List,
Optional,
- TYPE_CHECKING,
Union,
)
from urllib.parse import quote_plus
@@ -28,9 +27,13 @@ from urllib.parse import quote_plus
from markupsafe import escape
from galaxy.datatypes import metadata
+from galaxy.datatypes._protocols import (
+ DatasetProtocol,
+ HasExtraFilesAndMetadata,
+ HasMetadata,
+)
from galaxy.datatypes.data import (
DatatypeValidation,
- GeneratePrimaryFileDataset,
Text,
)
from galaxy.datatypes.metadata import MetadataElement
@@ -46,9 +49,6 @@ from galaxy.util import (
)
from galaxy.util.compression_utils import FileObjType
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
-
gal_Log = logging.getLogger(__name__)
verbose = False
@@ -75,7 +75,7 @@ class GenomeGraphs(Tabular):
super().__init__(**kwd)
self.add_display_app("ucsc", "Genome Graph", "as_ucsc_display_file", "ucsc_links")
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
super().set_meta(dataset, overwrite=overwrite, **kwd)
dataset.metadata.markerCol = 1
header = open(dataset.file_name).readlines()[0].strip().split("\t")
@@ -84,13 +84,13 @@ class GenomeGraphs(Tabular):
t[0] = "string"
dataset.metadata.column_types = t
- def as_ucsc_display_file(self, dataset: "DatasetInstance", **kwd) -> Union[FileObjType, str]:
+ def as_ucsc_display_file(self, dataset: DatasetProtocol, **kwd) -> Union[FileObjType, str]:
"""
Returns file
"""
return open(dataset.file_name, "rb")
- def ucsc_links(self, dataset: "DatasetInstance", type: str, app, base_url: str) -> List:
+ def ucsc_links(self, dataset: DatasetProtocol, type: str, app, base_url: str) -> List:
"""
from the ever-helpful angie hinrichs angie@soe.ucsc.edu
a genome graphs call looks like this
@@ -142,7 +142,7 @@ class GenomeGraphs(Tabular):
ret_val.append((site_name, link))
return ret_val
- def make_html_table(self, dataset: "DatasetInstance", **kwargs) -> str:
+ def make_html_table(self, dataset: DatasetProtocol, **kwargs) -> str:
"""
Create HTML table, used for displaying peek
"""
@@ -173,7 +173,7 @@ class GenomeGraphs(Tabular):
except Exception as exc:
return f"Can't create peek {exc}"
- def validate(self, dataset: "DatasetInstance", **kwd) -> DatatypeValidation:
+ def validate(self, dataset: DatasetProtocol, **kwd) -> DatatypeValidation:
"""
Validate a gg file - all numeric after header row
"""
@@ -237,7 +237,7 @@ class rgTabList(Tabular):
super().__init__(**kwd)
self.column_names = []
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
"""Returns formated html of peek"""
return self.make_html_table(dataset, column_names=self.column_names)
@@ -302,7 +302,7 @@ class Rgenetics(Html):
composite_type = "auto_primary_file"
file_ext = "rgenetics"
- def generate_primary_file(self, dataset: GeneratePrimaryFileDataset) -> str:
+ def generate_primary_file(self, dataset: HasExtraFilesAndMetadata) -> str:
rval = ["Rgenetics Galaxy Composite Dataset "]
rval.append("This composite dataset is composed of the following files:
")
for composite_name, composite_file in self.get_composite_files(dataset=dataset).items():
@@ -319,7 +319,7 @@ class Rgenetics(Html):
rval.append("
")
return "\n".join(rval)
- def regenerate_primary_file(self, dataset: "DatasetInstance") -> None:
+ def regenerate_primary_file(self, dataset: DatasetProtocol) -> None:
"""
cannot do this until we are setting metadata
"""
@@ -341,7 +341,7 @@ class Rgenetics(Html):
"""Returns the mime type of the datatype"""
return "text/html"
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
for lped/pbed eg
@@ -381,7 +381,7 @@ class SNPMatrix(Rgenetics):
file_ext = "snpmatrix"
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
dataset.peek = "Binary RGenetics file"
dataset.blurb = nice_size(dataset.get_size())
@@ -577,7 +577,7 @@ class IdeasPre(Html):
self.add_composite_file("IDEAS_input_config.txt", description="IDEAS input config", is_binary=False)
self.add_composite_file("tmp.tar.gz", description="Compressed archive of compressed bed files", is_binary=True)
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
super().set_meta(dataset, overwrite=overwrite, **kwd)
for fname in os.listdir(dataset.extra_files_path):
if fname.startswith("chromosomes"):
@@ -590,7 +590,7 @@ class IdeasPre(Html):
dataset.metadata.tmp_archive = os.path.join(dataset.extra_files_path, fname)
self.regenerate_primary_file(dataset)
- def generate_primary_file(self, dataset: GeneratePrimaryFileDataset) -> str:
+ def generate_primary_file(self, dataset: HasExtraFilesAndMetadata) -> str:
rval = [""]
rval.append("Files prepared for IDEAS
")
rval.append("")
@@ -600,7 +600,7 @@ class IdeasPre(Html):
rval.append("
\n")
return "\n".join(rval)
- def regenerate_primary_file(self, dataset: "DatasetInstance") -> None:
+ def regenerate_primary_file(self, dataset: DatasetProtocol) -> None:
# Cannot do this until we are setting metadata.
rval = [""]
rval.append("Files prepared for IDEAS
")
@@ -654,7 +654,7 @@ class RexpBase(Html):
is_binary=False,
)
- def generate_primary_file(self, dataset: GeneratePrimaryFileDataset) -> str:
+ def generate_primary_file(self, dataset: HasExtraFilesAndMetadata) -> str:
"""
This is called only at upload to write the html file
cannot rename the datasets here - they come with the default unfortunately
@@ -764,7 +764,7 @@ class RexpBase(Html):
p = []
return "\n".join(p)
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
"""
expects a .pheno file in the extra_files_dir - ugh
note that R is weird and does not include the row.name in
@@ -813,7 +813,7 @@ class RexpBase(Html):
except Exception:
return "## rexpression get_file_peek: no file found"
- def regenerate_primary_file(self, dataset: "DatasetInstance") -> None:
+ def regenerate_primary_file(self, dataset: DatasetProtocol) -> None:
"""
cannot do this until we are setting metadata
"""
@@ -830,11 +830,11 @@ class RexpBase(Html):
f.write("\n".join(rval))
f.write("\n")
- def init_meta(self, dataset: "DatasetInstance", copy_from: Optional["DatasetInstance"] = None) -> None:
+ def init_meta(self, dataset: HasMetadata, copy_from: Optional[HasMetadata] = None) -> None:
if copy_from:
dataset.metadata = copy_from.metadata
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
NOTE we apply the tabular machinary to the phenodata extracted
from a BioC eSet or affybatch.
@@ -910,7 +910,7 @@ class RexpBase(Html):
except Exception as exc:
return f"Can't create html table {unicodify(exc)}"
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
"""
Returns formatted html of peek
"""
diff --git a/lib/galaxy/datatypes/gis.py b/lib/galaxy/datatypes/gis.py
index ead789aa2d9..b3b37dce200 100644
--- a/lib/galaxy/datatypes/gis.py
+++ b/lib/galaxy/datatypes/gis.py
@@ -1,13 +1,11 @@
"""
GIS classes
"""
-from typing import TYPE_CHECKING
-
+from galaxy.datatypes._protocols import (
+ DatasetProtocol,
+ HasExtraFilesAndMetadata,
+)
from galaxy.datatypes.binary import Binary
-from galaxy.datatypes.data import GeneratePrimaryFileDataset
-
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
class Shapefile(Binary):
@@ -66,7 +64,7 @@ class Shapefile(Binary):
"shapefile.shp.xml", description="Geospatial metadata in XML format (xml)", is_binary=False, optional=True
)
- def generate_primary_file(self, dataset: GeneratePrimaryFileDataset) -> str:
+ def generate_primary_file(self, dataset: HasExtraFilesAndMetadata) -> str:
rval = ["Shapefile Galaxy Composite Dataset"]
rval.append("This composite dataset is composed of the following files:
")
for composite_name, composite_file in self.get_composite_files(dataset=dataset).items():
@@ -83,7 +81,7 @@ class Shapefile(Binary):
rval.append("
\n")
return "\n".join(rval)
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
"""Set the peek and blurb text."""
if not dataset.dataset.purged:
dataset.peek = "Shapefile data"
@@ -92,7 +90,7 @@ class Shapefile(Binary):
dataset.peek = "file does not exist"
dataset.blurb = "file purged from disk"
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
"""Create HTML content, used for displaying peek."""
try:
return dataset.peek
diff --git a/lib/galaxy/datatypes/goldenpath.py b/lib/galaxy/datatypes/goldenpath.py
index d5ef0405449..e3204ac3067 100644
--- a/lib/galaxy/datatypes/goldenpath.py
+++ b/lib/galaxy/datatypes/goldenpath.py
@@ -3,10 +3,10 @@ import logging
import os
from typing import (
Set,
- TYPE_CHECKING,
Union,
)
+from galaxy.datatypes._protocols import DatasetProtocol
from galaxy.datatypes.sniff import (
build_sniff_from_prefix,
FilePrefix,
@@ -14,9 +14,6 @@ from galaxy.datatypes.sniff import (
)
from .tabular import Tabular
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
-
log = logging.getLogger(__name__)
@@ -27,7 +24,7 @@ class GoldenPath(Tabular):
edam_format = "format_3693"
file_ext = "agp"
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
# AGPFile reads and validates entire file.
AGPFile(dataset.file_name)
super().set_meta(dataset, overwrite=overwrite, **kwd)
diff --git a/lib/galaxy/datatypes/graph.py b/lib/galaxy/datatypes/graph.py
index babdb0c8489..3ecea4e1ede 100644
--- a/lib/galaxy/datatypes/graph.py
+++ b/lib/galaxy/datatypes/graph.py
@@ -2,11 +2,9 @@
Graph content classes.
"""
import logging
-from typing import (
- List,
- TYPE_CHECKING,
-)
+from typing import List
+from galaxy.datatypes._protocols import DatasetProtocol
from galaxy.datatypes.dataproviders.column import ColumnarDataProvider
from galaxy.datatypes.dataproviders.dataset import DatasetDataProvider
from galaxy.datatypes.dataproviders.hierarchy import XMLDataProvider
@@ -18,9 +16,6 @@ from . import (
xml,
)
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
-
log = logging.getLogger(__name__)
@@ -33,7 +28,7 @@ class Xgmml(xml.GenericXml):
file_ext = "xgmml"
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
"""
Set the peek and blurb text
"""
@@ -63,7 +58,7 @@ class Xgmml(xml.GenericXml):
data.Text.merge(split_files, output_file)
@dataproviders.decorators.dataprovider_factory("node-edge", XMLDataProvider.settings)
- def node_edge_dataprovider(self, dataset: "DatasetInstance", **settings) -> "XGMMLGraphDataProvider":
+ def node_edge_dataprovider(self, dataset: DatasetProtocol, **settings) -> "XGMMLGraphDataProvider":
dataset_source = DatasetDataProvider(dataset)
return XGMMLGraphDataProvider(dataset_source, **settings)
@@ -81,7 +76,7 @@ class Sif(tabular.Tabular):
file_ext = "sif"
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
"""
Set the peek and blurb text
"""
@@ -103,7 +98,7 @@ class Sif(tabular.Tabular):
data.Text.merge(split_files, output_file)
@dataproviders.decorators.dataprovider_factory("node-edge", ColumnarDataProvider.settings)
- def node_edge_dataprovider(self, dataset: "DatasetInstance", **settings) -> "SIFGraphDataProvider":
+ def node_edge_dataprovider(self, dataset: DatasetProtocol, **settings) -> "SIFGraphDataProvider":
dataset_source = DatasetDataProvider(dataset)
return SIFGraphDataProvider(dataset_source, **settings)
diff --git a/lib/galaxy/datatypes/hdf5.py b/lib/galaxy/datatypes/hdf5.py
index 6a2b42c58be..2bc15251420 100644
--- a/lib/galaxy/datatypes/hdf5.py
+++ b/lib/galaxy/datatypes/hdf5.py
@@ -2,20 +2,15 @@
This datatype was created for use with the iSEE interactive tool.
"""
-from typing import (
- Optional,
- TYPE_CHECKING,
-)
+from typing import Optional
-from galaxy.datatypes.data import (
- Data,
- GeneratePrimaryFileDataset,
+from galaxy.datatypes._protocols import (
+ HasExtraFilesAndMetadata,
+ HasMetadata,
)
+from galaxy.datatypes.data import Data
from galaxy.datatypes.metadata import MetadataElement
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
-
class HDF5SummarizedExperiment(Data):
"""Composite datatype to represent HDF5SummarizedExperiment objects.
@@ -56,11 +51,11 @@ class HDF5SummarizedExperiment(Data):
description="Summarized experiment data array",
)
- def init_meta(self, dataset: "DatasetInstance", copy_from: Optional["DatasetInstance"] = None) -> None:
+ def init_meta(self, dataset: HasMetadata, copy_from: Optional[HasMetadata] = None) -> None:
"""Override parent init metadata."""
Data.init_meta(self, dataset, copy_from=copy_from)
- def generate_primary_file(self, dataset: GeneratePrimaryFileDataset) -> str:
+ def generate_primary_file(self, dataset: HasExtraFilesAndMetadata) -> str:
"""Generate primary file to represent dataset."""
return f"""
diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py
index 8dd6f79b7c6..6576b88b871 100644
--- a/lib/galaxy/datatypes/images.py
+++ b/lib/galaxy/datatypes/images.py
@@ -4,17 +4,17 @@ Image classes
import base64
import json
import logging
-from typing import (
- Optional,
- TYPE_CHECKING,
-)
+from typing import Optional
import mrcfile
import numpy as np
import tifffile
+from galaxy.datatypes._protocols import (
+ DatasetProtocol,
+ HasExtraFilesAndMetadata,
+)
from galaxy.datatypes.binary import Binary
-from galaxy.datatypes.data import GeneratePrimaryFileDataset
from galaxy.datatypes.metadata import (
FileParameter,
MetadataElement,
@@ -29,9 +29,6 @@ from galaxy.util.image_util import check_image_type
from . import data
from .xml import GenericXml
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
-
log = logging.getLogger(__name__)
# TODO: Uploading image files of various types is supported in Galaxy, but on
@@ -56,7 +53,7 @@ class Image(data.Data):
super().__init__(**kwd)
self.image_formats = [self.file_ext.upper()]
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
dataset.peek = f"Image in {dataset.extension} format"
dataset.blurb = nice_size(dataset.get_size())
@@ -68,7 +65,7 @@ class Image(data.Data):
"""Determine if the file is in this format"""
return check_image_type(filename, self.image_formats)
- def handle_dataset_as_image(self, hda: "DatasetInstance") -> str:
+ def handle_dataset_as_image(self, hda: DatasetProtocol) -> str:
dataset = hda.dataset
name = hda.name or ""
with open(dataset.file_name, "rb") as f:
@@ -108,7 +105,7 @@ class OMETiff(Tiff):
)
def set_meta(
- self, dataset: "DatasetInstance", overwrite: bool = True, metadata_tmp_files_dir: Optional[str] = None, **kwd
+ self, dataset: DatasetProtocol, overwrite: bool = True, metadata_tmp_files_dir: Optional[str] = None, **kwd
) -> None:
spec_key = "offsets"
offsets_file = dataset.metadata.offsets
@@ -383,7 +380,7 @@ class Analyze75(Binary):
self.add_composite_file("t2m", description="The Analyze75 t2m file.", optional=True, is_binary=True)
- def generate_primary_file(self, dataset: GeneratePrimaryFileDataset) -> str:
+ def generate_primary_file(self, dataset: HasExtraFilesAndMetadata) -> str:
rval = ["Analyze75 Composite Dataset."]
rval.append("This composite dataset is composed of the following files:
")
for composite_name, composite_file in self.get_composite_files(dataset=dataset).items():
@@ -493,7 +490,7 @@ class Star(data.Text):
file_ext = "star"
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
"""Set the peek and blurb text"""
if not dataset.dataset.purged:
dataset.peek = data.get_file_peek(dataset.file_name)
diff --git a/lib/galaxy/datatypes/interval.py b/lib/galaxy/datatypes/interval.py
index 2c4e710d9e1..a21cb2368c4 100644
--- a/lib/galaxy/datatypes/interval.py
+++ b/lib/galaxy/datatypes/interval.py
@@ -9,7 +9,6 @@ from typing import (
List,
Optional,
Tuple,
- TYPE_CHECKING,
Union,
)
from urllib.parse import quote_plus
@@ -22,6 +21,11 @@ from bx.intervals.io import (
from galaxy import util
from galaxy.datatypes import metadata
+from galaxy.datatypes._protocols import (
+ DatasetProtocol,
+ HasId,
+ HasMetadata,
+)
from galaxy.datatypes.data import DatatypeValidation
from galaxy.datatypes.dataproviders.dataset import (
DatasetDataProvider,
@@ -48,9 +52,6 @@ from . import (
dataproviders,
)
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
-
log = logging.getLogger(__name__)
# Contains the meta columns and the words that map to it; list aliases on the
@@ -123,11 +124,11 @@ class Interval(Tabular):
Tabular.__init__(self, **kwd)
self.add_display_app("ucsc", "display at UCSC", "as_ucsc_display_file", "ucsc_links")
- def init_meta(self, dataset: "DatasetInstance", copy_from: Optional["DatasetInstance"] = None) -> None:
+ def init_meta(self, dataset: HasMetadata, copy_from: Optional[HasMetadata] = None) -> None:
Tabular.init_meta(self, dataset, copy_from=copy_from)
def set_meta(
- self, dataset: "DatasetInstance", *, overwrite: bool = True, first_line_is_header: bool = False, **kwd
+ self, dataset: DatasetProtocol, *, overwrite: bool = True, first_line_is_header: bool = False, **kwd
) -> None:
"""Tries to guess from the line the location number of the column for the chromosome, region start-end and strand"""
Tabular.set_meta(self, dataset, overwrite=overwrite, skip=0)
@@ -188,7 +189,7 @@ class Interval(Tabular):
else:
empty_line_count += 1
- def displayable(self, dataset: "DatasetInstance"):
+ def displayable(self, dataset: DatasetProtocol) -> bool:
try:
return (
not dataset.dataset.purged
@@ -205,7 +206,7 @@ class Interval(Tabular):
def get_estimated_display_viewport(
self,
- dataset: "DatasetInstance",
+ dataset: DatasetProtocol,
chrom_col: Optional[int] = None,
start_col: Optional[int] = None,
end_col: Optional[int] = None,
@@ -267,7 +268,7 @@ class Interval(Tabular):
log.exception("Exception caught attempting to generate viewport for dataset '%d'", dataset.id)
return (None, None, None)
- def as_ucsc_display_file(self, dataset: "DatasetInstance", **kwd) -> Union[FileObjType, str]:
+ def as_ucsc_display_file(self, dataset: DatasetProtocol, **kwd) -> Union[FileObjType, str]:
"""Returns file contents with only the bed data"""
with tempfile.NamedTemporaryFile(delete=False, mode="w") as fh:
c, s, e, t, n = (
@@ -301,7 +302,7 @@ class Interval(Tabular):
fh.write("%s\n" % "\t".join(tmp))
return compression_utils.get_fileobj(fh.name, mode="rb")
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
"""Returns formated html of peek"""
return self.make_html_table(
dataset,
@@ -314,7 +315,7 @@ class Interval(Tabular):
},
)
- def ucsc_links(self, dataset: "DatasetInstance", type: str, app, base_url: str) -> List:
+ def ucsc_links(self, dataset: DatasetProtocol, type: str, app, base_url: str) -> List:
"""
Generate links to UCSC genome browser sites based on the dbkey
and content of dataset.
@@ -348,7 +349,7 @@ class Interval(Tabular):
ret_val.append((site_name, link))
return ret_val
- def validate(self, dataset: "DatasetInstance", **kwd) -> DatatypeValidation:
+ def validate(self, dataset: DatasetProtocol, **kwd) -> DatatypeValidation:
"""Validate an interval file using the bx GenomicIntervalReader"""
c, s, e, t = (
dataset.metadata.chromCol,
@@ -403,20 +404,20 @@ class Interval(Tabular):
# ------------- Dataproviders
@dataproviders.decorators.dataprovider_factory("genomic-region", GenomicRegionDataProvider.settings)
- def genomic_region_dataprovider(self, dataset: "DatasetInstance", **settings) -> GenomicRegionDataProvider:
+ def genomic_region_dataprovider(self, dataset: DatasetProtocol, **settings) -> GenomicRegionDataProvider:
return GenomicRegionDataProvider(dataset, **settings)
@dataproviders.decorators.dataprovider_factory("genomic-region-dict", GenomicRegionDataProvider.settings)
- def genomic_region_dict_dataprovider(self, dataset: "DatasetInstance", **settings) -> GenomicRegionDataProvider:
+ def genomic_region_dict_dataprovider(self, dataset: DatasetProtocol, **settings) -> GenomicRegionDataProvider:
settings["named_columns"] = True
return self.genomic_region_dataprovider(dataset, **settings)
@dataproviders.decorators.dataprovider_factory("interval", IntervalDataProvider.settings)
- def interval_dataprovider(self, dataset: "DatasetInstance", **settings) -> IntervalDataProvider:
+ def interval_dataprovider(self, dataset: DatasetProtocol, **settings) -> IntervalDataProvider:
return IntervalDataProvider(dataset, **settings)
@dataproviders.decorators.dataprovider_factory("interval-dict", IntervalDataProvider.settings)
- def interval_dict_dataprovider(self, dataset: "DatasetInstance", **settings) -> IntervalDataProvider:
+ def interval_dict_dataprovider(self, dataset: DatasetProtocol, **settings) -> IntervalDataProvider:
settings["named_columns"] = True
return self.interval_dataprovider(dataset, **settings)
@@ -429,7 +430,7 @@ class BedGraph(Interval):
track_type = "LineTrack"
data_sources = {"data": "bigwig", "index": "bigwig"}
- def as_ucsc_display_file(self, dataset: "DatasetInstance", **kwd) -> Union[FileObjType, str]:
+ def as_ucsc_display_file(self, dataset: DatasetProtocol, **kwd) -> Union[FileObjType, str]:
"""
Returns file contents as is with no modifications.
TODO: this is a functional stub and will need to be enhanced moving forward to provide additional support for bedgraph.
@@ -438,7 +439,7 @@ class BedGraph(Interval):
def get_estimated_display_viewport(
self,
- dataset: "DatasetInstance",
+ dataset: DatasetProtocol,
chrom_col: Optional[int] = 0,
start_col: Optional[int] = 1,
end_col: Optional[int] = 2,
@@ -497,7 +498,7 @@ class Bed(Interval):
)
# do we need to repeat these? they are the same as should be inherited from interval type
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""Sets the metadata information for datasets previously determined to be in bed format."""
if dataset.has_data():
i = 0
@@ -518,7 +519,7 @@ class Bed(Interval):
break
Tabular.set_meta(self, dataset, overwrite=overwrite, skip=i)
- def as_ucsc_display_file(self, dataset: "DatasetInstance", **kwd) -> Union[FileObjType, str]:
+ def as_ucsc_display_file(self, dataset: DatasetProtocol, **kwd) -> Union[FileObjType, str]:
"""Returns file contents with only the bed data. If bed 6+, treat as interval."""
for line in open(dataset.file_name):
line = line.strip()
@@ -730,7 +731,7 @@ class BedStrict(Bed):
Tabular.__init__(self, **kwd)
self.clear_display_apps() # only new style display applications for this datatype
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
Tabular.set_meta(self, dataset, overwrite=overwrite, **kwd) # need column count first
if dataset.metadata.columns >= 4:
dataset.metadata.nameCol = 4
@@ -757,7 +758,7 @@ class Bed12(BedStrict):
class _RemoteCallMixin:
def _get_remote_call_url(
- self, redirect_url: str, site_name: str, dataset: "DatasetInstance", type: str, app, base_url: str
+ self, redirect_url: str, site_name: str, dataset: HasId, type: str, app, base_url: str
) -> str:
"""Retrieve the URL to call out to an external site and retrieve data.
This routes our external URL through a local galaxy instance which makes
@@ -814,7 +815,7 @@ class Gff(Tabular, _RemoteCallMixin):
self.add_display_app("ucsc", "display at UCSC", "as_ucsc_display_file", "ucsc_links")
self.add_display_app("gbrowse", "display in Gbrowse", "as_gbrowse_display_file", "gbrowse_links")
- def set_attribute_metadata(self, dataset: "DatasetInstance") -> None:
+ def set_attribute_metadata(self, dataset: DatasetProtocol) -> None:
"""
Sets metadata elements for dataset's attributes.
"""
@@ -854,7 +855,7 @@ class Gff(Tabular, _RemoteCallMixin):
dataset.metadata.attribute_types = attribute_types
dataset.metadata.attributes = len(attribute_types)
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
self.set_attribute_metadata(dataset)
i = 0
@@ -872,12 +873,12 @@ class Gff(Tabular, _RemoteCallMixin):
pass
Tabular.set_meta(self, dataset, overwrite=overwrite, skip=i)
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
"""Returns formated html of peek"""
return self.make_html_table(dataset, column_names=self.column_names)
def get_estimated_display_viewport(
- self, dataset: "DatasetInstance"
+ self, dataset: DatasetProtocol
) -> Tuple[Optional[str], Optional[str], Optional[str]]:
"""
Return a chrom, start, stop tuple for viewing a file. There are slight differences between gff 2 and gff 3
@@ -953,7 +954,7 @@ class Gff(Tabular, _RemoteCallMixin):
log.exception("Unexpected error")
return (None, None, None) # could not determine viewport
- def ucsc_links(self, dataset: "DatasetInstance", type: str, app, base_url: str) -> List:
+ def ucsc_links(self, dataset: DatasetProtocol, type: str, app, base_url: str) -> List:
ret_val = []
seqid, start, stop = self.get_estimated_display_viewport(dataset)
if seqid is not None:
@@ -966,7 +967,7 @@ class Gff(Tabular, _RemoteCallMixin):
ret_val.append((site_name, link))
return ret_val
- def gbrowse_links(self, dataset: "DatasetInstance", type: str, app, base_url: str) -> List:
+ def gbrowse_links(self, dataset: DatasetProtocol, type: str, app, base_url: str) -> List:
ret_val = []
seqid, start, stop = self.get_estimated_display_viewport(dataset)
if seqid is not None:
@@ -1032,20 +1033,20 @@ class Gff(Tabular, _RemoteCallMixin):
# ------------- Dataproviders
# redefine bc super is Tabular
@dataproviders.decorators.dataprovider_factory("genomic-region", GenomicRegionDataProvider.settings)
- def genomic_region_dataprovider(self, dataset: "DatasetInstance", **settings) -> GenomicRegionDataProvider:
+ def genomic_region_dataprovider(self, dataset: DatasetProtocol, **settings) -> GenomicRegionDataProvider:
return GenomicRegionDataProvider(dataset, 0, 3, 4, **settings)
@dataproviders.decorators.dataprovider_factory("genomic-region-dict", GenomicRegionDataProvider.settings)
- def genomic_region_dict_dataprovider(self, dataset: "DatasetInstance", **settings) -> GenomicRegionDataProvider:
+ def genomic_region_dict_dataprovider(self, dataset: DatasetProtocol, **settings) -> GenomicRegionDataProvider:
settings["named_columns"] = True
return self.genomic_region_dataprovider(dataset, **settings)
@dataproviders.decorators.dataprovider_factory("interval", IntervalDataProvider.settings)
- def interval_dataprovider(self, dataset: "DatasetInstance", **settings):
+ def interval_dataprovider(self, dataset: DatasetProtocol, **settings):
return IntervalDataProvider(dataset, 0, 3, 4, 6, 2, **settings)
@dataproviders.decorators.dataprovider_factory("interval-dict", IntervalDataProvider.settings)
- def interval_dict_dataprovider(self, dataset: "DatasetInstance", **settings):
+ def interval_dict_dataprovider(self, dataset: DatasetProtocol, **settings):
settings["named_columns"] = True
return self.interval_dataprovider(dataset, **settings)
@@ -1073,7 +1074,7 @@ class Gff3(Gff):
"""Initialize datatype, by adding GBrowse display app"""
Gff.__init__(self, **kwd)
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
self.set_attribute_metadata(dataset)
i = 0
with compression_utils.get_fileobj(dataset.file_name) as in_fh:
@@ -1289,7 +1290,7 @@ class Wiggle(Tabular, _RemoteCallMixin):
self.add_display_app("gbrowse", "display in Gbrowse", "as_gbrowse_display_file", "gbrowse_links")
def get_estimated_display_viewport(
- self, dataset: "DatasetInstance"
+ self, dataset: DatasetProtocol
) -> Tuple[Optional[str], Optional[str], Optional[str]]:
"""Return a chrom, start, stop tuple for viewing a file."""
viewport_feature_count = 100 # viewport should check at least 100 features; excludes comment lines
@@ -1355,7 +1356,7 @@ class Wiggle(Tabular, _RemoteCallMixin):
log.exception("Unexpected error")
return (None, None, None) # could not determine viewport
- def gbrowse_links(self, dataset: "DatasetInstance", type: str, app, base_url: str) -> List:
+ def gbrowse_links(self, dataset: DatasetProtocol, type: str, app, base_url: str) -> List:
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport(dataset)
if chrom is not None:
@@ -1368,7 +1369,7 @@ class Wiggle(Tabular, _RemoteCallMixin):
ret_val.append((site_name, link))
return ret_val
- def ucsc_links(self, dataset: "DatasetInstance", type: str, app, base_url: str) -> List:
+ def ucsc_links(self, dataset: DatasetProtocol, type: str, app, base_url: str) -> List:
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport(dataset)
if chrom is not None:
@@ -1381,11 +1382,11 @@ class Wiggle(Tabular, _RemoteCallMixin):
ret_val.append((site_name, link))
return ret_val
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
"""Returns formated html of peek"""
return self.make_html_table(dataset, skipchars=["track", "#"])
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
max_data_lines = None
i = 0
for i, line in enumerate(open(dataset.file_name)): # noqa: B007
@@ -1443,12 +1444,12 @@ class Wiggle(Tabular, _RemoteCallMixin):
# ------------- Dataproviders
@dataproviders.decorators.dataprovider_factory("wiggle", WiggleDataProvider.settings)
- def wiggle_dataprovider(self, dataset: "DatasetInstance", **settings) -> WiggleDataProvider:
+ def wiggle_dataprovider(self, dataset: DatasetProtocol, **settings) -> WiggleDataProvider:
dataset_source = DatasetDataProvider(dataset)
return WiggleDataProvider(dataset_source, **settings)
@dataproviders.decorators.dataprovider_factory("wiggle-dict", WiggleDataProvider.settings)
- def wiggle_dict_dataprovider(self, dataset: "DatasetInstance", **settings) -> WiggleDataProvider:
+ def wiggle_dict_dataprovider(self, dataset: DatasetProtocol, **settings) -> WiggleDataProvider:
dataset_source = DatasetDataProvider(dataset)
settings["named_columns"] = True
return WiggleDataProvider(dataset_source, **settings)
@@ -1466,16 +1467,16 @@ class CustomTrack(Tabular):
Tabular.__init__(self, **kwd)
self.add_display_app("ucsc", "display at UCSC", "as_ucsc_display_file", "ucsc_links")
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
Tabular.set_meta(self, dataset, overwrite=overwrite, skip=1)
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
"""Returns formated html of peek"""
return self.make_html_table(dataset, skipchars=["track", "#"])
def get_estimated_display_viewport(
self,
- dataset: "DatasetInstance",
+ dataset: DatasetProtocol,
chrom_col: Optional[int] = None,
start_col: Optional[int] = None,
end_col: Optional[int] = None,
@@ -1537,7 +1538,7 @@ class CustomTrack(Tabular):
log.exception("Unexpected error")
return (None, None, None) # could not determine viewport
- def ucsc_links(self, dataset: "DatasetInstance", type: str, app, base_url: str) -> List:
+ def ucsc_links(self, dataset: DatasetProtocol, type: str, app, base_url: str) -> List:
ret_val = []
chrom, start, stop = self.get_estimated_display_viewport(dataset)
if chrom is not None:
@@ -1782,7 +1783,7 @@ class IntervalTabix(Interval):
# Ideally the tabix_index would be regenerated when the metadataElements are updated
def set_meta(
self,
- dataset: "DatasetInstance",
+ dataset: DatasetProtocol,
overwrite: bool = True,
first_line_is_header: bool = False,
metadata_tmp_files_dir: Optional[str] = None,
diff --git a/lib/galaxy/datatypes/isa.py b/lib/galaxy/datatypes/isa.py
index 6b5f81687d2..38edcd22dfd 100644
--- a/lib/galaxy/datatypes/isa.py
+++ b/lib/galaxy/datatypes/isa.py
@@ -26,21 +26,19 @@ from isatools import (
from markupsafe import escape
from galaxy import util
-from galaxy.datatypes.data import (
- Data,
- GeneratePrimaryFileDataset,
+from galaxy.datatypes._protocols import (
+ DatasetHasHidProtocol,
+ DatasetProtocol,
+ HasExtraFilesAndMetadata,
+ HasExtraFilesPath,
)
+from galaxy.datatypes.data import Data
from galaxy.util.compression_utils import CompressedFile
from galaxy.util.sanitize_html import sanitize_html
if TYPE_CHECKING:
from isatools.model import Investigation
- from galaxy.model import (
- DatasetInstance,
- HistoryDatasetAssociation,
- )
-
# CONSTANTS {{{1
################################################################
@@ -92,7 +90,7 @@ class _Isa(Data):
# Get ISA folder path {{{2
################################################################
- def _get_isa_folder_path(self, dataset: "DatasetInstance") -> str:
+ def _get_isa_folder_path(self, dataset: HasExtraFilesPath) -> str:
isa_folder = dataset.extra_files_path
if not isa_folder:
raise Exception("Unvalid dataset object, or no extra files path found for this dataset.")
@@ -101,7 +99,7 @@ class _Isa(Data):
# Get main file {{{2
################################################################
- def _get_main_file(self, dataset: "DatasetInstance") -> Optional[str]:
+ def _get_main_file(self, dataset: HasExtraFilesPath) -> Optional[str]:
"""Get the main file of the ISA archive. Either the investigation file i_*.txt for ISA-Tab, or the JSON file for ISA-JSON."""
main_file = None
@@ -127,7 +125,7 @@ class _Isa(Data):
# Get investigation {{{2
################################################################
- def _get_investigation(self, dataset: "DatasetInstance") -> Optional["Investigation"]:
+ def _get_investigation(self, dataset: HasExtraFilesPath) -> Optional["Investigation"]:
"""Create a contained instance specific to the exact ISA type (Tab or Json).
We will use it to parse and access information from the archive."""
@@ -164,7 +162,7 @@ class _Isa(Data):
# Set peek {{{2
################################################################
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
"""Set the peek and blurb text. Get first lines of the main file and set it as the peek."""
main_file = self._get_main_file(dataset)
@@ -190,7 +188,7 @@ class _Isa(Data):
# Display peek {{{2
################################################################
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
"""Create the HTML table used for displaying peek, from the peek text found by set_peek() method."""
out = ['']
@@ -211,7 +209,7 @@ class _Isa(Data):
# Generate primary file {{{2
################################################################
- def generate_primary_file(self, dataset: GeneratePrimaryFileDataset) -> str:
+ def generate_primary_file(self, dataset: HasExtraFilesAndMetadata) -> str:
"""Generate the primary file. It is an HTML file containing description of the composite dataset
as well as a list of the composite files that it contains."""
@@ -270,7 +268,7 @@ class _Isa(Data):
def display_data(
self,
trans,
- dataset: "HistoryDatasetAssociation",
+ dataset: DatasetHasHidProtocol,
preview: bool = False,
filename: Optional[str] = None,
to_ext: Optional[str] = None,
diff --git a/lib/galaxy/datatypes/media.py b/lib/galaxy/datatypes/media.py
index 987df7fc928..1b7baf4ddd7 100644
--- a/lib/galaxy/datatypes/media.py
+++ b/lib/galaxy/datatypes/media.py
@@ -6,9 +6,9 @@ from functools import lru_cache
from typing import (
List,
Tuple,
- TYPE_CHECKING,
)
+from galaxy.datatypes._protocols import DatasetProtocol
from galaxy.datatypes.binary import Binary
from galaxy.datatypes.metadata import (
ListParameter,
@@ -16,9 +16,6 @@ from galaxy.datatypes.metadata import (
)
from galaxy.util import which
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
-
@lru_cache(maxsize=128)
def _ffprobe(path):
@@ -75,7 +72,7 @@ class Audio(Binary):
no_value=0,
)
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
if which("ffprobe"):
metadata, streams = ffprobe(dataset.file_name)
@@ -162,7 +159,7 @@ class Video(Binary):
w = h = fps = 0
return w, h, fps
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
if which("ffprobe"):
metadata, streams = ffprobe(dataset.file_name)
(w, h, fps) = self._get_resolution(streams)
@@ -283,7 +280,7 @@ class Wav(Audio):
with wave.open(filename, "rb"):
return True
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""Set the metadata for this dataset from the file contents."""
try:
with wave.open(dataset.dataset.file_name, "rb") as fd:
diff --git a/lib/galaxy/datatypes/microarrays.py b/lib/galaxy/datatypes/microarrays.py
index aa8ba733a1a..1aeea27a6c5 100644
--- a/lib/galaxy/datatypes/microarrays.py
+++ b/lib/galaxy/datatypes/microarrays.py
@@ -1,7 +1,7 @@
import logging
-from typing import TYPE_CHECKING
from galaxy.datatypes import data
+from galaxy.datatypes._protocols import DatasetProtocol
from galaxy.datatypes.binary import Cel # noqa: F401
from galaxy.datatypes.data import get_file_peek
from galaxy.datatypes.metadata import MetadataElement
@@ -11,9 +11,6 @@ from galaxy.datatypes.sniff import (
get_headers,
)
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
-
log = logging.getLogger(__name__)
@@ -80,7 +77,7 @@ class GenericMicroarrayFile(data.Text):
name="block_type", default=0, desc="Type of block", readonly=True, visible=True, optional=True, no_value=0
)
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
if dataset.metadata.block_count == 1:
dataset.blurb = f"{dataset.metadata.file_type} {dataset.metadata.version_number}: Format {dataset.metadata.file_format}, 1 block, {dataset.metadata.number_of_optional_header_records} headers and {dataset.metadata.number_of_data_columns} columns"
@@ -120,7 +117,7 @@ class Gal(GenericMicroarrayFile):
headers = get_headers(file_prefix, sep="\t", count=3)
return "ATF" in headers[0][0] and "GenePix ArrayList" in headers[2][0]
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Set metadata for Gal file.
"""
@@ -162,7 +159,7 @@ class Gpr(GenericMicroarrayFile):
headers = get_headers(file_prefix, sep="\t", count=3)
return "ATF" in headers[0][0] and "GenePix Results" in headers[2][0]
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Set metadata for Gpr file.
"""
diff --git a/lib/galaxy/datatypes/molecules.py b/lib/galaxy/datatypes/molecules.py
index fa2d103353e..69fbc1a4515 100644
--- a/lib/galaxy/datatypes/molecules.py
+++ b/lib/galaxy/datatypes/molecules.py
@@ -5,10 +5,10 @@ from typing import (
Callable,
Dict,
List,
- TYPE_CHECKING,
)
from galaxy.datatypes import metadata
+from galaxy.datatypes._protocols import DatasetProtocol
from galaxy.datatypes.binary import Binary
from galaxy.datatypes.data import (
get_file_peek,
@@ -29,9 +29,6 @@ from galaxy.util import (
unicodify,
)
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
-
# optional import to enhance metadata
try:
from ase import io as ase_io
@@ -72,7 +69,7 @@ class GenericMolFile(Text):
no_value=0,
)
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
if dataset.metadata.number_of_molecules == 1:
dataset.blurb = "1 molecule"
@@ -204,7 +201,7 @@ class GenericMolFile(Text):
class MOL(GenericMolFile):
file_ext = "mol"
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Set the number molecules, in the case of MOL its always one.
"""
@@ -257,7 +254,7 @@ class SDF(GenericMolFile):
break
return False
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Set the number of molecules in dataset.
"""
@@ -340,7 +337,7 @@ class MOL2(GenericMolFile):
break
return False
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Set the number of lines of data in dataset.
"""
@@ -426,7 +423,7 @@ class FPS(GenericMolFile):
else:
return False
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Set the number of lines of data in dataset.
"""
@@ -531,7 +528,7 @@ class OBFS(Binary):
self.add_composite_file("molecule.mol2", optional=True, is_binary=False, description="Molecule File")
self.add_composite_file("molecule.cml", optional=True, is_binary=False, description="Molecule File")
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
"""Set the peek and blurb text."""
if not dataset.dataset.purged:
dataset.peek = "OpenBabel Fastsearch Index"
@@ -540,7 +537,7 @@ class OBFS(Binary):
dataset.peek = "file does not exist"
dataset.blurb = "file purged from disk"
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
"""Create HTML content, used for displaying peek."""
try:
return dataset.peek
@@ -567,7 +564,7 @@ class OBFS(Binary):
class DRF(GenericMolFile):
file_ext = "drf"
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Set the number of lines of data in dataset.
"""
@@ -581,7 +578,7 @@ class PHAR(GenericMolFile):
file_ext = "phar"
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
dataset.peek = get_file_peek(dataset.file_name)
dataset.blurb = "pharmacophore"
@@ -634,7 +631,7 @@ class PDB(GenericMolFile):
else:
return False
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Find Chain_IDs for metadata.
"""
@@ -650,7 +647,7 @@ class PDB(GenericMolFile):
log.error("Error finding chain_ids: %s", unicodify(e))
raise
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
atom_numbers = count_special_lines("^ATOM", dataset.file_name)
hetatm_numbers = count_special_lines("^HETATM", dataset.file_name)
@@ -703,7 +700,7 @@ class PDBQT(GenericMolFile):
else:
return False
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
root_numbers = count_special_lines("^ROOT", dataset.file_name)
branch_numbers = count_special_lines("^BRANCH", dataset.file_name)
@@ -788,7 +785,7 @@ class PQR(GenericMolFile):
else:
return False
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Find Optional Chain_IDs for metadata.
"""
@@ -807,7 +804,7 @@ class PQR(GenericMolFile):
log.error("Error finding chain_ids: %s", unicodify(e))
raise
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
atom_numbers = count_special_lines("^ATOM", dataset.file_name)
hetatm_numbers = count_special_lines("^HETATM", dataset.file_name)
@@ -885,7 +882,7 @@ class Cell(GenericMolFile):
return True
return False
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Find Atom IDs for metadata.
"""
@@ -925,7 +922,7 @@ class Cell(GenericMolFile):
dataset.metadata.is_periodic = bool(pbc.any())
dataset.metadata.lattice_parameters = list(lattice_parameters)
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
dataset.peek = get_file_peek(dataset.file_name)
dataset.info = self.get_dataset_info(dataset.metadata)
@@ -1046,7 +1043,7 @@ class CIF(GenericMolFile):
return False
return False
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Find Atom IDs for metadata.
"""
@@ -1092,7 +1089,7 @@ class CIF(GenericMolFile):
dataset.metadata.is_periodic = is_periodic
dataset.metadata.lattice_parameters = list(lattice_parameters)
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
dataset.peek = get_file_peek(dataset.file_name)
dataset.info = self.get_dataset_info(dataset.metadata)
@@ -1243,7 +1240,7 @@ class XYZ(GenericMolFile):
raise
return blocks
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Find Atom IDs for metadata.
"""
@@ -1290,7 +1287,7 @@ class XYZ(GenericMolFile):
dataset.metadata.is_periodic = is_periodic
dataset.metadata.lattice_parameters = list(lattice_parameters)
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
dataset.peek = get_file_peek(dataset.file_name)
dataset.info = self.get_dataset_info(dataset.metadata)
@@ -1445,7 +1442,7 @@ class ExtendedXYZ(XYZ):
blocks.append({"number_of_atoms": n_atoms, "comment": comment, "atom_data": atoms})
return blocks
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
super().set_peek(dataset)
dataset.blurb = "Extended " + dataset.blurb
@@ -1453,7 +1450,7 @@ class ExtendedXYZ(XYZ):
class grd(Text):
file_ext = "grd"
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
dataset.peek = get_file_peek(dataset.file_name)
dataset.blurb = "grids for docking"
@@ -1465,7 +1462,7 @@ class grd(Text):
class grdtgz(Binary):
file_ext = "grd.tgz"
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
dataset.peek = "binary data"
dataset.blurb = "compressed grids for docking"
@@ -1497,13 +1494,13 @@ class InChI(Tabular):
no_value=0,
)
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Set the number of lines of data in dataset.
"""
dataset.metadata.number_of_molecules = self.count_data_lines(dataset)
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
if dataset.metadata.number_of_molecules == 1:
dataset.blurb = "1 molecule"
@@ -1562,13 +1559,13 @@ class SMILES(Tabular):
no_value=0,
)
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Set the number of lines of data in dataset.
"""
dataset.metadata.number_of_molecules = self.count_data_lines(dataset)
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
if dataset.metadata.number_of_molecules == 1:
dataset.blurb = "1 molecule"
@@ -1598,13 +1595,13 @@ class CML(GenericXml):
no_value=0,
)
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Set the number of lines of data in dataset.
"""
dataset.metadata.number_of_molecules = count_special_lines(r"^\s* None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
if dataset.metadata.number_of_molecules == 1:
dataset.blurb = "1 molecule"
@@ -1762,7 +1759,7 @@ class GRO(GenericMolFile):
return False
return True
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
dataset.peek = get_file_peek(dataset.file_name)
atom_number = int(dataset.peek.split("\n")[1])
diff --git a/lib/galaxy/datatypes/mothur.py b/lib/galaxy/datatypes/mothur.py
index a82771dcd8f..c798e6fd14e 100644
--- a/lib/galaxy/datatypes/mothur.py
+++ b/lib/galaxy/datatypes/mothur.py
@@ -6,9 +6,12 @@ import re
from typing import (
List,
Optional,
- TYPE_CHECKING,
)
+from galaxy.datatypes._protocols import (
+ DatasetProtocol,
+ HasMetadata,
+)
from galaxy.datatypes.data import Text
from galaxy.datatypes.metadata import MetadataElement
from galaxy.datatypes.sniff import (
@@ -20,9 +23,6 @@ from galaxy.datatypes.sniff import (
from galaxy.datatypes.tabular import Tabular
from galaxy.util import unicodify
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
-
log = logging.getLogger(__name__)
@@ -36,7 +36,7 @@ class Otu(Text):
def __init__(self, **kwd):
super().__init__(**kwd)
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Set metadata for Otu files.
@@ -126,7 +126,7 @@ class Sabund(Otu):
"""
super().__init__(**kwd)
- def init_meta(self, dataset: "DatasetInstance", copy_from: Optional["DatasetInstance"] = None) -> None:
+ def init_meta(self, dataset: HasMetadata, copy_from: Optional[HasMetadata] = None) -> None:
super().init_meta(dataset, copy_from=copy_from)
def sniff_prefix(self, file_prefix: FilePrefix) -> bool:
@@ -170,10 +170,10 @@ class GroupAbund(Otu):
def __init__(self, **kwd):
super().__init__(**kwd)
- def init_meta(self, dataset: "DatasetInstance", copy_from: Optional["DatasetInstance"] = None) -> None:
+ def init_meta(self, dataset: HasMetadata, copy_from: Optional[HasMetadata] = None) -> None:
super().init_meta(dataset, copy_from=copy_from)
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, skip: Optional[int] = 1, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, skip: Optional[int] = 1, **kwd) -> None:
super().set_meta(dataset, overwrite=overwrite, **kwd)
# See if file starts with header line
@@ -297,7 +297,7 @@ class AlignCheck(Tabular):
self.column_types = ["str", "int", "int", "int", "int", "int", "int", "int"]
self.comment_lines = 1
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
super().set_meta(dataset, overwrite=overwrite, **kwd)
dataset.metadata.column_names = self.column_names
@@ -351,10 +351,10 @@ class DistanceMatrix(Text):
no_value="?",
)
- def init_meta(self, dataset: "DatasetInstance", copy_from: Optional["DatasetInstance"] = None) -> None:
+ def init_meta(self, dataset: HasMetadata, copy_from: Optional[HasMetadata] = None) -> None:
super().init_meta(dataset, copy_from=copy_from)
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, skip: Optional[int] = 0, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, skip: Optional[int] = 0, **kwd) -> None:
super().set_meta(dataset, overwrite=overwrite, skip=skip, **kwd)
headers = iter_headers(dataset.file_name, sep="\t")
@@ -376,7 +376,7 @@ class LowerTriangleDistanceMatrix(DistanceMatrix):
"""Initialize secondary structure map datatype"""
super().__init__(**kwd)
- def init_meta(self, dataset: "DatasetInstance", copy_from: Optional["DatasetInstance"] = None) -> None:
+ def init_meta(self, dataset: HasMetadata, copy_from: Optional[HasMetadata] = None) -> None:
super().init_meta(dataset, copy_from=copy_from)
def sniff_prefix(self, file_prefix: FilePrefix) -> bool:
@@ -441,7 +441,7 @@ class SquareDistanceMatrix(DistanceMatrix):
def __init__(self, **kwd):
super().__init__(**kwd)
- def init_meta(self, dataset: "DatasetInstance", copy_from: Optional["DatasetInstance"] = None) -> None:
+ def init_meta(self, dataset: HasMetadata, copy_from: Optional[HasMetadata] = None) -> None:
super().init_meta(dataset, copy_from=copy_from)
def sniff_prefix(self, file_prefix: FilePrefix) -> bool:
@@ -507,7 +507,7 @@ class PairwiseDistanceMatrix(DistanceMatrix, Tabular):
self.column_names = ["Sequence", "Sequence", "Distance"]
self.column_types = ["str", "str", "float"]
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, skip: Optional[int] = None, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, skip: Optional[int] = None, **kwd) -> None:
super().set_meta(dataset, overwrite=overwrite, skip=skip, **kwd)
def sniff_prefix(self, file_prefix: FilePrefix) -> bool:
@@ -597,7 +597,7 @@ class Group(Tabular):
def set_meta(
self,
- dataset: "DatasetInstance",
+ dataset: DatasetProtocol,
overwrite: bool = True,
skip: Optional[int] = None,
max_data_lines: Optional[int] = None,
@@ -845,7 +845,7 @@ class CountTable(Tabular):
def set_meta(
self,
- dataset: "DatasetInstance",
+ dataset: DatasetProtocol,
overwrite: bool = True,
skip: Optional[int] = 1,
max_data_lines: Optional[int] = None,
@@ -1057,7 +1057,7 @@ class SffFlow(Tabular):
def set_meta(
self,
- dataset: "DatasetInstance",
+ dataset: DatasetProtocol,
overwrite: bool = True,
skip: Optional[int] = 1,
max_data_lines: Optional[int] = None,
@@ -1072,7 +1072,7 @@ class SffFlow(Tabular):
except Exception as e:
log.warning(f"SffFlow set_meta {e}")
- def make_html_table(self, dataset: "DatasetInstance", skipchars: Optional[List] = None, **kwargs) -> str:
+ def make_html_table(self, dataset: DatasetProtocol, skipchars: Optional[List] = None, **kwargs) -> str:
"""Create HTML table, used for displaying peek"""
skipchars = skipchars or []
try:
diff --git a/lib/galaxy/datatypes/msa.py b/lib/galaxy/datatypes/msa.py
index 6dc5f3e5c03..901709c0f16 100644
--- a/lib/galaxy/datatypes/msa.py
+++ b/lib/galaxy/datatypes/msa.py
@@ -6,9 +6,9 @@ from typing import (
Callable,
Dict,
List,
- TYPE_CHECKING,
)
+from galaxy.datatypes._protocols import DatasetProtocol
from galaxy.datatypes.binary import Binary
from galaxy.datatypes.data import (
get_file_peek,
@@ -25,9 +25,6 @@ from galaxy.util import (
unicodify,
)
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
-
log = logging.getLogger(__name__)
STOCKHOLM_SEARCH_PATTERN = re.compile(r"#\s+STOCKHOLM\s+1\.0")
@@ -57,7 +54,7 @@ class InfernalCM(Text):
no_value=0,
)
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
dataset.peek = get_file_peek(dataset.file_name)
if dataset.metadata.number_of_models == 1:
@@ -81,7 +78,7 @@ class InfernalCM(Text):
"""
return file_prefix.startswith("INFERNAL")
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Set the number of models and the version of CM file in dataset.
"""
@@ -97,7 +94,7 @@ class Hmmer(Text):
edam_data = "data_1364"
edam_format = "format_1370"
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
dataset.peek = get_file_peek(dataset.file_name)
dataset.blurb = "HMMER Database"
@@ -105,7 +102,7 @@ class Hmmer(Text):
dataset.peek = "file does not exist"
dataset.blurb = "file purged from disc"
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
try:
return dataset.peek
except Exception:
@@ -140,7 +137,7 @@ class HmmerPress(Binary):
file_ext = "hmmpress"
composite_type = "basic"
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
"""Set the peek and blurb text."""
if not dataset.dataset.purged:
dataset.peek = "HMMER Binary database"
@@ -149,7 +146,7 @@ class HmmerPress(Binary):
dataset.peek = "file does not exist"
dataset.blurb = "file purged from disk"
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
"""Create HTML content, used for displaying peek."""
try:
return dataset.peek
@@ -184,7 +181,7 @@ class Stockholm_1_0(Text):
no_value=0,
)
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
if dataset.metadata.number_of_models == 1:
dataset.blurb = "1 alignment"
@@ -198,7 +195,7 @@ class Stockholm_1_0(Text):
def sniff_prefix(self, file_prefix: FilePrefix) -> bool:
return file_prefix.search(STOCKHOLM_SEARCH_PATTERN)
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Set the number of models in dataset.
@@ -275,7 +272,7 @@ class MauveXmfa(Text):
no_value=0,
)
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
if dataset.metadata.number_of_models == 1:
dataset.blurb = "1 alignment"
@@ -289,7 +286,7 @@ class MauveXmfa(Text):
def sniff_prefix(self, file_prefix: FilePrefix) -> bool:
return file_prefix.startswith("#FormatVersion Mauve1")
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
dataset.metadata.number_of_models = generic_util.count_special_lines(
"^#Sequence([[:digit:]]+)Entry", dataset.file_name
)
diff --git a/lib/galaxy/datatypes/neo4j.py b/lib/galaxy/datatypes/neo4j.py
index 4c53dccb56a..27fede42da8 100644
--- a/lib/galaxy/datatypes/neo4j.py
+++ b/lib/galaxy/datatypes/neo4j.py
@@ -2,18 +2,15 @@
Neo4j Composite Dataset
"""
import logging
-from typing import TYPE_CHECKING
-from galaxy.datatypes.data import (
- Data,
- GeneratePrimaryFileDataset,
+from galaxy.datatypes._protocols import (
+ DatasetProtocol,
+ HasExtraFilesAndMetadata,
)
+from galaxy.datatypes.data import Data
from galaxy.datatypes.images import Html
from galaxy.datatypes.metadata import MetadataElement
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
-
gal_Log = logging.getLogger(__name__)
verbose = True
@@ -25,7 +22,7 @@ class Neo4j(Html):
stored in extra files path
"""
- def generate_primary_file(self, dataset: GeneratePrimaryFileDataset) -> str:
+ def generate_primary_file(self, dataset: HasExtraFilesAndMetadata) -> str:
"""
This is called only at upload to write the html file
cannot rename the datasets here - they come with the default unfortunately
@@ -48,7 +45,7 @@ class Neo4j(Html):
"""Returns the mime type of the datatype"""
return "text/html"
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
"""Set the peek and blurb text"""
if not dataset.dataset.purged:
dataset.peek = "Neo4j database (multiple files)"
@@ -57,7 +54,7 @@ class Neo4j(Html):
dataset.peek = "file does not exist"
dataset.blurb = "file purged from disk"
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
"""Create HTML content, used for displaying peek."""
try:
return dataset.peek
diff --git a/lib/galaxy/datatypes/ngsindex.py b/lib/galaxy/datatypes/ngsindex.py
index 198fb994b0f..2da0df85d96 100644
--- a/lib/galaxy/datatypes/ngsindex.py
+++ b/lib/galaxy/datatypes/ngsindex.py
@@ -3,15 +3,14 @@ NGS indexes
"""
import logging
import os
-from typing import TYPE_CHECKING
-from galaxy.datatypes.data import GeneratePrimaryFileDataset
+from galaxy.datatypes._protocols import (
+ DatasetProtocol,
+ HasExtraFilesAndMetadata,
+)
from .metadata import MetadataElement
from .text import Html
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
-
log = logging.getLogger(__name__)
@@ -38,14 +37,14 @@ class BowtieIndex(Html):
composite_type = "auto_primary_file"
- def generate_primary_file(self, dataset: GeneratePrimaryFileDataset) -> str:
+ def generate_primary_file(self, dataset: HasExtraFilesAndMetadata) -> str:
"""
This is called only at upload to write the html file
cannot rename the datasets here - they come with the default unfortunately
"""
return "AutoGenerated Primary File for Composite Dataset"
- def regenerate_primary_file(self, dataset: "DatasetInstance") -> None:
+ def regenerate_primary_file(self, dataset: DatasetProtocol) -> None:
"""
cannot do this until we are setting metadata
"""
@@ -62,7 +61,7 @@ class BowtieIndex(Html):
f.write("\n".join(rval))
f.write("\n")
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
dataset.peek = f"Bowtie index file ({dataset.metadata.sequence_space})"
dataset.blurb = f"{dataset.metadata.sequence_space} space"
@@ -70,7 +69,7 @@ class BowtieIndex(Html):
dataset.peek = "file does not exist"
dataset.blurb = "file purged from disk"
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
try:
return dataset.peek
except Exception:
diff --git a/lib/galaxy/datatypes/phylip.py b/lib/galaxy/datatypes/phylip.py
index 551043080cf..5c468fe77eb 100644
--- a/lib/galaxy/datatypes/phylip.py
+++ b/lib/galaxy/datatypes/phylip.py
@@ -10,6 +10,7 @@ Phylip datatype sniffer
from typing import TYPE_CHECKING
from galaxy import util
+from galaxy.datatypes._protocols import DatasetProtocol
from galaxy.datatypes.data import (
get_file_peek,
Text,
@@ -24,8 +25,6 @@ from .metadata import MetadataElement
if TYPE_CHECKING:
from io import StringIO
- from galaxy.model import DatasetInstance
-
@build_sniff_from_prefix
class Phylip(Text):
@@ -39,7 +38,7 @@ class Phylip(Text):
name="sequences", default=0, desc="Number of sequences", readonly=True, visible=False, optional=True, no_value=0
)
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Set the number of sequences and the number of data lines in dataset.
"""
@@ -49,7 +48,7 @@ class Phylip(Text):
except Exception:
raise Exception("Header does not correspond to PHYLIP header.")
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
dataset.peek = get_file_peek(dataset.file_name)
if dataset.metadata.sequences:
diff --git a/lib/galaxy/datatypes/plant_tribes.py b/lib/galaxy/datatypes/plant_tribes.py
index a0528fe0e3d..929a648deb5 100644
--- a/lib/galaxy/datatypes/plant_tribes.py
+++ b/lib/galaxy/datatypes/plant_tribes.py
@@ -1,7 +1,7 @@
import logging
import re
-from typing import TYPE_CHECKING
+from galaxy.datatypes._protocols import DatasetProtocol
from galaxy.datatypes.data import (
get_file_peek,
Text,
@@ -15,9 +15,6 @@ from galaxy.datatypes.sniff import (
from galaxy.datatypes.tabular import Tabular
from galaxy.util import nice_size
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
-
log = logging.getLogger(__name__)
@@ -25,13 +22,13 @@ log = logging.getLogger(__name__)
class Smat(Text):
file_ext = "smat"
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
try:
return dataset.peek
except Exception:
return f"ESTScan scores matrices ({nice_size(dataset.get_size())})"
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
dataset.peek = get_file_peek(dataset.file_name)
dataset.blurb = "ESTScan scores matrices"
@@ -97,13 +94,13 @@ class PlantTribesKsComponents(Tabular):
no_value=0,
)
- def display_peek(self, dataset: "DatasetInstance") -> str:
+ def display_peek(self, dataset: DatasetProtocol) -> str:
try:
return dataset.peek
except Exception:
return f"Significant components in the Ks distribution ({nice_size(dataset.get_size())})"
- def set_meta(self, dataset: "DatasetInstance", overwrite: bool = True, **kwd) -> None:
+ def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd) -> None:
"""
Set the number of significant components in the Ks distribution.
The dataset will always be on the order of less than 10 lines.
@@ -125,7 +122,7 @@ class PlantTribesKsComponents(Tabular):
if len(significant_components) > 0:
dataset.metadata.number_comp = max(significant_components)
- def set_peek(self, dataset: "DatasetInstance", **kwd) -> None:
+ def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
if not dataset.dataset.purged:
dataset.peek = get_file_peek(dataset.file_name)
if dataset.metadata.number_comp == 1:
diff --git a/lib/galaxy/datatypes/proteomics.py b/lib/galaxy/datatypes/proteomics.py
index 7bda20361aa..dae3f7c5f81 100644
--- a/lib/galaxy/datatypes/proteomics.py
+++ b/lib/galaxy/datatypes/proteomics.py
@@ -7,15 +7,15 @@ from typing import (
IO,
List,
Optional,
- TYPE_CHECKING,
)
from galaxy.datatypes import data
-from galaxy.datatypes.binary import Binary
-from galaxy.datatypes.data import (
- GeneratePrimaryFileDataset,
- Text,
+from galaxy.datatypes._protocols import (
+ DatasetProtocol,
+ HasExtraFilesAndMetadata,
)
+from galaxy.datatypes.binary import Binary
+from galaxy.datatypes.data import Text
from galaxy.datatypes.sequence import Sequence
from galaxy.datatypes.sniff import (
build_sniff_from_prefix,
@@ -28,9 +28,6 @@ from galaxy.datatypes.tabular import (
from galaxy.datatypes.xml import GenericXml
from galaxy.util import nice_size
-if TYPE_CHECKING:
- from galaxy.model import DatasetInstance
-
log = logging.getLogger(__name__)
@@ -58,7 +55,7 @@ class Wiff(Binary):
is_binary=True,
)
- def generate_primary_file(self, dataset: GeneratePrimaryFileDataset) -> str:
+ def generate_primary_file(self, dataset: HasExtraFilesAndMetadata) -> str:
rval = ["Wiff Composite Dataset "]
rval.append("This composite dataset is composed of the following files: