diff --git a/.ci/jenkins/api-py3 b/.ci/jenkins/api-py3 new file mode 120000 index 00000000000..9e5bfb42d23 --- /dev/null +++ b/.ci/jenkins/api-py3 @@ -0,0 +1 @@ +api \ No newline at end of file diff --git a/.ci/jenkins/api-py3/run_tests.sh b/.ci/jenkins/api-py3/run_tests.sh deleted file mode 100755 index 24790d0b720..00000000000 --- a/.ci/jenkins/api-py3/run_tests.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -./run_tests.sh --dockerize --python3 --db postgres --clean_pyc --skip_flakey_fails -api "$@" diff --git a/.ci/jenkins/framework-py3 b/.ci/jenkins/framework-py3 new file mode 120000 index 00000000000..e3939d229ce --- /dev/null +++ b/.ci/jenkins/framework-py3 @@ -0,0 +1 @@ +framework \ No newline at end of file diff --git a/.ci/jenkins/framework-py3/run_tests.sh b/.ci/jenkins/framework-py3/run_tests.sh deleted file mode 100755 index f8bfe97335d..00000000000 --- a/.ci/jenkins/framework-py3/run_tests.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -./run_tests.sh --dockerize --python3 --db postgres --clean_pyc --framework "$@" diff --git a/.ci/jenkins/integration-py3 b/.ci/jenkins/integration-py3 new file mode 120000 index 00000000000..c0bfe0d4f94 --- /dev/null +++ b/.ci/jenkins/integration-py3 @@ -0,0 +1 @@ +integration \ No newline at end of file diff --git a/.ci/jenkins/integration-py3/run_tests.sh b/.ci/jenkins/integration-py3/run_tests.sh deleted file mode 100755 index ffc6c87e98c..00000000000 --- a/.ci/jenkins/integration-py3/run_tests.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -DOCKER_RUN_EXTRA_ARGS="--privileged" ./run_tests.sh --dockerize --python3 --db postgres --clean_pyc --integration "$@" diff --git a/.ci/jenkins/main-tools-py3 b/.ci/jenkins/main-tools-py3 new file mode 120000 index 00000000000..49589fdd3a8 --- /dev/null +++ b/.ci/jenkins/main-tools-py3 @@ -0,0 +1 @@ +main-tools \ No newline at end of file diff --git a/.ci/jenkins/main-tools-py3/run_tests.sh b/.ci/jenkins/main-tools-py3/run_tests.sh deleted file mode 100755 index 442b15cdf2d..00000000000 --- a/.ci/jenkins/main-tools-py3/run_tests.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -./run_tests.sh --dockerize --python3 --db postgres --clean_pyc -main "$@" diff --git a/.ci/jenkins/selenium-compose/run_galaxy.bash b/.ci/jenkins/selenium-compose/run_galaxy.bash index aaf10b77401..c16901324c1 100755 --- a/.ci/jenkins/selenium-compose/run_galaxy.bash +++ b/.ci/jenkins/selenium-compose/run_galaxy.bash @@ -20,8 +20,7 @@ virtualenv "$GALAXY_VIRTUAL_ENV" chown -R "$GALAXY_TEST_UID:$GALAXY_TEST_UID" "$GALAXY_VIRTUAL_ENV" cd /galaxy -HOME=/galaxy -sudo -E -u "#${GALAXY_TEST_UID}" ./scripts/common_startup.sh || { echo "common_startup.sh failed"; exit 1; } +sudo -E -H -u "#${GALAXY_TEST_UID}" ./scripts/common_startup.sh || { echo "common_startup.sh failed"; exit 1; } echo "Waiting for postgres to become available" while ! nc -z postgres 5432; @@ -34,7 +33,7 @@ echo "Creating postgres database for Galaxy" createdb -w -U postgres -h postgres galaxy echo "Starting and waiting for Galaxy daemon(s)" -sudo -E -u "#${GALAXY_TEST_UID}" GALAXY_RUN_ALL=1 bash "$GALAXY_ROOT/run.sh" --daemon --wait +sudo -E -H -u "#${GALAXY_TEST_UID}" GALAXY_RUN_ALL=1 bash "$GALAXY_ROOT/run.sh" --daemon --wait echo "Galaxy daemon ready, monitoring Galaxy logs" tail -f "$GALAXY_ROOT/main.log" diff --git a/.ci/jenkins/selenium-py3 b/.ci/jenkins/selenium-py3 new file mode 120000 index 00000000000..954f0db0bc0 --- /dev/null +++ b/.ci/jenkins/selenium-py3 @@ -0,0 +1 @@ +selenium \ No newline at end of file diff --git a/.ci/jenkins/selenium-py3/run_tests.sh b/.ci/jenkins/selenium-py3/run_tests.sh deleted file mode 100755 index 827151835f6..00000000000 --- a/.ci/jenkins/selenium-py3/run_tests.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# Enable retries on tests to reduce chances of transient failures. -: ${GALAXY_TEST_SELENIUM_RETRIES:=1} - -# If in Jenkins environment, use it for artifacts. -if [ -n "$BUILD_NUMBER" ]; -then - : ${GALAXY_TEST_ERRORS_DIRECTORY:=${BUILD_NUMBER}-test-errors} - : ${GALAXY_TEST_SCREENSHOTS_DIRECTORY:=${BUILD_NUMBER}-test-screenshots} -else - : ${GALAXY_TEST_ERRORS_DIRECTORY:=database/test-errors} - : ${GALAXY_TEST_SCREENSHOTS_DIRECTORY:=database/test-screenshots} -fi - -mkdir -p "$GALAXY_TEST_ERRORS_DIRECTORY" -mkdir -p "$GALAXY_TEST_SCREENSHOTS_DIRECTORY" - -mkdir -p ~/.jenkins-yarn-cache -YARN_CACHE_FOLDER=~/.jenkins-yarn-cache - -# Start Selenium server in the test Docker container. -DOCKER_RUN_EXTRA_ARGS="${DOCKER_RUN_EXTRA_ARGS} --shm-size=2g -v $YARN_CACHE_FOLDER:$YARN_CACHE_FOLDER -e YARN_CACHE_FOLDER=$YARN_CACHE_FOLDER -e USE_SELENIUM=1 -e GALAXY_TEST_SELENIUM_RETRIES=${GALAXY_TEST_SELENIUM_RETRIES} -e GALAXY_TEST_ERRORS_DIRECTORY=${GALAXY_TEST_ERRORS_DIRECTORY} -e GALAXY_TEST_SCREENSHOTS_DIRECTORY=${GALAXY_TEST_SCREENSHOTS_DIRECTORY}" -export DOCKER_RUN_EXTRA_ARGS - -./run_tests.sh --dockerize --python3 --db postgres --clean_pyc --skip_flakey_fails --selenium "$@" diff --git a/.circleci/config.yml b/.circleci/config.yml index 3afbbc0bb98..b83111cc575 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -81,23 +81,6 @@ jobs: key: v1-repo-{{ .Environment.CIRCLE_SHA1 }} paths: - ~/repo - py27_lint: - docker: - - image: circleci/python:2.7 - <<: *set_workdir - steps: - - *restore_repo_cache - - *install_tox - - run: tox -e py27-lint - py27_unit: - docker: - - image: circleci/python:2.7 - <<: *set_workdir - steps: - - *restore_repo_cache - # Ensure minimum virtualenv version due to https://github.com/pypa/virtualenv/issues/1670 - - run: sudo pip install tox 'virtualenv>=20.0.8' - - run: tox -e py27-unit py35_docstring: docker: - image: circleci/python:3.5 @@ -106,14 +89,6 @@ jobs: - *restore_repo_cache - *install_tox - run: tox -e py35-lint_docstring_include_list - py27_first_startup: - docker: - - image: circleci/python:2.7 - <<: *set_workdir - steps: - - *restore_repo_cache - - *install_tox - - run: tox -e py27-first_startup py35_lint: docker: - image: circleci/python:3.5 @@ -136,10 +111,9 @@ jobs: <<: *set_workdir steps: - *restore_repo_cache - - run: sh scripts/common_startup.sh - - run: wget -q https://github.com/jmchilton/galaxy-downloads/raw/master/db_gx_rev_0127.sqlite - - run: mv db_gx_rev_0127.sqlite database/universe.sqlite - - run: sh manage_db.sh -c ./config/galaxy.yml.sample upgrade + # Use this job to test the latest migrations + - run: wget -q https://github.com/jmchilton/galaxy-downloads/raw/master/db_gx_rev_0141.sqlite + - run: mv db_gx_rev_0141.sqlite database/universe.sqlite - *install_tox - run: tox -e py35-first_startup validate_test_tools: @@ -195,12 +169,6 @@ workflows: get_code_and_test: jobs: - get_code - - py27_lint: - <<: *requires_get_code - - py27_unit: - <<: *requires_get_code - - py27_first_startup: - <<: *requires_get_code - py35_docstring: <<: *requires_get_code - py35_lint: diff --git a/.github/workflows/integration_selenium.yaml b/.github/workflows/integration_selenium.yaml new file mode 100644 index 00000000000..b57342c4912 --- /dev/null +++ b/.github/workflows/integration_selenium.yaml @@ -0,0 +1,45 @@ +name: Integration Selenium +on: [push, pull_request] +env: + GALAXY_TEST_DBURI: 'postgres://postgres:postgres@localhost:5432/galaxy?client_encoding=utf8' + GALAXY_TEST_SELENIUM_REMOTE: '1' + GALAXY_TEST_SELENIUM_REMOTE_PORT: "4444" + GALAXY_SKIP_CLIENT_BUILD: '0' +jobs: + test: + name: Test + runs-on: ubuntu-18.04 + strategy: + matrix: + python-version: [3.7] + services: + postgres: + image: postgres:11 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - 5432:5432 + selenium: + image: selenium/standalone-chrome:3.141.59 + ports: + - 4444:4444 + steps: + - name: Prune unused docker image, volumes and containers + run: docker system prune -a -f + - uses: actions/checkout@v2 + with: + path: 'galaxy root' + - uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Cache pip dir + uses: actions/cache@v1 + id: pip-cache + with: + path: ~/.cache/pip + key: pip-cache-${{ matrix.python-version }}-${{ hashFiles('galaxy root/requirements.txt') }} + - name: Run tests + run: './run_tests.sh -integration test/integration_selenium' + working-directory: 'galaxy root' diff --git a/.gitignore b/.gitignore index e4d13b558e7..e66973d8067 100644 --- a/.gitignore +++ b/.gitignore @@ -178,3 +178,4 @@ packages/*/*.egg-info config/plugins/visualizations/**/static/script.js # TODO: Really need to follow up on this and make it standard. config/plugins/visualizations/**/static/main.css +config/plugins/visualizations/**/static/plugin_build_hash.txt diff --git a/README.rst b/README.rst index ac5af154f94..cc66ea4bcdb 100644 --- a/README.rst +++ b/README.rst @@ -5,14 +5,14 @@ The latest information about Galaxy can be found on the `Galaxy Community Hub `__. -.. image:: https://img.shields.io/badge/chat-irc.freenode.net%23galaxyproject-blue.svg - :target: https://webchat.freenode.net/?channels=galaxyproject - :alt: Chat on irc - .. image:: https://img.shields.io/badge/chat-gitter-blue.svg :target: https://gitter.im/galaxyproject/Lobby :alt: Chat on gitter +.. image:: https://img.shields.io/badge/chat-irc.freenode.net%23galaxyproject-blue.svg + :target: https://webchat.freenode.net/?channels=galaxyproject + :alt: Chat on irc + .. image:: https://img.shields.io/badge/release-documentation-blue.svg :target: https://docs.galaxyproject.org/en/master/ :alt: Release Documentation @@ -24,12 +24,12 @@ Community support is available at `Galaxy Help Galaxy Quickstart ================= -Galaxy requires Python 2.7 To check your python version, run: +Galaxy requires Python 3.5 or 3.6 . To check your Python version, run: .. code:: console $ python -V - Python 2.7.3 + Python 3.6.10 Start Galaxy: @@ -38,18 +38,19 @@ Start Galaxy: $ sh run.sh Once Galaxy completes startup, you should be able to view Galaxy in your -browser at: +browser at: http://localhost:8080 -http://localhost:8080 +For more installation details please see: https://getgalaxy.org/ -Configuration & Tools -===================== +Documentation is available at: https://docs.galaxyproject.org/ -You may wish to make changes from the default configuration. This can be -done in the ``config/galaxy.ini`` file. +Tutorials on how to use Galaxy, perform scientific analyses with it, develop Galaxy and its tools, and admin a Galaxy server are at: https://training.galaxyproject.org/ + +Tools +===== Tools can be either installed from the Tool Shed or added manually. - For details please see the `tutorial `__. +For details please see the `tutorial `__. Note that not all dependencies for the tools provided in the ``tool_conf.xml.sample`` are included. To install them please visit "Manage dependencies" in the admin interface. @@ -58,8 +59,3 @@ Issues and Galaxy Development ============================= Please see `CONTRIBUTING.md `_ . - -Roadmap -============================= - -Interested in the next steps for Galaxy? Take a look at the `roadmap `__. diff --git a/client/galaxy/scripts/bundleEntries.js b/client/galaxy/scripts/bundleEntries.js index 2818689602f..8e6efed234d 100644 --- a/client/galaxy/scripts/bundleEntries.js +++ b/client/galaxy/scripts/bundleEntries.js @@ -95,6 +95,7 @@ export { mountJobMetrics } from "components/JobMetrics"; export { mountJobParameters } from "components/JobParameters"; export { mountWorkflowEditor } from "components/Workflow/Editor/mount"; export { mountPageDisplay } from "components/PageDisplay"; +export { mountDestinationParams } from "components/JobDestinationParams"; // Used in common.mako export { default as store } from "storemodern"; diff --git a/client/galaxy/scripts/components/JobDestinationParams/JobDestinationParams.test.js b/client/galaxy/scripts/components/JobDestinationParams/JobDestinationParams.test.js new file mode 100644 index 00000000000..1a0eb75321a --- /dev/null +++ b/client/galaxy/scripts/components/JobDestinationParams/JobDestinationParams.test.js @@ -0,0 +1,67 @@ +import Vuex from "vuex"; +import axios from "axios"; +import MockAdapter from "axios-mock-adapter"; +import { mount, createLocalVue } from "@vue/test-utils"; +import { createStore } from "../../store"; +import flushPromises from "flush-promises"; +import JobDestinationParams from "./JobDestinationParams"; +import jobDestinationResponse from "./testData/jobDestinationResponse"; + +const JOB_ID = "foo_job_id"; + +describe("JobDestinationParams/JobDestinationParams.vue", () => { + const localVue = createLocalVue(); + localVue.use(Vuex); + + const responseKeys = Object.keys(jobDestinationResponse); + + let testStore, axiosMock, wrapper; + + beforeEach(async () => { + axiosMock = new MockAdapter(axios); + testStore = createStore(); + const propsData = { + jobId: JOB_ID, + }; + axiosMock.onGet(`/api/jobs/${JOB_ID}/destination_params`).reply(200, jobDestinationResponse); + wrapper = mount(JobDestinationParams, { + store: testStore, + propsData, + localVue, + }); + await flushPromises(); + assert(responseKeys.length > 0, "test data is invalid!"); + }); + + afterEach(() => { + axiosMock.restore(); + }); + + it("destination parameters should exist", async () => { + expect(Object.keys(wrapper.vm.jobDestinationParams).length).to.equals(responseKeys.length); + expect(wrapper.vm.jobId).to.equals(JOB_ID); + expect(wrapper.vm.jobDestinationParams["docker_net"]).to.equals("bridge"); + expect(wrapper.vm.jobDestinationParams["docker_set_user"]).to.equals(null); + }); + + it("destination parameters should be rendered", async () => { + console.log(wrapper.html()); + const paramsTable = wrapper.find("#destination_parameters"); + expect(paramsTable.isVisible()).to.equals(true); + const params = paramsTable.findAll("tbody > tr"); + expect(params.length).to.equals(responseKeys.length); + + for (let counter = 0; counter < responseKeys.length - 1; counter++) { + const parameter = params.at(counter).findAll("td"); + const parameterTitle = parameter.at(0).text(); + const parameterValue = parameter.at(1).text(); + + assert(responseKeys.includes(parameterTitle), "rendered parameter should exist in test data!"); + // since we render null as an empty string, rendered empty string should always equal null in test data + assert( + jobDestinationResponse[parameterTitle] === (parameterValue === "" ? null : parameterValue), + "parameter value is not equal to test data!" + ); + } + }); +}); diff --git a/client/galaxy/scripts/components/JobDestinationParams/JobDestinationParams.vue b/client/galaxy/scripts/components/JobDestinationParams/JobDestinationParams.vue new file mode 100644 index 00000000000..7732edf8b90 --- /dev/null +++ b/client/galaxy/scripts/components/JobDestinationParams/JobDestinationParams.vue @@ -0,0 +1,36 @@ + + + diff --git a/client/galaxy/scripts/components/JobDestinationParams/index.js b/client/galaxy/scripts/components/JobDestinationParams/index.js new file mode 100644 index 00000000000..d7e3a304577 --- /dev/null +++ b/client/galaxy/scripts/components/JobDestinationParams/index.js @@ -0,0 +1,3 @@ +export { default as JobDestinationParams } from "./JobDestinationParams"; + +export { mountDestinationParams } from "./mount"; diff --git a/client/galaxy/scripts/components/JobDestinationParams/mount.js b/client/galaxy/scripts/components/JobDestinationParams/mount.js new file mode 100644 index 00000000000..271db1a40b9 --- /dev/null +++ b/client/galaxy/scripts/components/JobDestinationParams/mount.js @@ -0,0 +1,13 @@ +/** + * Endpoint for mounting job metrics from non-Vue environment. + */ +import $ from "jquery"; +import JobDestinationParams from "./JobDestinationParams.vue"; +import { mountVueComponent } from "utils/mountVueComponent"; + +export const mountDestinationParams = (propsData = {}) => { + $(".job-destination-parameters").each((index, el) => { + propsData.jobId = $(el).attr("job_id"); + mountVueComponent(JobDestinationParams)(propsData, el); + }); +}; diff --git a/client/galaxy/scripts/components/JobDestinationParams/testData/jobDestinationResponse.json b/client/galaxy/scripts/components/JobDestinationParams/testData/jobDestinationResponse.json new file mode 100644 index 00000000000..42070b6f77e --- /dev/null +++ b/client/galaxy/scripts/components/JobDestinationParams/testData/jobDestinationResponse.json @@ -0,0 +1,12 @@ +{ + "Runner": "local", + "Runner Job ID": "23027", + "Handler": "main.web.1", + "docker_auto_rm": "true", + "docker_enabled": "true", + "docker_net": "bridge", + "docker_set_user": null, + "docker_sudo": "false", + "docker_volumes": "$galaxy_root:ro,$tool_directory:ro,$job_directory:rw,$working_directory:rw,$default_file_path:ro", + "require_container": "true" +} diff --git a/client/galaxy/scripts/components/JobMetrics/JobMetrics.test.js b/client/galaxy/scripts/components/JobMetrics/JobMetrics.test.js index 0a97c76206e..ccf5122e802 100644 --- a/client/galaxy/scripts/components/JobMetrics/JobMetrics.test.js +++ b/client/galaxy/scripts/components/JobMetrics/JobMetrics.test.js @@ -5,6 +5,7 @@ import { mount, createLocalVue } from "@vue/test-utils"; import { createStore } from "../../store"; import flushPromises from "flush-promises"; import JobMetrics from "./JobMetrics"; +import ec2 from "./ec2.json"; const JOB_ID = "moo"; @@ -65,4 +66,63 @@ describe("JobMetrics/JobMetrics.vue", () => { expect(metricsTables.at(1).find(".metrics_plugin_title").text()).to.equals("extended"); expect(metricsTables.at(1).findAll("tr").length).to.equals(1); }); + + it("should render correct AWS Estimates", async () => { + let deriveRenderedAwsEstimate = async (cores, seconds, memory) => { + const JOB_ID = Math.random().toString(36).substring(2); + const propsData = { + jobId: JOB_ID, + aws_estimate: "True", + }; + const metricsResponse = [ + { plugin: "core", name: "galaxy_slots", raw_value: cores }, + { plugin: "core", name: "runtime_seconds", raw_value: seconds }, + { plugin: "core", name: "galaxy_memory_mb", raw_value: memory }, + ]; + axiosMock.onGet(`/api/jobs/${JOB_ID}/metrics`).reply(200, metricsResponse); + const wrapper = mount(JobMetrics, { + store: testStore, + propsData, + localVue, + }); + // Wait for axios and rendering. + await flushPromises(); + + const estimates = {}; + + if (!wrapper.find("#aws-estimate").exists()) return false; + + estimates.cost = wrapper.find("#aws-estimate > b").text(); + estimates.vcpus = wrapper.find("#aws_vcpus").text(); + estimates.cpu = wrapper.find("#aws_cpu").text(); + estimates.mem = wrapper.find("#aws_mem").text(); + estimates.name = wrapper.find("#aws_name").text(); + + return estimates; + }; + let assertAwsInstance = (estimates) => { + const instance = ec2.find((instance) => estimates.name === instance.name); + expect(estimates.mem).to.equals(instance.mem.toString()); + expect(estimates.vcpus).to.equals(instance.vcpus.toString()); + expect(estimates.cpu).to.equals(instance.cpu.toString()); + }; + + let estimates_small = await deriveRenderedAwsEstimate("1.0000000", "9.0000000", "2048.0000000"); + + expect(estimates_small.name).to.equals("t2.small"); + expect(estimates_small.cost).to.equals("0.00 USD"); + assertAwsInstance(estimates_small); + + let estimates_large = await deriveRenderedAwsEstimate("40.0000000", "18000.0000000", "194560.0000000"); + expect(estimates_large.name).to.equals("m5d.12xlarge"); + expect(estimates_large.cost).to.equals("16.32 USD"); + assertAwsInstance(estimates_large); + + let estimates_not_available = await deriveRenderedAwsEstimate( + "99999.0000000", + "18000.0000000", + "99999.0000000" + ); + expect(estimates_not_available).to.equals(false); + }); }); diff --git a/client/galaxy/scripts/components/JobMetrics/JobMetrics.vue b/client/galaxy/scripts/components/JobMetrics/JobMetrics.vue index f0bcd270258..9e148322c30 100644 --- a/client/galaxy/scripts/components/JobMetrics/JobMetrics.vue +++ b/client/galaxy/scripts/components/JobMetrics/JobMetrics.vue @@ -12,12 +12,27 @@ + +
+

