diff --git a/.ci/flake8_ignorelist.txt b/.ci/flake8_ignorelist.txt index f1cffc4f5952..d4ced27b5b8d 100644 --- a/.ci/flake8_ignorelist.txt +++ b/.ci/flake8_ignorelist.txt @@ -11,6 +11,7 @@ database doc/build eggs lib/galaxy/web/proxy/js/node_modules +lib/tool_shed/test/test_data/repos static/maps static/scripts test/functional/tools/cwl_tools/v1.?/ diff --git a/.flake8 b/.flake8 index fe463fb975e1..3e086e9d1287 100644 --- a/.flake8 +++ b/.flake8 @@ -7,3 +7,4 @@ # W503 is line breaks before binary operators, which has been reversed in PEP 8. # D** are docstring linting - which we mostly ignore except D302. (Hopefully we will solve more over time). ignore = B008,E203,E402,E501,W503,D100,D101,D102,D103,D104,D105,D106,D107,D200,D201,D202,D204,D205,D206,D207,D208,D209,D210,D211,D300,D301,D400,D401,D402,D403,D412,D413 +exclude = lib/tool_shed/test/test_data/repos diff --git a/.github/workflows/converter_tests.yaml b/.github/workflows/converter_tests.yaml index d4833d376ab7..359edf8b0d9e 100644 --- a/.github/workflows/converter_tests.yaml +++ b/.github/workflows/converter_tests.yaml @@ -68,7 +68,7 @@ jobs: - name: Run tests run: | mapfile -t TOOL_ARRAY < tool_list.txt - planemo test --galaxy_python_version ${{ matrix.python-version }} --galaxy_root 'galaxy root' "${TOOL_ARRAY[@]}" + planemo test --biocontainers --galaxy_python_version ${{ matrix.python-version }} --galaxy_root 'galaxy root' "${TOOL_ARRAY[@]}" - uses: actions/upload-artifact@v3 if: failure() with: diff --git a/.github/workflows/dependencies.yaml b/.github/workflows/dependencies.yaml index 7d98dfaa1014..0d02ae2f1234 100644 --- a/.github/workflows/dependencies.yaml +++ b/.github/workflows/dependencies.yaml @@ -14,12 +14,12 @@ jobs: - uses: actions/checkout@v3 - 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') }} + # poetry requires Python >=3.8, but lint requirements currently need + # to be generated with `pip freeze`` on the oldest Python version + # supported by Galaxy. + python-version: | + ${{ matrix.python-version }} + 3.8 - name: Update dependencies run: | python -m venv .venv diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 575a6a0f9a31..590f93cd5e2c 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -20,6 +20,7 @@ jobs: name: Test runs-on: ubuntu-latest strategy: + fail-fast: false matrix: python-version: ['3.7', '3.11'] env: diff --git a/.github/workflows/lint_openapi_schema.yml b/.github/workflows/lint_openapi_schema.yml index c41e1489d933..02cfb35e640d 100644 --- a/.github/workflows/lint_openapi_schema.yml +++ b/.github/workflows/lint_openapi_schema.yml @@ -51,6 +51,9 @@ jobs: - name: Build typescript schema run: make update-client-api-schema working-directory: 'galaxy root' + - name: Diff... + run: git diff + working-directory: 'galaxy root' - name: Check for changes run: | if [[ `git status --porcelain` ]]; then diff --git a/.github/workflows/setup_selenium.yaml b/.github/workflows/setup_selenium.yaml index 4bbf7ddcfa2c..3e4e9fd4f9a3 100644 --- a/.github/workflows/setup_selenium.yaml +++ b/.github/workflows/setup_selenium.yaml @@ -5,4 +5,4 @@ jobs: runs-on: ubuntu-latest steps: - name: Install chromedriver - uses: mvdbeek/setup-chromedriver@chromedriver_puppeteer + uses: nanasess/setup-chromedriver@v2 diff --git a/.github/workflows/test_galaxy_packages_for_pulsar.yaml b/.github/workflows/test_galaxy_packages_for_pulsar.yaml new file mode 100644 index 000000000000..7f5f5499bc4c --- /dev/null +++ b/.github/workflows/test_galaxy_packages_for_pulsar.yaml @@ -0,0 +1,44 @@ +name: Test Galaxy packages for Pulsar +on: + push: + paths-ignore: + - 'client/**' + - 'doc/**' + - 'lib/galaxy_test/selenium/**' + pull_request: + paths-ignore: + - 'client/**' + - 'doc/**' + - 'lib/galaxy_test/selenium/**' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + test: + name: Test + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ['3.7'] + steps: + - uses: actions/checkout@v3 + with: + path: 'galaxy root' + - 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('galaxy root/requirements.txt') }} + - name: Install Apptainer's singularity + uses: eWaterCycle/setup-apptainer@v2 + - name: Install ffmpeg + run: sudo apt-get update && sudo apt-get -y install ffmpeg + - name: Install tox + run: pip install tox + - name: Run tests + run: tox -e test_galaxy_packages_for_pulsar + working-directory: 'galaxy root' diff --git a/.github/workflows/toolshed.yaml b/.github/workflows/toolshed.yaml index 64f148c0f346..4e3a2b2cac8c 100644 --- a/.github/workflows/toolshed.yaml +++ b/.github/workflows/toolshed.yaml @@ -19,8 +19,11 @@ jobs: name: Test runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - python-version: ['3.7'] + python-version: ['3.7', '3.11'] + shed-api: ['v1', 'v2'] + test-install-client: ['galaxy_api', 'standalone'] services: postgres: image: postgres:13 @@ -34,6 +37,11 @@ jobs: - uses: actions/checkout@v3 with: path: 'galaxy root' + - uses: actions/setup-node@v3 + with: + node-version: '18.12.1' + cache: 'yarn' + cache-dependency-path: 'galaxy root/client/yarn.lock' - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -52,11 +60,30 @@ jobs: with: path: 'galaxy root/.venv' key: gxy-venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('galaxy root/requirements.txt') }}-toolshed + - name: Install dependencies + run: ./scripts/common_startup.sh --skip-client-build + working-directory: 'galaxy root' + - name: Build Frontend + run: | + . .venv/bin/activate + cd lib/tool_shed/webapp/frontend + yarn + make client + working-directory: 'galaxy root' + - name: Install playwright + run: | + . .venv/bin/activate + playwright install + working-directory: 'galaxy root' - name: Run tests - run: './run_tests.sh -toolshed' + run: ./run_tests.sh -toolshed + env: + TOOL_SHED_TEST_INSTALL_CLIENT: ${{ matrix.test-install-client }} + TOOL_SHED_API_VERSION: ${{ matrix.shed-api }} + TOOL_SHED_TEST_BROWSER: ${{ matrix.shed-api == 'v1' && 'twill' || 'playwright' }} working-directory: 'galaxy root' - uses: actions/upload-artifact@v3 if: failure() with: - name: Toolshed test results (${{ matrix.python-version }}) + name: Toolshed test results (${{ matrix.python-version }}, ${{ matrix.shed-api }}, ${{ matrix.test-install-client }}) path: 'galaxy root/run_toolshed_tests.html' diff --git a/.isort.cfg b/.isort.cfg index c99585f7f278..ca490cd2930a 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -10,5 +10,5 @@ profile=black reverse_relative=true skip_gitignore=true # Make isort run faster by skipping database -skip_glob=database/* +skip_glob=database/*,lib/tool_shed/test/test_data/repos/* src_paths=lib diff --git a/.redocly.lint-ignore.yaml b/.redocly.lint-ignore.yaml index 01997597cebb..a09ad94d3248 100644 --- a/.redocly.lint-ignore.yaml +++ b/.redocly.lint-ignore.yaml @@ -14,3 +14,6 @@ _schema.yaml: #/paths/~1api~1histories~1{history_id}~1contents~1{history_content_id}~1metadata_file - '#/paths/~1api~1histories~1{history_id}~1contents~1{id}~1validate' - '#/paths/~1api~1histories~1{history_id}~1contents~1{type}s~1{id}' +_shed_schema.yaml: + no-empty-servers: + - '#/openapi' diff --git a/.vscode/shed.code-snippets b/.vscode/shed.code-snippets new file mode 100644 index 000000000000..f90a37c71313 --- /dev/null +++ b/.vscode/shed.code-snippets @@ -0,0 +1,41 @@ +{ + "shedcomp": { + "prefix": "shed_component", + "body": [ + "", + "" + ], + "description": "outline of a tool shed component" + }, + "shedpage": { + "prefix": "shed_page", + "body": [ + "", + "" + ], + "description": "outline of a tool shed page" + }, + "shedfetcher": { + "prefix": "shed_fetcher", + "body": [ + "import { fetcher } from \"@/schema\"", + "const fetcher = fetcher.path(\"$1\").method(\"get\").create()" + ], + "description": "Import shed fetcher and instantiate with a path" + }, + "shedrouter": { + "prefix": "shed_router", + "body": [ + "import router from \"@/router\"" + ] + } +} \ No newline at end of file diff --git a/Makefile b/Makefile index b6164b5b44ec..f20548df435e 100644 --- a/Makefile +++ b/Makefile @@ -139,7 +139,7 @@ release-bootstrap-history: ## bootstrap history for a new release update-lint-requirements: ./lib/galaxy/dependencies/update_lint_requirements.sh -update-dependencies: update-lint-requirements ## update pinned and dev dependencies +update-dependencies: update-lint-requirements ## update pinned, dev and typecheck dependencies $(IN_VENV) ./lib/galaxy/dependencies/update.sh $(CWL_TARGETS): @@ -182,17 +182,22 @@ endif build-api-schema: $(IN_VENV) python scripts/dump_openapi_schema.py _schema.yaml + $(IN_VENV) python scripts/dump_openapi_schema.py --app shed _shed_schema.yaml remove-api-schema: rm _schema.yaml + rm _shed_schema.yaml 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 + $(IN_VENV) cd client && node openapi_to_schema.mjs ../_shed_schema.yaml > ../lib/tool_shed/webapp/frontend/src/schema/schema.ts && npx prettier --write ../lib/tool_shed/webapp/frontend/src/schema/schema.ts $(MAKE) remove-api-schema lint-api-schema: build-api-schema $(IN_VENV) npx --yes @redocly/cli lint _schema.yaml + $(IN_VENV) npx --yes @redocly/cli lint _shed_schema.yaml $(IN_VENV) codespell -I .ci/ignore-spelling.txt _schema.yaml + $(IN_VENV) codespell -I .ci/ignore-spelling.txt _shed_schema.yaml $(MAKE) remove-api-schema update-navigation-schema: client-node-deps @@ -210,7 +215,10 @@ client-production: client-node-deps ## Rebuild client-side artifacts for a produ client-production-maps: client-node-deps ## Rebuild client-side artifacts for a production deployment with sourcemaps. $(IN_VENV) cd client && $(NODE_ENV) yarn run build-production-maps -client-format: client-node-deps ## Reformat client code +client-lint-autofix: client-node-deps ## Automatically fix linting errors in client code + $(IN_VENV) cd client && yarn run eslint --quiet --fix + +client-format: client-node-deps client-lint-autofix ## Reformat client code, ensures autofixes are applied first $(IN_VENV) cd client && yarn run format client-dev-server: client-node-deps ## Starts a webpack dev server for client development (HMR enabled) diff --git a/client/package.json b/client/package.json index 609af124ea4f..435e8e847690 100644 --- a/client/package.json +++ b/client/package.json @@ -15,7 +15,8 @@ ], "browserslist": [ "defaults", - "not op_mini all" + "not op_mini all", + "not ios_saf <= 15.0" ], "resolutions": { "chokidar": "3.5.3", diff --git a/client/src/bundleToolshed.js b/client/src/bundleToolshed.js deleted file mode 100644 index c2510b540b17..000000000000 --- a/client/src/bundleToolshed.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * The toolshed list of globals we expose in window.bundleToolshed used by Toolshed makos. - */ - -/* jquery and _ are exposed via expose-loader while several external plugins rely on these */ -import $ from "jquery"; // eslint-disable-line no-unused-vars -import _ from "underscore"; // eslint-disable-line no-unused-vars - -export { default as LegacyGridView } from "legacy/grid/grid-view"; -export { default as store } from "storemodern"; -export { default as ToolshedGroups } from "toolshed/toolshed.groups"; diff --git a/client/src/components/AboutGalaxy.vue b/client/src/components/AboutGalaxy.vue index 5f065b718d65..4c40f4978d06 100644 --- a/client/src/components/AboutGalaxy.vue +++ b/client/src/components/AboutGalaxy.vue @@ -12,7 +12,7 @@ import ExternalLink from "@/components/ExternalLink.vue"; import License from "@/components/License/License.vue"; import UtcDate from "@/components/UtcDate.vue"; -const { config, isLoaded } = useConfig(); +const { config, isConfigLoaded } = useConfig(); const clientBuildDate = __buildTimestamp__ || new Date().toISOString(); const apiDocsLink = `${getAppRoot()}api/docs`; @@ -27,7 +27,7 @@ const versionUserDocumentationUrl = computed(() => { @@ -62,12 +60,13 @@ import { faBars, faCog, faDatabase, faTable, faUser } from "@fortawesome/free-so import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import axios from "axios"; import BootstrapVue from "bootstrap-vue"; -import ConfigProvider from "components/providers/ConfigProvider"; import { mapState } from "pinia"; import { prependPath } from "utils/redirect"; import { errorMessageAsString } from "utils/simple-error"; import Vue from "vue"; +import { useConfig } from "@/composables/config"; +import { useCollectionAttributesStore } from "@/stores/collectionAttributesStore"; import { useHistoryStore } from "@/stores/historyStore"; import { DatatypesProvider, DbKeyProvider, SuitableConvertersProvider } from "../../providers"; @@ -87,20 +86,22 @@ export default { FontAwesomeIcon, DbKeyProvider, SuitableConvertersProvider, - ConfigProvider, ChangeDatatypeTab, DatatypesProvider, LoadingSpan, }, props: { - collection_id: { + collectionId: { type: String, required: true, }, }, + setup() { + const { config, isConfigLoaded } = useConfig(true); + return { config, isConfigLoaded }; + }, data: function () { return { - attributesData: {}, errorMessage: null, jobError: null, noQuotaIncrease: true, @@ -113,6 +114,10 @@ export default { }, computed: { ...mapState(useHistoryStore, ["currentHistoryId"]), + ...mapState(useCollectionAttributesStore, ["getAttributes"]), + attributesData() { + return this.getAttributes(this.collectionId); + }, databaseKeyFromElements: function () { return this.attributesData.dbkey; }, @@ -120,23 +125,12 @@ export default { return this.attributesData.extension; }, }, - created() { - this.getCollectionDataAndAttributes(); - }, methods: { updateInfoMessage: function (strMessage) { this.infoMessage = strMessage; }, - getCollectionDataAndAttributes: async function () { - let attributesGet = this.$store.getters.getCollectionAttributes(this.collection_id); - if (attributesGet == null) { - await this.$store.dispatch("fetchCollectionAttributes", this.collection_id); - attributesGet = this.$store.getters.getCollectionAttributes(this.collection_id); - } - this.attributesData = attributesGet; - }, clickedSave: function (attribute, newValue) { - const url = prependPath(`/api/dataset_collections/${this.collection_id}/copy`); + const url = prependPath(`/api/dataset_collections/${this.collectionId}/copy`); const data = {}; if (attribute == "dbkey") { data["dbkey"] = newValue.id; @@ -151,7 +145,7 @@ export default { const url = prependPath(`/api/tools/${selectedConverter.tool_id}/convert`); const data = { src: "hdca", - id: this.collection_id, + id: this.collectionId, source_type: selectedConverter.original_type, target_type: selectedConverter.target_type, }; @@ -164,7 +158,7 @@ export default { items: [ { history_content_type: "dataset_collection", - id: this.collection_id, + id: this.collectionId, }, ], params: { diff --git a/client/src/components/Common/AsyncButton.vue b/client/src/components/Common/AsyncButton.vue index f9f2691cd39c..2d6ccd4881e7 100644 --- a/client/src/components/Common/AsyncButton.vue +++ b/client/src/components/Common/AsyncButton.vue @@ -29,6 +29,11 @@ const props = defineProps({ required: false, default: "link", }, + disabled: { + type: Boolean, + required: false, + default: false, + }, }); async function onClick() { @@ -44,7 +49,7 @@ async function onClick() { :title="title" :size="size" :variant="variant" - :disabled="loading" + :disabled="loading || disabled" @click="onClick"> diff --git a/client/src/components/Common/ContextMenu.vue b/client/src/components/Common/ContextMenu.vue index 9ad752806767..970ed8872580 100644 --- a/client/src/components/Common/ContextMenu.vue +++ b/client/src/components/Common/ContextMenu.vue @@ -33,7 +33,13 @@ watch( - + Search string too short! No histories found. -
+
+ :max-visible-tags="10" + @tag-click="setFilterValue('tag', $event)" />
const scrolledLeft = computed(() => !isScrollable.value || arrived.left); const scrolledRight = computed(() => !isScrollable.value || arrived.right); + +const showDropZone = ref(false); +const historyPickerText = computed(() => + showDropZone.value ? localize("Create new history with this item") : localize("Select histories") +); +const processingDrop = ref(false); +async function onDrop(evt: any) { + if (processingDrop.value) { + showDropZone.value = false; + return; + } + processingDrop.value = true; + showDropZone.value = false; + let data: any; + try { + data = JSON.parse(evt.dataTransfer.getData("text"))[0]; + } catch (error) { + // this was not a valid object for this dropzone, ignore + } + if (data) { + await historyStore.createNewHistory(); + const currentHistoryId = historyStore.currentHistoryId; + const dataSource = data.history_content_type === "dataset" ? "hda" : "hdca"; + if (currentHistoryId) { + await copyDataset(data.id, currentHistoryId, data.history_content_type, dataSource) + .then(() => { + if (data.history_content_type === "dataset") { + Toast.info(localize("Dataset copied to new history")); + } else { + Toast.info(localize("Collection copied to new history")); + } + historyStore.loadHistoryById(currentHistoryId); + }) + .catch((error) => { + Toast.error(error); + }); + historyStore.pinHistory(currentHistoryId); + } + processingDrop.value = false; + } +}