AWS estimate

+ {{ awsEstimate.price }} USD
+ This job requested {{ awsEstimate.vcpus }} cores and {{ awsEstimate.memory }} Gb. Given this, the smallest + EC2 machine we could find is {{ awsEstimate.instance.name }} ({{ awsEstimate.instance.mem }} + GB / {{ awsEstimate.instance.vcpus }} vCPUs / + {{ awsEstimate.instance.cpu }}). That instance is priced at {{ awsEstimate.instance.price }} USD/hour.
+ Please note, that those numbers are only estimates, all jobs are always free of charge for all users. +
diff --git a/client/galaxy/scripts/components/RuleBuilder/IdentifierDisplay.vue b/client/galaxy/scripts/components/RuleBuilder/IdentifierDisplay.vue new file mode 100644 index 00000000000..54f1ce1ca4b --- /dev/null +++ b/client/galaxy/scripts/components/RuleBuilder/IdentifierDisplay.vue @@ -0,0 +1,54 @@ + + + diff --git a/client/galaxy/scripts/components/RuleBuilder/RegularExpressionInput.vue b/client/galaxy/scripts/components/RuleBuilder/RegularExpressionInput.vue new file mode 100644 index 00000000000..36f65600040 --- /dev/null +++ b/client/galaxy/scripts/components/RuleBuilder/RegularExpressionInput.vue @@ -0,0 +1,43 @@ + + + diff --git a/client/galaxy/scripts/components/RuleBuilder/RuleComponent.vue b/client/galaxy/scripts/components/RuleBuilder/RuleComponent.vue new file mode 100644 index 00000000000..474e6cbc4a4 --- /dev/null +++ b/client/galaxy/scripts/components/RuleBuilder/RuleComponent.vue @@ -0,0 +1,48 @@ + + + diff --git a/client/galaxy/scripts/components/RuleBuilder/RuleDisplay.vue b/client/galaxy/scripts/components/RuleBuilder/RuleDisplay.vue new file mode 100644 index 00000000000..1d52daf8011 --- /dev/null +++ b/client/galaxy/scripts/components/RuleBuilder/RuleDisplay.vue @@ -0,0 +1,54 @@ + + + diff --git a/client/galaxy/scripts/components/RuleBuilder/RuleModalFooter.vue b/client/galaxy/scripts/components/RuleBuilder/RuleModalFooter.vue new file mode 100644 index 00000000000..fc2a3bfe739 --- /dev/null +++ b/client/galaxy/scripts/components/RuleBuilder/RuleModalFooter.vue @@ -0,0 +1,10 @@ + diff --git a/client/galaxy/scripts/components/RuleBuilder/RuleModalHeader.vue b/client/galaxy/scripts/components/RuleBuilder/RuleModalHeader.vue new file mode 100644 index 00000000000..9ea12aefa61 --- /dev/null +++ b/client/galaxy/scripts/components/RuleBuilder/RuleModalHeader.vue @@ -0,0 +1,3 @@ + diff --git a/client/galaxy/scripts/components/RuleBuilder/RuleModalMiddle.vue b/client/galaxy/scripts/components/RuleBuilder/RuleModalMiddle.vue new file mode 100644 index 00000000000..35fca19d00a --- /dev/null +++ b/client/galaxy/scripts/components/RuleBuilder/RuleModalMiddle.vue @@ -0,0 +1,3 @@ + diff --git a/client/galaxy/scripts/components/RuleBuilder/RuleTargetComponent.vue b/client/galaxy/scripts/components/RuleBuilder/RuleTargetComponent.vue new file mode 100644 index 00000000000..aeb4d07d1df --- /dev/null +++ b/client/galaxy/scripts/components/RuleBuilder/RuleTargetComponent.vue @@ -0,0 +1,34 @@ + + + diff --git a/client/galaxy/scripts/components/RuleBuilder/StateDiv.vue b/client/galaxy/scripts/components/RuleBuilder/StateDiv.vue new file mode 100644 index 00000000000..2f06d8daf7f --- /dev/null +++ b/client/galaxy/scripts/components/RuleBuilder/StateDiv.vue @@ -0,0 +1,5 @@ + diff --git a/client/galaxy/scripts/components/RuleCollectionBuilder.vue b/client/galaxy/scripts/components/RuleCollectionBuilder.vue index d4d99436d37..185ae53f7eb 100644 --- a/client/galaxy/scripts/components/RuleCollectionBuilder.vue +++ b/client/galaxy/scripts/components/RuleCollectionBuilder.vue @@ -576,360 +576,22 @@ import RuleDefs from "mvc/rules/rule-definitions"; import Vue from "vue"; import BootstrapVue from "bootstrap-vue"; import Select2 from "components/Select2"; +import ColumnSelector from "components/RuleBuilder/ColumnSelector"; +import RegularExpressionInput from "components/RuleBuilder/RegularExpressionInput"; +import RuleDisplay from "components/RuleBuilder/RuleDisplay"; +import IdentifierDisplay from "components/RuleBuilder/IdentifierDisplay"; +import RuleTargetComponent from "components/RuleBuilder/RuleTargetComponent"; +import RuleComponent from "components/RuleBuilder/RuleComponent"; +import RuleModalHeader from "components/RuleBuilder/RuleModalHeader"; +import RuleModalMiddle from "components/RuleBuilder/RuleModalMiddle"; +import RuleModalFooter from "components/RuleBuilder/RuleModalFooter"; +import StateDiv from "components/RuleBuilder/StateDiv"; Vue.use(BootstrapVue); const RULES = RuleDefs.RULES; const MAPPING_TARGETS = RuleDefs.MAPPING_TARGETS; -const ColumnSelector = { - template: ` -
- -
-
- {{ label }} - -
    -
  1. - {{ colHeaders[targetEl] }} - - - -
  2. -
  3. - - ... {{ l("Assign Another Column") }} - - - - - - -
  4. -
-
- `, - data: function () { - return { - l: _l, - }; - }, - props: { - target: { - required: true, - }, - label: { - required: false, - type: String, - default: _l("From Column"), - }, - help: { - required: false, - }, - colHeaders: { - type: Array, - required: true, - }, - multiple: { - type: Boolean, - required: false, - default: false, - }, - ordered: { - type: Boolean, - required: false, - default: false, - }, - valueAsList: { - type: Boolean, - required: false, - default: false, - }, - orderedEdit: { - type: Boolean, - required: false, - default: false, - }, - }, - computed: { - remainingHeaders() { - const colHeaders = this.colHeaders; - if (!this.multiple) { - return colHeaders; - } - const remaining = {}; - for (const key in colHeaders) { - if (this.target.indexOf(parseInt(key)) === -1) { - remaining[key] = colHeaders[key]; - } - } - return remaining; - }, - title() { - return _l("Select a column"); - }, - }, - methods: { - handleInput(value) { - if (this.multiple) { - // https://stackoverflow.com/questions/262427/why-does-parseint-yield-nan-with-arraymap - const val = value.map((idx) => parseInt(idx)); - this.$emit("update:target", val); - } else { - let val = parseInt(value); - if (this.valueAsList) { - val = [val]; - } - this.$emit("update:target", val); - } - }, - handleAdd(value) { - this.target.push(parseInt(value)); - this.$emit("update:orderedEdit", false); - }, - handleRemove(index) { - this.target.splice(index, 1); - }, - moveUp(value) { - const swapVal = this.target[value - 1]; - Vue.set(this.target, value - 1, this.target[value]); - Vue.set(this.target, value, swapVal); - }, - }, - components: { - Select2, - }, -}; - -const RegularExpressionInput = { - template: ` -
- - - -
- `, - props: { - target: { - required: true, - }, - }, - computed: { - label() { - return _l("Regular Expression"); - }, - title() { - return _l("Enter a regular expression."); - }, - popoverTitle() { - return _l("Regular Expressions"); - }, - popoverContent() { - return _l( - `Regular expressions are patterns used to match character combinations in strings. This input accepts Python-style regular expressions, find more information about these in this Python for Biologists tutorial.` - ); - }, - }, -}; - -const RuleDisplay = { - template: ` -
  • - - {{ title }} - - - - - {{ rule.warn }} - - - {{ rule.error }} - -
  • - `, - props: { - rule: { - required: true, - type: Object, - }, - colHeaders: { - type: Array, - required: true, - }, - }, - computed: { - title() { - const ruleType = this.rule.type; - return RULES[ruleType].display(this.rule, this.colHeaders); - }, - editTitle() { - return _l("Edit this rule."); - }, - removeTitle() { - return _l("Remove this rule."); - }, - }, - methods: { - edit() { - this.$emit("edit"); - }, - remove() { - this.$emit("remove"); - }, - }, -}; - -const IdentifierDisplay = { - template: ` -
  • - Set {{ columnsLabel }} as {{ typeDisplay }} - - -
  • - `, - props: { - type: { - type: String, - required: true, - }, - columns: { - required: true, - }, - colHeaders: { - type: Array, - required: true, - }, - }, - methods: { - remove() { - this.$emit("remove"); - }, - edit() { - this.$emit("edit"); - }, - }, - computed: { - typeDisplay() { - return MAPPING_TARGETS[this.type].label; - }, - help() { - return MAPPING_TARGETS[this.type].help || ""; - }, - titleEdit() { - return _l("Edit column definition"); - }, - titleRemove() { - return _l("Remove this column definition"); - }, - columnsLabel() { - return RuleDefs.columnDisplay(this.columns, this.colHeaders); - }, - }, -}; - -const RuleTargetComponent = { - template: `{{title}}`, - props: { - ruleType: { - type: String, - required: true, - }, - builder: { - required: true, - }, - }, - computed: { - linkClassName() { - return "rule-link-" + this.ruleType.replace(/_/g, "-"); - }, - title() { - return RULES[this.ruleType].title; - }, - }, -}; - -const RuleComponent = { - template: ` -
    - -
    - - -
    -
    `, - data: function () { - return { - applyLabel: _l("Apply"), - cancelLabel: _l("Cancel"), - }; - }, - props: { - ruleType: { - type: String, - required: true, - }, - displayRuleType: { - required: true, - }, - builder: { - required: true, - }, - }, - methods: { - cancel() { - this.builder.displayRuleType = null; - }, - okay() { - this.builder.handleRuleSave(this.ruleType); - this.cancel(); - }, - }, - computed: { - typeToClass() { - return "rule-edit-" + this.ruleType.replace(/_/g, "-"); - }, - }, -}; - -const StateDiv = { - template: ` -
    - -
    `, -}; - -const RuleModalHeader = { - template: `
    `, -}; - -const RuleModalMiddle = { - template: `
    `, -}; - -const RuleModalFooter = { - template: ` - `, -}; - export default { data: function () { let orientation = "vertical"; @@ -1977,19 +1639,19 @@ export default {